roxify 1.13.2 → 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 -1
- package/README.md +4 -2
- 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 +9 -3
- package/native/streaming_decode.rs +91 -7
- package/native/streaming_encode.rs +54 -59
- 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
|
@@ -343,10 +343,16 @@ fn main() -> anyhow::Result<()> {
|
|
|
343
343
|
&& sig == [137, 80, 78, 71, 13, 10, 26, 10]
|
|
344
344
|
});
|
|
345
345
|
|
|
346
|
-
if is_png_file && files.is_none() && dict.is_none()
|
|
346
|
+
if is_png_file && files.is_none() && dict.is_none() {
|
|
347
347
|
let out_dir = output.clone().unwrap_or_else(|| PathBuf::from("out.raw"));
|
|
348
|
-
|
|
349
|
-
|
|
348
|
+
match streaming_decode::streaming_decode_to_dir_encrypted_with_progress(
|
|
349
|
+
&input,
|
|
350
|
+
&out_dir,
|
|
351
|
+
passphrase.as_deref(),
|
|
352
|
+
Some(Box::new(|current, total, step| {
|
|
353
|
+
eprintln!("PROGRESS:{}:{}:{}", current, total, step);
|
|
354
|
+
})),
|
|
355
|
+
) {
|
|
350
356
|
Ok(written) => {
|
|
351
357
|
eprintln!("PROGRESS:100:100:done");
|
|
352
358
|
println!("Unpacked {} files (TAR)", written.len());
|
|
@@ -7,14 +7,25 @@ const MARKER_BYTES: usize = 12;
|
|
|
7
7
|
|
|
8
8
|
type Aes256Ctr = ctr::Ctr64BE<aes::Aes256>;
|
|
9
9
|
|
|
10
|
+
pub type DecodeProgressCallback = Box<dyn Fn(u64, u64, &str) + Send>;
|
|
11
|
+
|
|
10
12
|
pub fn streaming_decode_to_dir(png_path: &Path, out_dir: &Path) -> Result<Vec<String>, String> {
|
|
11
|
-
|
|
13
|
+
streaming_decode_to_dir_encrypted_with_progress(png_path, out_dir, None, None)
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
pub fn streaming_decode_to_dir_encrypted(
|
|
15
17
|
png_path: &Path,
|
|
16
18
|
out_dir: &Path,
|
|
17
19
|
passphrase: Option<&str>,
|
|
20
|
+
) -> Result<Vec<String>, String> {
|
|
21
|
+
streaming_decode_to_dir_encrypted_with_progress(png_path, out_dir, passphrase, None)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub fn streaming_decode_to_dir_encrypted_with_progress(
|
|
25
|
+
png_path: &Path,
|
|
26
|
+
out_dir: &Path,
|
|
27
|
+
passphrase: Option<&str>,
|
|
28
|
+
progress: Option<DecodeProgressCallback>,
|
|
18
29
|
) -> Result<Vec<String>, String> {
|
|
19
30
|
let file = std::fs::File::open(png_path).map_err(|e| format!("open: {}", e))?;
|
|
20
31
|
let mmap = unsafe { memmap2::Mmap::map(&file).map_err(|e| format!("mmap: {}", e))? };
|
|
@@ -24,7 +35,16 @@ pub fn streaming_decode_to_dir_encrypted(
|
|
|
24
35
|
return Err("Not a PNG file".into());
|
|
25
36
|
}
|
|
26
37
|
|
|
38
|
+
if let Some(ref cb) = progress {
|
|
39
|
+
cb(2, 100, "parsing_png");
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
let (width, height, idat_data_start, idat_data_end) = parse_png_header(data)?;
|
|
43
|
+
let total_expected = parse_rxfl_total_bytes(data).unwrap_or(0);
|
|
44
|
+
|
|
45
|
+
if let Some(ref cb) = progress {
|
|
46
|
+
cb(5, 100, "reading_header");
|
|
47
|
+
}
|
|
28
48
|
|
|
29
49
|
let mut reader = DeflatePixelReader::new(data, width, height, idat_data_start, idat_data_end);
|
|
30
50
|
|
|
@@ -51,6 +71,10 @@ pub fn streaming_decode_to_dir_encrypted(
|
|
|
51
71
|
reader.read_exact(&mut plen_buf).map_err(|e| format!("read payload_len: {}", e))?;
|
|
52
72
|
let payload_len = u32::from_be_bytes(plen_buf) as u64;
|
|
53
73
|
|
|
74
|
+
if let Some(ref cb) = progress {
|
|
75
|
+
cb(8, 100, "decrypting");
|
|
76
|
+
}
|
|
77
|
+
|
|
54
78
|
let payload_reader = reader.take(payload_len);
|
|
55
79
|
|
|
56
80
|
let first_byte_reader = FirstByteReader::new(payload_reader);
|
|
@@ -58,10 +82,13 @@ pub fn streaming_decode_to_dir_encrypted(
|
|
|
58
82
|
|
|
59
83
|
match enc_byte {
|
|
60
84
|
0x00 => {
|
|
85
|
+
if let Some(ref cb) = progress {
|
|
86
|
+
cb(10, 100, "decompressing");
|
|
87
|
+
}
|
|
61
88
|
let mut decoder = zstd::stream::Decoder::new(remaining_reader)
|
|
62
89
|
.map_err(|e| format!("zstd decoder: {}", e))?;
|
|
63
90
|
decoder.window_log_max(31).map_err(|e| format!("zstd window_log_max: {}", e))?;
|
|
64
|
-
|
|
91
|
+
read_rox1_and_untar_with_progress(decoder, out_dir, progress, total_expected)
|
|
65
92
|
}
|
|
66
93
|
0x03 => {
|
|
67
94
|
let pass = passphrase.ok_or("Passphrase required for AES-CTR decryption")?;
|
|
@@ -79,23 +106,31 @@ pub fn streaming_decode_to_dir_encrypted(
|
|
|
79
106
|
let encrypted_data_len = payload_len - 1 - 16 - 16 - hmac_size;
|
|
80
107
|
let ctr_reader = CtrDecryptReader::new(r.take(encrypted_data_len), cipher);
|
|
81
108
|
|
|
109
|
+
if let Some(ref cb) = progress {
|
|
110
|
+
cb(10, 100, "decompressing");
|
|
111
|
+
}
|
|
82
112
|
let mut decoder = zstd::stream::Decoder::new(ctr_reader)
|
|
83
113
|
.map_err(|e| format!("zstd decoder: {}", e))?;
|
|
84
114
|
decoder.window_log_max(31).map_err(|e| format!("zstd window_log_max: {}", e))?;
|
|
85
|
-
|
|
115
|
+
read_rox1_and_untar_with_progress(decoder, out_dir, progress, total_expected)
|
|
86
116
|
}
|
|
87
117
|
_ => Err(format!("Unsupported encryption (enc=0x{:02x}) in streaming decode", enc_byte)),
|
|
88
118
|
}
|
|
89
119
|
}
|
|
90
120
|
|
|
91
|
-
fn
|
|
121
|
+
fn read_rox1_and_untar_with_progress<R: Read>(
|
|
122
|
+
mut decoder: R,
|
|
123
|
+
out_dir: &Path,
|
|
124
|
+
progress: Option<DecodeProgressCallback>,
|
|
125
|
+
total_expected: u64,
|
|
126
|
+
) -> Result<Vec<String>, String> {
|
|
92
127
|
let mut magic = [0u8; 4];
|
|
93
128
|
decoder.read_exact(&mut magic).map_err(|e| format!("read ROX1: {}", e))?;
|
|
94
129
|
if &magic != b"ROX1" {
|
|
95
130
|
return Err(format!("Expected ROX1, got {:?}", magic));
|
|
96
131
|
}
|
|
97
132
|
std::fs::create_dir_all(out_dir).map_err(|e| format!("mkdir: {}", e))?;
|
|
98
|
-
|
|
133
|
+
tar_unpack_from_reader_with_progress(decoder, out_dir, progress, total_expected)
|
|
99
134
|
}
|
|
100
135
|
|
|
101
136
|
fn parse_png_header(data: &[u8]) -> Result<(usize, usize, usize, usize), String> {
|
|
@@ -147,6 +182,30 @@ fn parse_png_header(data: &[u8]) -> Result<(usize, usize, usize, usize), String>
|
|
|
147
182
|
Ok((width, height, idat_start, idat_end))
|
|
148
183
|
}
|
|
149
184
|
|
|
185
|
+
fn parse_rxfl_total_bytes(data: &[u8]) -> Option<u64> {
|
|
186
|
+
let mut pos = 8;
|
|
187
|
+
while pos + 12 <= data.len() {
|
|
188
|
+
let chunk_len = u32::from_be_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize;
|
|
189
|
+
let chunk_type = &data[pos + 4..pos + 8];
|
|
190
|
+
let chunk_data_start = pos + 8;
|
|
191
|
+
|
|
192
|
+
if chunk_type == b"rXFL" && chunk_data_start + chunk_len <= data.len() {
|
|
193
|
+
let json_bytes = &data[chunk_data_start..chunk_data_start + chunk_len];
|
|
194
|
+
if let Ok(entries) = serde_json::from_slice::<Vec<serde_json::Value>>(json_bytes) {
|
|
195
|
+
let total: u64 = entries.iter()
|
|
196
|
+
.filter_map(|e| e.get("size").and_then(|s| s.as_u64()))
|
|
197
|
+
.sum();
|
|
198
|
+
return Some(total);
|
|
199
|
+
}
|
|
200
|
+
} else if chunk_type == b"IEND" {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
pos = chunk_data_start + chunk_len + 4;
|
|
205
|
+
}
|
|
206
|
+
None
|
|
207
|
+
}
|
|
208
|
+
|
|
150
209
|
struct DeflatePixelReader<'a> {
|
|
151
210
|
data: &'a [u8],
|
|
152
211
|
height: usize,
|
|
@@ -298,15 +357,23 @@ impl<R: Read> Read for CtrDecryptReader<R> {
|
|
|
298
357
|
}
|
|
299
358
|
}
|
|
300
359
|
|
|
301
|
-
fn
|
|
360
|
+
fn tar_unpack_from_reader_with_progress<R: Read>(
|
|
361
|
+
reader: R,
|
|
362
|
+
output_dir: &Path,
|
|
363
|
+
progress: Option<DecodeProgressCallback>,
|
|
364
|
+
total_expected: u64,
|
|
365
|
+
) -> Result<Vec<String>, String> {
|
|
302
366
|
let buf_reader = std::io::BufReader::with_capacity(8 * 1024 * 1024, reader);
|
|
303
367
|
let mut archive = tar::Archive::new(buf_reader);
|
|
304
368
|
let mut written = Vec::new();
|
|
305
369
|
let mut created_dirs = std::collections::HashSet::new();
|
|
370
|
+
let mut bytes_extracted: u64 = 0;
|
|
371
|
+
let mut last_pct: u64 = 10;
|
|
306
372
|
|
|
307
373
|
let entries = archive.entries().map_err(|e| format!("tar entries: {}", e))?;
|
|
308
374
|
for entry in entries {
|
|
309
375
|
let mut entry = entry.map_err(|e| format!("tar entry: {}", e))?;
|
|
376
|
+
let entry_size = entry.size();
|
|
310
377
|
let path = entry.path().map_err(|e| format!("tar path: {}", e))?.to_path_buf();
|
|
311
378
|
|
|
312
379
|
let mut safe = std::path::PathBuf::new();
|
|
@@ -327,11 +394,28 @@ fn tar_unpack_from_reader<R: Read>(reader: R, output_dir: &Path) -> Result<Vec<S
|
|
|
327
394
|
}
|
|
328
395
|
|
|
329
396
|
let mut f = std::io::BufWriter::with_capacity(
|
|
330
|
-
(
|
|
397
|
+
(entry_size as usize).min(4 * 1024 * 1024).max(8192),
|
|
331
398
|
std::fs::File::create(&dest).map_err(|e| format!("create {:?}: {}", dest, e))?,
|
|
332
399
|
);
|
|
333
400
|
std::io::copy(&mut entry, &mut f).map_err(|e| format!("write {:?}: {}", dest, e))?;
|
|
334
401
|
written.push(safe.to_string_lossy().to_string());
|
|
402
|
+
|
|
403
|
+
bytes_extracted += entry_size;
|
|
404
|
+
if let Some(ref cb) = progress {
|
|
405
|
+
let pct = if total_expected > 0 {
|
|
406
|
+
10 + (bytes_extracted * 89 / total_expected).min(89)
|
|
407
|
+
} else {
|
|
408
|
+
(10 + (bytes_extracted / (1024 * 1024))).min(99)
|
|
409
|
+
};
|
|
410
|
+
if pct > last_pct {
|
|
411
|
+
last_pct = pct;
|
|
412
|
+
cb(pct, 100, "extracting");
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if let Some(ref cb) = progress {
|
|
418
|
+
cb(99, 100, "finishing");
|
|
335
419
|
}
|
|
336
420
|
|
|
337
421
|
Ok(written)
|