roxify 1.13.2 → 1.13.4
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 +3 -1
- package/README.md +17 -13
- package/dist/cli.js +14 -19
- 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/core.rs +3 -48
- package/native/crypto.rs +5 -0
- package/native/encoder.rs +10 -228
- package/native/lib.rs +54 -39
- package/native/main.rs +53 -21
- package/native/packer.rs +57 -6
- package/native/streaming_decode.rs +141 -7
- package/native/streaming_encode.rs +284 -112
- 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/native/lib.rs
CHANGED
|
@@ -69,51 +69,51 @@ pub fn native_adler32(buffer: Buffer) -> u32 {
|
|
|
69
69
|
|
|
70
70
|
#[cfg(not(test))]
|
|
71
71
|
#[napi]
|
|
72
|
-
pub fn native_delta_encode(buffer: Buffer) ->
|
|
73
|
-
core::delta_encode_bytes(&buffer)
|
|
72
|
+
pub fn native_delta_encode(buffer: Buffer) -> Buffer {
|
|
73
|
+
core::delta_encode_bytes(&buffer).into()
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
#[cfg(not(test))]
|
|
77
77
|
#[napi]
|
|
78
|
-
pub fn native_delta_decode(buffer: Buffer) ->
|
|
79
|
-
core::delta_decode_bytes(&buffer)
|
|
78
|
+
pub fn native_delta_decode(buffer: Buffer) -> Buffer {
|
|
79
|
+
core::delta_decode_bytes(&buffer).into()
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
#[cfg(not(test))]
|
|
83
83
|
#[napi]
|
|
84
|
-
pub fn native_zstd_compress(buffer: Buffer, level: i32) -> Result<
|
|
85
|
-
core::zstd_compress_bytes(&buffer, level, None).map_err(|e| Error::from_reason(e))
|
|
84
|
+
pub fn native_zstd_compress(buffer: Buffer, level: i32) -> Result<Buffer> {
|
|
85
|
+
core::zstd_compress_bytes(&buffer, level, None).map(Buffer::from).map_err(|e| Error::from_reason(e))
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
#[cfg(not(test))]
|
|
89
89
|
#[napi]
|
|
90
|
-
pub fn native_zstd_compress_with_dict(buffer: Buffer, level: i32, dict: Buffer) -> Result<
|
|
90
|
+
pub fn native_zstd_compress_with_dict(buffer: Buffer, level: i32, dict: Buffer) -> Result<Buffer> {
|
|
91
91
|
let dict_slice: &[u8] = &dict;
|
|
92
|
-
core::zstd_compress_bytes(&buffer, level, Some(dict_slice)).map_err(|e| Error::from_reason(e))
|
|
92
|
+
core::zstd_compress_bytes(&buffer, level, Some(dict_slice)).map(Buffer::from).map_err(|e| Error::from_reason(e))
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
#[cfg(not(test))]
|
|
96
96
|
#[napi]
|
|
97
|
-
pub fn native_zstd_decompress(buffer: Buffer) -> Result<
|
|
98
|
-
core::zstd_decompress_bytes(&buffer, None).map_err(|e| Error::from_reason(e))
|
|
97
|
+
pub fn native_zstd_decompress(buffer: Buffer) -> Result<Buffer> {
|
|
98
|
+
core::zstd_decompress_bytes(&buffer, None).map(Buffer::from).map_err(|e| Error::from_reason(e))
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
#[cfg(not(test))]
|
|
102
102
|
#[napi]
|
|
103
|
-
pub fn native_zstd_decompress_with_dict(buffer: Buffer, dict: Buffer) -> Result<
|
|
103
|
+
pub fn native_zstd_decompress_with_dict(buffer: Buffer, dict: Buffer) -> Result<Buffer> {
|
|
104
104
|
let dict_slice: &[u8] = &dict;
|
|
105
|
-
core::zstd_decompress_bytes(&buffer, Some(dict_slice)).map_err(|e| Error::from_reason(e))
|
|
105
|
+
core::zstd_decompress_bytes(&buffer, Some(dict_slice)).map(Buffer::from).map_err(|e| Error::from_reason(e))
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
#[cfg(not(test))]
|
|
109
109
|
#[napi]
|
|
110
|
-
pub fn bwt_transform(buffer: Buffer) -> Result<
|
|
110
|
+
pub fn bwt_transform(buffer: Buffer) -> Result<Buffer> {
|
|
111
111
|
match bwt::bwt_encode(&buffer) {
|
|
112
112
|
Ok(result) => {
|
|
113
113
|
let mut output = Vec::with_capacity(4 + result.transformed.len());
|
|
114
114
|
output.extend_from_slice(&result.primary_index.to_le_bytes());
|
|
115
115
|
output.extend_from_slice(&result.transformed);
|
|
116
|
-
Ok(output)
|
|
116
|
+
Ok(output.into())
|
|
117
117
|
}
|
|
118
118
|
Err(e) => Err(Error::from_reason(e.to_string())),
|
|
119
119
|
}
|
|
@@ -191,15 +191,17 @@ mod tests {
|
|
|
191
191
|
|
|
192
192
|
#[cfg(not(test))]
|
|
193
193
|
#[napi]
|
|
194
|
-
pub fn native_encode_png(buffer: Buffer, compression_level: i32) -> Result<
|
|
194
|
+
pub fn native_encode_png(buffer: Buffer, compression_level: i32) -> Result<Buffer> {
|
|
195
195
|
encoder::encode_to_png(&buffer, compression_level)
|
|
196
|
+
.map(Buffer::from)
|
|
196
197
|
.map_err(|e| Error::from_reason(e.to_string()))
|
|
197
198
|
}
|
|
198
199
|
|
|
199
200
|
#[cfg(not(test))]
|
|
200
201
|
#[napi]
|
|
201
|
-
pub fn native_encode_png_raw(buffer: Buffer, compression_level: i32) -> Result<
|
|
202
|
+
pub fn native_encode_png_raw(buffer: Buffer, compression_level: i32) -> Result<Buffer> {
|
|
202
203
|
encoder::encode_to_png_raw(&buffer, compression_level)
|
|
204
|
+
.map(Buffer::from)
|
|
203
205
|
.map_err(|e| Error::from_reason(e.to_string()))
|
|
204
206
|
}
|
|
205
207
|
|
|
@@ -210,13 +212,14 @@ pub fn native_encode_png_with_name_and_filelist(
|
|
|
210
212
|
compression_level: i32,
|
|
211
213
|
name: Option<String>,
|
|
212
214
|
file_list_json: Option<String>,
|
|
213
|
-
) -> Result<
|
|
215
|
+
) -> Result<Buffer> {
|
|
214
216
|
encoder::encode_to_png_with_name_and_filelist(
|
|
215
217
|
&buffer,
|
|
216
218
|
compression_level,
|
|
217
219
|
name.as_deref(),
|
|
218
220
|
file_list_json.as_deref(),
|
|
219
221
|
)
|
|
222
|
+
.map(Buffer::from)
|
|
220
223
|
.map_err(|e| Error::from_reason(e.to_string()))
|
|
221
224
|
}
|
|
222
225
|
|
|
@@ -229,7 +232,7 @@ pub fn native_encode_png_with_encryption_name_and_filelist(
|
|
|
229
232
|
encrypt_type: Option<String>,
|
|
230
233
|
name: Option<String>,
|
|
231
234
|
file_list_json: Option<String>,
|
|
232
|
-
) -> Result<
|
|
235
|
+
) -> Result<Buffer> {
|
|
233
236
|
encoder::encode_to_png_with_encryption_name_and_filelist(
|
|
234
237
|
&buffer,
|
|
235
238
|
compression_level,
|
|
@@ -238,13 +241,14 @@ pub fn native_encode_png_with_encryption_name_and_filelist(
|
|
|
238
241
|
name.as_deref(),
|
|
239
242
|
file_list_json.as_deref(),
|
|
240
243
|
)
|
|
244
|
+
.map(Buffer::from)
|
|
241
245
|
.map_err(|e| Error::from_reason(e.to_string()))
|
|
242
246
|
}
|
|
243
247
|
|
|
244
248
|
#[napi(object)]
|
|
245
249
|
pub struct PngChunkData {
|
|
246
250
|
pub name: String,
|
|
247
|
-
pub data:
|
|
251
|
+
pub data: Buffer,
|
|
248
252
|
}
|
|
249
253
|
|
|
250
254
|
#[cfg(not(test))]
|
|
@@ -255,21 +259,22 @@ pub fn extract_png_chunks(png_buffer: Buffer) -> Result<Vec<PngChunkData>> {
|
|
|
255
259
|
|
|
256
260
|
Ok(chunks.into_iter().map(|c| PngChunkData {
|
|
257
261
|
name: c.name,
|
|
258
|
-
data: c.data,
|
|
262
|
+
data: c.data.into(),
|
|
259
263
|
}).collect())
|
|
260
264
|
}
|
|
261
265
|
|
|
262
266
|
#[cfg(not(test))]
|
|
263
267
|
#[napi]
|
|
264
|
-
pub fn encode_png_chunks(chunks: Vec<PngChunkData>) -> Result<
|
|
268
|
+
pub fn encode_png_chunks(chunks: Vec<PngChunkData>) -> Result<Buffer> {
|
|
265
269
|
let native_chunks: Vec<png_utils::PngChunk> = chunks.into_iter()
|
|
266
270
|
.map(|c| png_utils::PngChunk {
|
|
267
271
|
name: c.name,
|
|
268
|
-
data: c.data,
|
|
272
|
+
data: c.data.to_vec(),
|
|
269
273
|
})
|
|
270
274
|
.collect();
|
|
271
275
|
|
|
272
276
|
png_utils::encode_png_chunks(&native_chunks)
|
|
277
|
+
.map(Buffer::from)
|
|
273
278
|
.map_err(|e| Error::from_reason(e))
|
|
274
279
|
}
|
|
275
280
|
|
|
@@ -309,22 +314,23 @@ pub fn sharp_resize_image(
|
|
|
309
314
|
width: u32,
|
|
310
315
|
height: u32,
|
|
311
316
|
kernel: String,
|
|
312
|
-
) -> Result<
|
|
317
|
+
) -> Result<Buffer> {
|
|
313
318
|
image_utils::sharp_resize(&input_buffer, width, height, &kernel)
|
|
319
|
+
.map(Buffer::from)
|
|
314
320
|
.map_err(|e| Error::from_reason(e))
|
|
315
321
|
}
|
|
316
322
|
|
|
317
323
|
#[cfg(not(test))]
|
|
318
324
|
#[napi]
|
|
319
|
-
pub fn sharp_raw_pixels(input_buffer: Buffer) -> Result<
|
|
325
|
+
pub fn sharp_raw_pixels(input_buffer: Buffer) -> Result<Buffer> {
|
|
320
326
|
let (pixels, _w, _h) = image_utils::sharp_raw_pixels(&input_buffer)
|
|
321
327
|
.map_err(|e| Error::from_reason(e))?;
|
|
322
|
-
Ok(pixels)
|
|
328
|
+
Ok(pixels.into())
|
|
323
329
|
}
|
|
324
330
|
|
|
325
331
|
#[napi(object)]
|
|
326
332
|
pub struct RawPixelsWithDimensions {
|
|
327
|
-
pub pixels:
|
|
333
|
+
pub pixels: Buffer,
|
|
328
334
|
pub width: u32,
|
|
329
335
|
pub height: u32,
|
|
330
336
|
}
|
|
@@ -334,7 +340,7 @@ pub struct RawPixelsWithDimensions {
|
|
|
334
340
|
pub fn sharp_to_raw(input_buffer: Buffer) -> Result<RawPixelsWithDimensions> {
|
|
335
341
|
let (pixels, width, height) = image_utils::sharp_raw_pixels(&input_buffer)
|
|
336
342
|
.map_err(|e| Error::from_reason(e))?;
|
|
337
|
-
Ok(RawPixelsWithDimensions { pixels, width, height })
|
|
343
|
+
Ok(RawPixelsWithDimensions { pixels: pixels.into(), width, height })
|
|
338
344
|
}
|
|
339
345
|
|
|
340
346
|
#[cfg(not(test))]
|
|
@@ -347,8 +353,9 @@ pub fn sharp_metadata(input_buffer: Buffer) -> Result<SharpMetadata> {
|
|
|
347
353
|
|
|
348
354
|
#[cfg(not(test))]
|
|
349
355
|
#[napi]
|
|
350
|
-
pub fn rgb_to_png(rgb_buffer: Buffer, width: u32, height: u32) -> Result<
|
|
356
|
+
pub fn rgb_to_png(rgb_buffer: Buffer, width: u32, height: u32) -> Result<Buffer> {
|
|
351
357
|
image_utils::rgb_to_png(&rgb_buffer, width, height)
|
|
358
|
+
.map(Buffer::from)
|
|
352
359
|
.map_err(|e| Error::from_reason(e))
|
|
353
360
|
}
|
|
354
361
|
|
|
@@ -357,27 +364,30 @@ pub fn rgb_to_png(rgb_buffer: Buffer, width: u32, height: u32) -> Result<Vec<u8>
|
|
|
357
364
|
pub fn png_to_rgb(png_buffer: Buffer) -> Result<RawPixelsWithDimensions> {
|
|
358
365
|
let (pixels, width, height) = image_utils::png_to_rgb(&png_buffer)
|
|
359
366
|
.map_err(|e| Error::from_reason(e))?;
|
|
360
|
-
Ok(RawPixelsWithDimensions { pixels, width, height })
|
|
367
|
+
Ok(RawPixelsWithDimensions { pixels: pixels.into(), width, height })
|
|
361
368
|
}
|
|
362
369
|
|
|
363
370
|
#[cfg(not(test))]
|
|
364
371
|
#[napi]
|
|
365
|
-
pub fn crop_and_reconstitute(png_buffer: Buffer) -> Result<
|
|
372
|
+
pub fn crop_and_reconstitute(png_buffer: Buffer) -> Result<Buffer> {
|
|
366
373
|
reconstitution::crop_and_reconstitute(&png_buffer)
|
|
374
|
+
.map(Buffer::from)
|
|
367
375
|
.map_err(|e| Error::from_reason(e))
|
|
368
376
|
}
|
|
369
377
|
|
|
370
378
|
#[cfg(not(test))]
|
|
371
379
|
#[napi]
|
|
372
|
-
pub fn unstretch_nn(png_buffer: Buffer) -> Result<
|
|
380
|
+
pub fn unstretch_nn(png_buffer: Buffer) -> Result<Buffer> {
|
|
373
381
|
reconstitution::unstretch_nn(&png_buffer)
|
|
382
|
+
.map(Buffer::from)
|
|
374
383
|
.map_err(|e| Error::from_reason(e))
|
|
375
384
|
}
|
|
376
385
|
|
|
377
386
|
#[cfg(not(test))]
|
|
378
387
|
#[napi]
|
|
379
|
-
pub fn extract_payload_from_png(png_buffer: Buffer) -> Result<
|
|
388
|
+
pub fn extract_payload_from_png(png_buffer: Buffer) -> Result<Buffer> {
|
|
380
389
|
png_utils::extract_payload_from_png(&png_buffer)
|
|
390
|
+
.map(Buffer::from)
|
|
381
391
|
.map_err(|e| Error::from_reason(e))
|
|
382
392
|
}
|
|
383
393
|
|
|
@@ -392,8 +402,9 @@ pub fn extract_file_list_from_pixels(png_buffer: Buffer) -> Result<String> {
|
|
|
392
402
|
|
|
393
403
|
#[cfg(not(test))]
|
|
394
404
|
#[napi]
|
|
395
|
-
pub fn native_encode_wav(buffer: Buffer, compression_level: i32) -> Result<
|
|
405
|
+
pub fn native_encode_wav(buffer: Buffer, compression_level: i32) -> Result<Buffer> {
|
|
396
406
|
encoder::encode_to_wav(&buffer, compression_level)
|
|
407
|
+
.map(Buffer::from)
|
|
397
408
|
.map_err(|e| Error::from_reason(e.to_string()))
|
|
398
409
|
}
|
|
399
410
|
|
|
@@ -404,13 +415,14 @@ pub fn native_encode_wav_with_name_and_filelist(
|
|
|
404
415
|
compression_level: i32,
|
|
405
416
|
name: Option<String>,
|
|
406
417
|
file_list_json: Option<String>,
|
|
407
|
-
) -> Result<
|
|
418
|
+
) -> Result<Buffer> {
|
|
408
419
|
encoder::encode_to_wav_with_name_and_filelist(
|
|
409
420
|
&buffer,
|
|
410
421
|
compression_level,
|
|
411
422
|
name.as_deref(),
|
|
412
423
|
file_list_json.as_deref(),
|
|
413
424
|
)
|
|
425
|
+
.map(Buffer::from)
|
|
414
426
|
.map_err(|e| Error::from_reason(e.to_string()))
|
|
415
427
|
}
|
|
416
428
|
|
|
@@ -423,7 +435,7 @@ pub fn native_encode_wav_with_encryption_name_and_filelist(
|
|
|
423
435
|
encrypt_type: Option<String>,
|
|
424
436
|
name: Option<String>,
|
|
425
437
|
file_list_json: Option<String>,
|
|
426
|
-
) -> Result<
|
|
438
|
+
) -> Result<Buffer> {
|
|
427
439
|
encoder::encode_to_wav_with_encryption_name_and_filelist(
|
|
428
440
|
&buffer,
|
|
429
441
|
compression_level,
|
|
@@ -432,26 +444,29 @@ pub fn native_encode_wav_with_encryption_name_and_filelist(
|
|
|
432
444
|
name.as_deref(),
|
|
433
445
|
file_list_json.as_deref(),
|
|
434
446
|
)
|
|
447
|
+
.map(Buffer::from)
|
|
435
448
|
.map_err(|e| Error::from_reason(e.to_string()))
|
|
436
449
|
}
|
|
437
450
|
|
|
438
451
|
#[cfg(not(test))]
|
|
439
452
|
#[napi]
|
|
440
|
-
pub fn native_decode_wav_payload(wav_buffer: Buffer) -> Result<
|
|
453
|
+
pub fn native_decode_wav_payload(wav_buffer: Buffer) -> Result<Buffer> {
|
|
441
454
|
encoder::decode_wav_payload(&wav_buffer)
|
|
455
|
+
.map(Buffer::from)
|
|
442
456
|
.map_err(|e| Error::from_reason(e.to_string()))
|
|
443
457
|
}
|
|
444
458
|
|
|
445
459
|
#[cfg(not(test))]
|
|
446
460
|
#[napi]
|
|
447
|
-
pub fn native_bytes_to_wav(buffer: Buffer) ->
|
|
448
|
-
audio::bytes_to_wav(&buffer)
|
|
461
|
+
pub fn native_bytes_to_wav(buffer: Buffer) -> Buffer {
|
|
462
|
+
audio::bytes_to_wav(&buffer).into()
|
|
449
463
|
}
|
|
450
464
|
|
|
451
465
|
#[cfg(not(test))]
|
|
452
466
|
#[napi]
|
|
453
|
-
pub fn native_wav_to_bytes(wav_buffer: Buffer) -> Result<
|
|
467
|
+
pub fn native_wav_to_bytes(wav_buffer: Buffer) -> Result<Buffer> {
|
|
454
468
|
audio::wav_to_bytes(&wav_buffer)
|
|
469
|
+
.map(Buffer::from)
|
|
455
470
|
.map_err(|e| Error::from_reason(e))
|
|
456
471
|
}
|
|
457
472
|
|
package/native/main.rs
CHANGED
|
@@ -144,6 +144,19 @@ fn parse_markers(v: &[String]) -> Option<Vec<u8>> {
|
|
|
144
144
|
|
|
145
145
|
fn main() -> anyhow::Result<()> {
|
|
146
146
|
let cli = Cli::parse();
|
|
147
|
+
|
|
148
|
+
fn parse_requested_files(files: &str) -> anyhow::Result<Vec<String>> {
|
|
149
|
+
if files.trim_start().starts_with('[') {
|
|
150
|
+
serde_json::from_str::<Vec<String>>(files)
|
|
151
|
+
.map_err(|e| anyhow::anyhow!("Invalid JSON for --files: {}", e))
|
|
152
|
+
} else {
|
|
153
|
+
Ok(files
|
|
154
|
+
.split(',')
|
|
155
|
+
.map(|file| file.trim().to_string())
|
|
156
|
+
.filter(|file| !file.is_empty())
|
|
157
|
+
.collect())
|
|
158
|
+
}
|
|
159
|
+
}
|
|
147
160
|
match cli.command {
|
|
148
161
|
Commands::TrainDict { samples, size, output } => {
|
|
149
162
|
let dict = core::train_zstd_dictionary(&samples, size)?;
|
|
@@ -169,7 +182,7 @@ fn main() -> anyhow::Result<()> {
|
|
|
169
182
|
eprintln!("PROGRESS:{}:{}:{}", current, total, step);
|
|
170
183
|
})),
|
|
171
184
|
)?;
|
|
172
|
-
println!("(
|
|
185
|
+
println!("(directory payload, rXFL chunk embedded)");
|
|
173
186
|
return Ok(());
|
|
174
187
|
}
|
|
175
188
|
|
|
@@ -234,7 +247,7 @@ fn main() -> anyhow::Result<()> {
|
|
|
234
247
|
if file_list_json.is_some() {
|
|
235
248
|
eprintln!("PROGRESS:100:100:done");
|
|
236
249
|
if is_dir {
|
|
237
|
-
println!("(
|
|
250
|
+
println!("(directory payload, rXFL chunk embedded)");
|
|
238
251
|
} else {
|
|
239
252
|
println!("(rXFL chunk embedded)");
|
|
240
253
|
}
|
|
@@ -343,13 +356,24 @@ fn main() -> anyhow::Result<()> {
|
|
|
343
356
|
&& sig == [137, 80, 78, 71, 13, 10, 26, 10]
|
|
344
357
|
});
|
|
345
358
|
|
|
346
|
-
|
|
359
|
+
let requested_files = match files.as_deref() {
|
|
360
|
+
Some(files_str) => Some(parse_requested_files(files_str)?),
|
|
361
|
+
None => None,
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
if is_png_file && files.is_none() && dict.is_none() {
|
|
347
365
|
let out_dir = output.clone().unwrap_or_else(|| PathBuf::from("out.raw"));
|
|
348
|
-
|
|
349
|
-
|
|
366
|
+
match streaming_decode::streaming_decode_to_dir_encrypted_with_progress(
|
|
367
|
+
&input,
|
|
368
|
+
&out_dir,
|
|
369
|
+
passphrase.as_deref(),
|
|
370
|
+
Some(Box::new(|current, total, step| {
|
|
371
|
+
eprintln!("PROGRESS:{}:{}:{}", current, total, step);
|
|
372
|
+
})),
|
|
373
|
+
) {
|
|
350
374
|
Ok(written) => {
|
|
351
375
|
eprintln!("PROGRESS:100:100:done");
|
|
352
|
-
println!("Unpacked {} files
|
|
376
|
+
println!("Unpacked {} files", written.len());
|
|
353
377
|
return Ok(());
|
|
354
378
|
}
|
|
355
379
|
Err(e) => {
|
|
@@ -358,25 +382,31 @@ fn main() -> anyhow::Result<()> {
|
|
|
358
382
|
}
|
|
359
383
|
}
|
|
360
384
|
|
|
385
|
+
if is_png_file && requested_files.is_some() && dict.is_none() {
|
|
386
|
+
let out_dir = output.clone().unwrap_or_else(|| PathBuf::from("."));
|
|
387
|
+
std::fs::create_dir_all(&out_dir).map_err(|e| anyhow::anyhow!("Cannot create output directory {:?}: {}", out_dir, e))?;
|
|
388
|
+
let written = streaming_decode::streaming_decode_selected_to_dir_encrypted_with_progress(
|
|
389
|
+
&input,
|
|
390
|
+
&out_dir,
|
|
391
|
+
requested_files.as_deref(),
|
|
392
|
+
passphrase.as_deref(),
|
|
393
|
+
Some(Box::new(|current, total, step| {
|
|
394
|
+
eprintln!("PROGRESS:{}:{}:{}", current, total, step);
|
|
395
|
+
})),
|
|
396
|
+
).map_err(|e| anyhow::anyhow!(e))?;
|
|
397
|
+
eprintln!("PROGRESS:100:100:done");
|
|
398
|
+
println!("Unpacked {} files", written.len());
|
|
399
|
+
return Ok(());
|
|
400
|
+
}
|
|
401
|
+
|
|
361
402
|
let buf = read_all(&input)?;
|
|
362
403
|
eprintln!("PROGRESS:20:100:decompressing");
|
|
363
404
|
let dict_bytes: Option<Vec<u8>> = match dict {
|
|
364
405
|
Some(path) => Some(read_all(&path)?),
|
|
365
406
|
None => None,
|
|
366
407
|
};
|
|
367
|
-
if
|
|
368
|
-
let file_list
|
|
369
|
-
match serde_json::from_str::<Vec<String>>(&files_str) {
|
|
370
|
-
Ok(v) => Some(v),
|
|
371
|
-
Err(e) => {
|
|
372
|
-
eprintln!("Invalid JSON for --files: {}", e);
|
|
373
|
-
std::process::exit(1);
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
} else {
|
|
377
|
-
let list = files_str.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect::<Vec<_>>();
|
|
378
|
-
Some(list)
|
|
379
|
-
};
|
|
408
|
+
if requested_files.is_some() {
|
|
409
|
+
let file_list = requested_files;
|
|
380
410
|
|
|
381
411
|
let is_png = buf.len() >= 8 && &buf[0..8] == &[137, 80, 78, 71, 13, 10, 26, 10];
|
|
382
412
|
|
|
@@ -421,7 +451,9 @@ fn main() -> anyhow::Result<()> {
|
|
|
421
451
|
std::fs::create_dir_all(&out_dir).map_err(|e| anyhow::anyhow!("Cannot create output directory {:?}: {}", out_dir, e))?;
|
|
422
452
|
let files_slice = file_list.as_ref().map(|v| v.as_slice());
|
|
423
453
|
|
|
424
|
-
let written = packer::unpack_stream_to_dir(&mut reader, &out_dir, files_slice
|
|
454
|
+
let written = packer::unpack_stream_to_dir(&mut reader, &out_dir, files_slice, Some(&|current, total, step| {
|
|
455
|
+
eprintln!("PROGRESS:{}:{}:{}", current, total, step);
|
|
456
|
+
}), 0).map_err(|e| anyhow::anyhow!(e))?;
|
|
425
457
|
eprintln!("PROGRESS:100:100:done");
|
|
426
458
|
println!("Unpacked {} files", written.len());
|
|
427
459
|
} else {
|
|
@@ -492,7 +524,7 @@ fn main() -> anyhow::Result<()> {
|
|
|
492
524
|
.map_err(|e| anyhow::anyhow!("mkdir {:?}: {}", out_dir, e))?;
|
|
493
525
|
let written = archive::tar_unpack(&out_bytes, &out_dir)
|
|
494
526
|
.map_err(|e| anyhow::anyhow!(e))?;
|
|
495
|
-
println!("Unpacked {} files
|
|
527
|
+
println!("Unpacked {} files to {:?}", written.len(), out_dir);
|
|
496
528
|
} else if out_bytes.len() >= 4
|
|
497
529
|
&& (u32::from_be_bytes(out_bytes[0..4].try_into().unwrap()) == 0x524f5850u32
|
|
498
530
|
|| u32::from_be_bytes(out_bytes[0..4].try_into().unwrap()) == 0x524f5849u32)
|
package/native/packer.rs
CHANGED
|
@@ -255,13 +255,50 @@ fn unpack_entries_sequential(buf: &[u8], start: usize, out_dir: &Path, files_opt
|
|
|
255
255
|
Ok(written)
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
|
|
258
|
+
fn unpack_progress_percent(total_expected: u64, bytes_processed: u64, file_count: usize, processed_files: usize) -> u64 {
|
|
259
|
+
if total_expected > 0 {
|
|
260
|
+
return 10 + (bytes_processed.saturating_mul(89) / total_expected).min(89);
|
|
261
|
+
}
|
|
262
|
+
if file_count > 0 {
|
|
263
|
+
return 10 + ((processed_files as u64).saturating_mul(89) / file_count as u64).min(89);
|
|
264
|
+
}
|
|
265
|
+
10
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
fn report_unpack_progress(
|
|
269
|
+
progress: Option<&(dyn Fn(u64, u64, &str) + Send)>,
|
|
270
|
+
total_expected: u64,
|
|
271
|
+
bytes_processed: u64,
|
|
272
|
+
file_count: usize,
|
|
273
|
+
processed_files: usize,
|
|
274
|
+
last_pct: &mut u64,
|
|
275
|
+
) {
|
|
276
|
+
if let Some(cb) = progress {
|
|
277
|
+
let pct = unpack_progress_percent(total_expected, bytes_processed, file_count, processed_files);
|
|
278
|
+
if pct > *last_pct {
|
|
279
|
+
*last_pct = pct;
|
|
280
|
+
cb(pct, 100, "extracting");
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
pub fn unpack_stream_to_dir<R: std::io::Read>(
|
|
286
|
+
reader: &mut R,
|
|
287
|
+
out_dir: &Path,
|
|
288
|
+
files_opt: Option<&[String]>,
|
|
289
|
+
progress: Option<&(dyn Fn(u64, u64, &str) + Send)>,
|
|
290
|
+
total_expected: u64,
|
|
291
|
+
) -> Result<Vec<String>> {
|
|
259
292
|
let mut written = Vec::new();
|
|
260
293
|
let mut buf: Vec<u8> = Vec::new();
|
|
261
294
|
let mut pos: usize = 0;
|
|
262
295
|
let mut temp = [0u8; 64 * 1024];
|
|
263
296
|
let files_filter: Option<std::collections::HashSet<String>> = files_opt.map(|l| l.iter().map(|s| s.clone()).collect());
|
|
264
297
|
let mut requested = files_filter.as_ref().map(|s| s.len()).unwrap_or(usize::MAX);
|
|
298
|
+
let mut file_count = 0usize;
|
|
299
|
+
let mut processed_files = 0usize;
|
|
300
|
+
let mut bytes_processed = 0u64;
|
|
301
|
+
let mut last_pct = 10u64;
|
|
265
302
|
|
|
266
303
|
let mut header_parsed = false;
|
|
267
304
|
let debug = std::env::var("ROX_DEBUG").is_ok();
|
|
@@ -280,10 +317,10 @@ pub fn unpack_stream_to_dir<R: std::io::Read>(reader: &mut R, out_dir: &Path, fi
|
|
|
280
317
|
if debug { eprintln!("[rox debug] magic_header=0x{:08x}", magic_header); }
|
|
281
318
|
if magic_header == 0x524f5850u32 {
|
|
282
319
|
pos += 4;
|
|
283
|
-
|
|
320
|
+
file_count = u32::from_be_bytes(buf[pos..pos+4].try_into().unwrap()) as usize;
|
|
284
321
|
pos += 4;
|
|
285
322
|
header_parsed = true;
|
|
286
|
-
if debug { eprintln!("[rox debug] header parsed, file_count={}",
|
|
323
|
+
if debug { eprintln!("[rox debug] header parsed, file_count={}", file_count); }
|
|
287
324
|
} else if magic_header == 0x524f5831u32 {
|
|
288
325
|
if debug { eprintln!("[rox debug] found ROX1 outer magic, skipping 4 bytes"); }
|
|
289
326
|
pos += 4;
|
|
@@ -310,6 +347,8 @@ pub fn unpack_stream_to_dir<R: std::io::Read>(reader: &mut R, out_dir: &Path, fi
|
|
|
310
347
|
let content_start = pos + 2 + name_len + 8;
|
|
311
348
|
let content_end = content_start + size;
|
|
312
349
|
let content = &buf[content_start..content_end];
|
|
350
|
+
processed_files = processed_files.saturating_add(1);
|
|
351
|
+
bytes_processed = bytes_processed.saturating_add(size as u64);
|
|
313
352
|
|
|
314
353
|
let p = Path::new(&name);
|
|
315
354
|
let mut safe = std::path::PathBuf::new();
|
|
@@ -328,10 +367,18 @@ pub fn unpack_stream_to_dir<R: std::io::Read>(reader: &mut R, out_dir: &Path, fi
|
|
|
328
367
|
written.push(safe.to_string_lossy().to_string());
|
|
329
368
|
if let Some(_set) = files_filter.as_ref() {
|
|
330
369
|
requested = requested.saturating_sub(1);
|
|
331
|
-
|
|
370
|
+
report_unpack_progress(progress, total_expected, bytes_processed, file_count, processed_files, &mut last_pct);
|
|
371
|
+
if requested == 0 {
|
|
372
|
+
if let Some(cb) = progress {
|
|
373
|
+
cb(99, 100, "finishing");
|
|
374
|
+
}
|
|
375
|
+
return Ok(written);
|
|
376
|
+
}
|
|
332
377
|
}
|
|
333
378
|
}
|
|
334
379
|
|
|
380
|
+
report_unpack_progress(progress, total_expected, bytes_processed, file_count, processed_files, &mut last_pct);
|
|
381
|
+
|
|
335
382
|
pos = content_end; if pos > 0 {
|
|
336
383
|
buf.drain(0..pos);
|
|
337
384
|
pos = 0;
|
|
@@ -344,6 +391,10 @@ pub fn unpack_stream_to_dir<R: std::io::Read>(reader: &mut R, out_dir: &Path, fi
|
|
|
344
391
|
}
|
|
345
392
|
}
|
|
346
393
|
|
|
394
|
+
if let Some(cb) = progress {
|
|
395
|
+
cb(99, 100, "finishing");
|
|
396
|
+
}
|
|
397
|
+
|
|
347
398
|
Ok(written)
|
|
348
399
|
}
|
|
349
400
|
|
|
@@ -390,7 +441,7 @@ mod stream_tests {
|
|
|
390
441
|
let tmpdir = std::env::temp_dir().join(format!("rox_unpack_test_{}", ms));
|
|
391
442
|
let _ = std::fs::create_dir_all(&tmpdir);
|
|
392
443
|
|
|
393
|
-
let out = unpack_stream_to_dir(&mut dec2, &tmpdir, None)?;
|
|
444
|
+
let out = unpack_stream_to_dir(&mut dec2, &tmpdir, None, None, 0)?;
|
|
394
445
|
|
|
395
446
|
assert_eq!(out.len(), 2);
|
|
396
447
|
assert!(tmpdir.join("file1.txt").exists());
|
|
@@ -432,7 +483,7 @@ mod stream_tests {
|
|
|
432
483
|
let tmpdir = std::env::temp_dir().join(format!("rox_unpack_png_test_{}", ms));
|
|
433
484
|
let _ = std::fs::create_dir_all(&tmpdir);
|
|
434
485
|
|
|
435
|
-
let out = unpack_stream_to_dir(&mut dec, &tmpdir, None)?;
|
|
486
|
+
let out = unpack_stream_to_dir(&mut dec, &tmpdir, None, None, 0)?;
|
|
436
487
|
|
|
437
488
|
assert_eq!(out.len(), 2);
|
|
438
489
|
assert!(tmpdir.join("file1.txt").exists());
|