roxify 1.13.7 → 1.13.9
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/dist/cli.js +1 -8
- package/dist/stub-progress.d.ts +4 -4
- package/dist/stub-progress.js +4 -4
- package/dist/utils/decoder.d.ts +46 -2
- package/dist/utils/decoder.js +248 -38
- package/dist/utils/ecc.js +0 -1
- package/dist/utils/encoder.d.ts +30 -1
- package/dist/utils/encoder.js +34 -18
- package/dist/utils/inspection.d.ts +1 -1
- package/dist/utils/inspection.js +2 -2
- package/dist/utils/robust-audio.js +0 -13
- package/dist/utils/robust-image.js +0 -26
- package/package.json +12 -29
- 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/{dist/rox-macos-universal → roxify_native-universal-apple-darwin.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/scripts/postinstall.cjs +23 -2
- package/Cargo.toml +0 -91
- 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/native/archive.rs +0 -220
- package/native/audio.rs +0 -151
- package/native/bench_hybrid.rs +0 -145
- package/native/bwt.rs +0 -56
- package/native/context_mixing.rs +0 -117
- package/native/core.rs +0 -378
- package/native/crypto.rs +0 -209
- package/native/encoder.rs +0 -405
- package/native/hybrid.rs +0 -297
- package/native/image_utils.rs +0 -82
- package/native/io_advice.rs +0 -43
- package/native/io_ntfs_optimized.rs +0 -99
- package/native/lib.rs +0 -480
- package/native/main.rs +0 -842
- package/native/mtf.rs +0 -106
- package/native/packer.rs +0 -604
- package/native/png_chunk_writer.rs +0 -146
- package/native/png_utils.rs +0 -554
- package/native/pool.rs +0 -101
- package/native/progress.rs +0 -142
- package/native/rans.rs +0 -149
- package/native/rans_byte.rs +0 -286
- package/native/reconstitution.rs +0 -623
- package/native/streaming.rs +0 -189
- package/native/streaming_decode.rs +0 -625
- package/native/streaming_encode.rs +0 -684
- package/native/test_small_bwt.rs +0 -31
- package/native/test_stages.rs +0 -70
|
@@ -1,625 +0,0 @@
|
|
|
1
|
-
use std::fs::File;
|
|
2
|
-
use std::io::{Read, Seek, SeekFrom};
|
|
3
|
-
use std::path::Path;
|
|
4
|
-
use cipher::{KeyIvInit, StreamCipher};
|
|
5
|
-
|
|
6
|
-
const PIXEL_MAGIC: &[u8] = b"PXL1";
|
|
7
|
-
const MARKER_BYTES: usize = 12;
|
|
8
|
-
const PACK_MAGIC: [u8; 4] = 0x524f5850u32.to_be_bytes();
|
|
9
|
-
const HEADER_VERSION_V1: u8 = 1;
|
|
10
|
-
const HEADER_VERSION_V2: u8 = 2;
|
|
11
|
-
|
|
12
|
-
type Aes256Ctr = ctr::Ctr64BE<aes::Aes256>;
|
|
13
|
-
|
|
14
|
-
pub type DecodeProgressCallback = Box<dyn Fn(u64, u64, &str) + Send>;
|
|
15
|
-
|
|
16
|
-
pub fn streaming_decode_to_dir(png_path: &Path, out_dir: &Path) -> Result<Vec<String>, String> {
|
|
17
|
-
streaming_decode_selected_to_dir_encrypted_with_progress(png_path, out_dir, None, None, None)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
pub fn streaming_decode_to_dir_encrypted(
|
|
21
|
-
png_path: &Path,
|
|
22
|
-
out_dir: &Path,
|
|
23
|
-
passphrase: Option<&str>,
|
|
24
|
-
) -> Result<Vec<String>, String> {
|
|
25
|
-
streaming_decode_selected_to_dir_encrypted_with_progress(png_path, out_dir, None, passphrase, None)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
pub fn streaming_decode_to_dir_encrypted_with_progress(
|
|
29
|
-
png_path: &Path,
|
|
30
|
-
out_dir: &Path,
|
|
31
|
-
passphrase: Option<&str>,
|
|
32
|
-
progress: Option<DecodeProgressCallback>,
|
|
33
|
-
) -> Result<Vec<String>, String> {
|
|
34
|
-
streaming_decode_selected_to_dir_encrypted_with_progress(png_path, out_dir, None, passphrase, progress)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
pub fn streaming_decode_selected_to_dir_encrypted_with_progress(
|
|
38
|
-
png_path: &Path,
|
|
39
|
-
out_dir: &Path,
|
|
40
|
-
files_opt: Option<&[String]>,
|
|
41
|
-
passphrase: Option<&str>,
|
|
42
|
-
progress: Option<DecodeProgressCallback>,
|
|
43
|
-
) -> Result<Vec<String>, String> {
|
|
44
|
-
let mut meta_file = File::open(png_path).map_err(|e| format!("open: {}", e))?;
|
|
45
|
-
let (width, height, idat_ranges, total_expected) = parse_png_metadata(&mut meta_file)?;
|
|
46
|
-
|
|
47
|
-
if let Some(ref cb) = progress {
|
|
48
|
-
cb(2, 100, "parsing_png");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if let Some(ref cb) = progress {
|
|
52
|
-
cb(5, 100, "reading_header");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
let data_file = File::open(png_path).map_err(|e| format!("open data: {}", e))?;
|
|
56
|
-
let mut reader = DeflatePixelReader::new(data_file, width, height, idat_ranges)
|
|
57
|
-
.map_err(|e| format!("init deflate reader: {}", e))?;
|
|
58
|
-
|
|
59
|
-
let mut marker_buf = [0u8; MARKER_BYTES];
|
|
60
|
-
reader.read_exact(&mut marker_buf).map_err(|e| format!("read markers: {}", e))?;
|
|
61
|
-
|
|
62
|
-
let mut pxl1 = [0u8; 4];
|
|
63
|
-
reader.read_exact(&mut pxl1).map_err(|e| format!("read PXL1: {}", e))?;
|
|
64
|
-
if &pxl1 != PIXEL_MAGIC {
|
|
65
|
-
return Err(format!("Expected PXL1, got {:?}", pxl1));
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
let mut hdr = [0u8; 2];
|
|
69
|
-
reader.read_exact(&mut hdr).map_err(|e| format!("read hdr: {}", e))?;
|
|
70
|
-
let _version = hdr[0];
|
|
71
|
-
let name_len = hdr[1] as usize;
|
|
72
|
-
|
|
73
|
-
if name_len > 0 {
|
|
74
|
-
let mut name_buf = vec![0u8; name_len];
|
|
75
|
-
reader.read_exact(&mut name_buf).map_err(|e| format!("read name: {}", e))?;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
let payload_len = read_payload_len(&mut reader, hdr[0])?;
|
|
79
|
-
|
|
80
|
-
if let Some(ref cb) = progress {
|
|
81
|
-
cb(8, 100, "decrypting");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
let mut enc_byte = [0u8; 1];
|
|
85
|
-
reader.read_exact(&mut enc_byte).map_err(|e| format!("read first byte: {}", e))?;
|
|
86
|
-
let remaining_payload_len = payload_len.saturating_sub(1);
|
|
87
|
-
|
|
88
|
-
match enc_byte[0] {
|
|
89
|
-
0x00 => {
|
|
90
|
-
if let Some(ref cb) = progress {
|
|
91
|
-
cb(10, 100, "decompressing");
|
|
92
|
-
}
|
|
93
|
-
let mut decoder = zstd::stream::Decoder::new(reader)
|
|
94
|
-
.map_err(|e| format!("zstd decoder: {}", e))?;
|
|
95
|
-
decoder.window_log_max(31).map_err(|e| format!("zstd window_log_max: {}", e))?;
|
|
96
|
-
read_rox1_and_unpack_with_progress(decoder, out_dir, files_opt, progress, total_expected)
|
|
97
|
-
}
|
|
98
|
-
0x03 => {
|
|
99
|
-
let pass = passphrase.ok_or("Passphrase required for AES-CTR decryption")?;
|
|
100
|
-
let mut salt = [0u8; 16];
|
|
101
|
-
let mut iv = [0u8; 16];
|
|
102
|
-
let mut r = reader.take(remaining_payload_len);
|
|
103
|
-
r.read_exact(&mut salt).map_err(|e| format!("read salt: {}", e))?;
|
|
104
|
-
r.read_exact(&mut iv).map_err(|e| format!("read iv: {}", e))?;
|
|
105
|
-
|
|
106
|
-
let key = crate::crypto::derive_aes_ctr_key(pass, &salt);
|
|
107
|
-
let cipher = Aes256Ctr::new_from_slices(&key, &iv)
|
|
108
|
-
.map_err(|e| format!("AES-CTR init: {}", e))?;
|
|
109
|
-
|
|
110
|
-
let hmac_size = 32u64;
|
|
111
|
-
let encrypted_data_len = remaining_payload_len - 16 - 16 - hmac_size;
|
|
112
|
-
let ctr_reader = CtrDecryptReader::new(r.take(encrypted_data_len), cipher);
|
|
113
|
-
|
|
114
|
-
if let Some(ref cb) = progress {
|
|
115
|
-
cb(10, 100, "decompressing");
|
|
116
|
-
}
|
|
117
|
-
let mut decoder = zstd::stream::Decoder::new(ctr_reader)
|
|
118
|
-
.map_err(|e| format!("zstd decoder: {}", e))?;
|
|
119
|
-
decoder.window_log_max(31).map_err(|e| format!("zstd window_log_max: {}", e))?;
|
|
120
|
-
read_rox1_and_unpack_with_progress(decoder, out_dir, files_opt, progress, total_expected)
|
|
121
|
-
}
|
|
122
|
-
_ => Err(format!("Unsupported encryption (enc=0x{:02x}) in streaming decode", enc_byte[0])),
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
fn read_payload_len<R: Read>(reader: &mut R, version: u8) -> Result<u64, String> {
|
|
127
|
-
match version {
|
|
128
|
-
HEADER_VERSION_V1 => {
|
|
129
|
-
let mut plen_buf = [0u8; 4];
|
|
130
|
-
reader.read_exact(&mut plen_buf).map_err(|e| format!("read payload_len: {}", e))?;
|
|
131
|
-
Ok(u32::from_be_bytes(plen_buf) as u64)
|
|
132
|
-
}
|
|
133
|
-
HEADER_VERSION_V2 => {
|
|
134
|
-
let mut plen_buf = [0u8; 8];
|
|
135
|
-
reader.read_exact(&mut plen_buf).map_err(|e| format!("read payload_len64: {}", e))?;
|
|
136
|
-
Ok(u64::from_be_bytes(plen_buf))
|
|
137
|
-
}
|
|
138
|
-
other => Err(format!("Unsupported header version {}", other)),
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
fn read_rox1_and_unpack_with_progress<R: Read>(
|
|
143
|
-
mut decoder: R,
|
|
144
|
-
out_dir: &Path,
|
|
145
|
-
files_opt: Option<&[String]>,
|
|
146
|
-
progress: Option<DecodeProgressCallback>,
|
|
147
|
-
total_expected: u64,
|
|
148
|
-
) -> Result<Vec<String>, String> {
|
|
149
|
-
let mut magic = [0u8; 4];
|
|
150
|
-
decoder.read_exact(&mut magic).map_err(|e| format!("read ROX1: {}", e))?;
|
|
151
|
-
if &magic != b"ROX1" {
|
|
152
|
-
return Err(format!("Expected ROX1, got {:?}", magic));
|
|
153
|
-
}
|
|
154
|
-
std::fs::create_dir_all(out_dir).map_err(|e| format!("mkdir: {}", e))?;
|
|
155
|
-
|
|
156
|
-
let mut prefix = [0u8; 4];
|
|
157
|
-
decoder.read_exact(&mut prefix).map_err(|e| format!("read payload magic: {}", e))?;
|
|
158
|
-
let mut chained = std::io::Cursor::new(prefix).chain(decoder);
|
|
159
|
-
|
|
160
|
-
if prefix == PACK_MAGIC {
|
|
161
|
-
crate::packer::unpack_stream_to_dir(&mut chained, out_dir, files_opt, progress.as_deref(), total_expected)
|
|
162
|
-
.map_err(|e| format!("pack unpack: {}", e))
|
|
163
|
-
} else {
|
|
164
|
-
tar_unpack_from_reader_with_progress(chained, out_dir, files_opt, progress, total_expected)
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
fn parse_png_metadata(file: &mut File) -> Result<(usize, usize, Vec<(u64, u64)>, u64), String> {
|
|
169
|
-
let mut sig = [0u8; 8];
|
|
170
|
-
file.read_exact(&mut sig).map_err(|e| format!("read sig: {}", e))?;
|
|
171
|
-
if sig != [137, 80, 78, 71, 13, 10, 26, 10] {
|
|
172
|
-
return Err("Not a PNG file".into());
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
let mut width = 0usize;
|
|
176
|
-
let mut height = 0usize;
|
|
177
|
-
let mut idat_ranges = Vec::new();
|
|
178
|
-
let mut total_expected = 0u64;
|
|
179
|
-
|
|
180
|
-
loop {
|
|
181
|
-
let mut header = [0u8; 8];
|
|
182
|
-
match file.read_exact(&mut header) {
|
|
183
|
-
Ok(()) => {}
|
|
184
|
-
Err(err) if err.kind() == std::io::ErrorKind::UnexpectedEof => break,
|
|
185
|
-
Err(err) => return Err(format!("read chunk header: {}", err)),
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
let chunk_len = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as u64;
|
|
189
|
-
let chunk_type = &header[4..8];
|
|
190
|
-
let chunk_data_start = file.stream_position().map_err(|e| format!("stream position: {}", e))?;
|
|
191
|
-
let chunk_data_end = chunk_data_start
|
|
192
|
-
.checked_add(chunk_len)
|
|
193
|
-
.ok_or_else(|| "PNG chunk length overflow".to_string())?;
|
|
194
|
-
|
|
195
|
-
if chunk_type == b"IHDR" {
|
|
196
|
-
if chunk_len < 13 {
|
|
197
|
-
return Err("Invalid IHDR".into());
|
|
198
|
-
}
|
|
199
|
-
let mut ihdr = [0u8; 13];
|
|
200
|
-
file.read_exact(&mut ihdr).map_err(|e| format!("read IHDR: {}", e))?;
|
|
201
|
-
width = u32::from_be_bytes([ihdr[0], ihdr[1], ihdr[2], ihdr[3]]) as usize;
|
|
202
|
-
height = u32::from_be_bytes([ihdr[4], ihdr[5], ihdr[6], ihdr[7]]) as usize;
|
|
203
|
-
if chunk_len > 13 {
|
|
204
|
-
file.seek(SeekFrom::Current((chunk_len - 13) as i64)).map_err(|e| format!("seek IHDR: {}", e))?;
|
|
205
|
-
}
|
|
206
|
-
} else if chunk_type == b"IDAT" {
|
|
207
|
-
idat_ranges.push((chunk_data_start, chunk_data_end));
|
|
208
|
-
file.seek(SeekFrom::Current(chunk_len as i64)).map_err(|e| format!("seek IDAT: {}", e))?;
|
|
209
|
-
} else if chunk_type == b"rXFL" {
|
|
210
|
-
let json_len = usize::try_from(chunk_len).map_err(|_| "rXFL too large".to_string())?;
|
|
211
|
-
let mut json = vec![0u8; json_len];
|
|
212
|
-
file.read_exact(&mut json).map_err(|e| format!("read rXFL: {}", e))?;
|
|
213
|
-
total_expected = parse_rxfl_total_bytes(&json).unwrap_or(total_expected);
|
|
214
|
-
} else if chunk_type == b"IEND" {
|
|
215
|
-
break;
|
|
216
|
-
} else {
|
|
217
|
-
file.seek(SeekFrom::Current(chunk_len as i64)).map_err(|e| format!("seek chunk: {}", e))?;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
file.seek(SeekFrom::Current(4)).map_err(|e| format!("seek crc: {}", e))?;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if width == 0 || height == 0 {
|
|
224
|
-
return Err("IHDR not found".into());
|
|
225
|
-
}
|
|
226
|
-
if idat_ranges.is_empty() {
|
|
227
|
-
return Err("IDAT not found".into());
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
Ok((width, height, idat_ranges, total_expected))
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
fn parse_rxfl_total_bytes(json_bytes: &[u8]) -> Option<u64> {
|
|
234
|
-
if let Ok(entries) = serde_json::from_slice::<Vec<serde_json::Value>>(json_bytes) {
|
|
235
|
-
return Some(entries.iter()
|
|
236
|
-
.filter_map(|e| e.get("size").and_then(|s| s.as_u64()))
|
|
237
|
-
.sum());
|
|
238
|
-
}
|
|
239
|
-
None
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
struct DeflatePixelReader {
|
|
243
|
-
file: File,
|
|
244
|
-
height: usize,
|
|
245
|
-
idat_ranges: Vec<(u64, u64)>,
|
|
246
|
-
range_index: usize,
|
|
247
|
-
offset: u64,
|
|
248
|
-
range_end: u64,
|
|
249
|
-
dropped_until: u64,
|
|
250
|
-
block_remaining: usize,
|
|
251
|
-
current_row: usize,
|
|
252
|
-
col_in_row: usize,
|
|
253
|
-
scanline_filter_pending: bool,
|
|
254
|
-
row_bytes: usize,
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
impl DeflatePixelReader {
|
|
258
|
-
fn new(mut file: File, width: usize, height: usize, idat_ranges: Vec<(u64, u64)>) -> Result<Self, String> {
|
|
259
|
-
let Some(&(offset, range_end)) = idat_ranges.first() else {
|
|
260
|
-
return Err("IDAT not found".to_string());
|
|
261
|
-
};
|
|
262
|
-
crate::io_advice::advise_file_sequential(&file);
|
|
263
|
-
file.seek(SeekFrom::Start(offset)).map_err(|e| format!("seek first IDAT: {}", e))?;
|
|
264
|
-
let row_bytes = width * 3;
|
|
265
|
-
let mut reader = Self {
|
|
266
|
-
file,
|
|
267
|
-
height,
|
|
268
|
-
idat_ranges,
|
|
269
|
-
range_index: 0,
|
|
270
|
-
offset,
|
|
271
|
-
range_end,
|
|
272
|
-
dropped_until: offset,
|
|
273
|
-
block_remaining: 0,
|
|
274
|
-
current_row: 0,
|
|
275
|
-
col_in_row: 0,
|
|
276
|
-
scanline_filter_pending: true,
|
|
277
|
-
row_bytes,
|
|
278
|
-
};
|
|
279
|
-
reader.skip_stream_bytes(2)
|
|
280
|
-
.map_err(|e| format!("read zlib header: {}", e))?;
|
|
281
|
-
Ok(reader)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
fn advance_range(&mut self) -> Result<bool, std::io::Error> {
|
|
285
|
-
while self.offset >= self.range_end {
|
|
286
|
-
crate::io_advice::advise_drop(&self.file, self.dropped_until, self.range_end.saturating_sub(self.dropped_until));
|
|
287
|
-
self.dropped_until = self.range_end;
|
|
288
|
-
self.range_index += 1;
|
|
289
|
-
let Some(&(offset, end)) = self.idat_ranges.get(self.range_index) else {
|
|
290
|
-
return Ok(false);
|
|
291
|
-
};
|
|
292
|
-
self.file.seek(SeekFrom::Start(offset))?;
|
|
293
|
-
self.offset = offset;
|
|
294
|
-
self.range_end = end;
|
|
295
|
-
self.dropped_until = offset;
|
|
296
|
-
}
|
|
297
|
-
Ok(true)
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
fn maybe_drop_consumed(&mut self) {
|
|
301
|
-
let consumed = self.offset.saturating_sub(self.dropped_until);
|
|
302
|
-
if consumed >= crate::io_advice::INPUT_DROP_GRANULARITY {
|
|
303
|
-
crate::io_advice::advise_drop(&self.file, self.dropped_until, consumed);
|
|
304
|
-
self.dropped_until = self.offset;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
fn read_stream_bytes(&mut self, buf: &mut [u8]) -> Result<usize, std::io::Error> {
|
|
309
|
-
let mut written = 0;
|
|
310
|
-
while written < buf.len() {
|
|
311
|
-
if !self.advance_range()? {
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
let available = usize::try_from(self.range_end - self.offset).unwrap_or(buf.len() - written);
|
|
315
|
-
if available == 0 {
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
let take = available.min(buf.len() - written);
|
|
319
|
-
let read = self.file.read(&mut buf[written..written + take])?;
|
|
320
|
-
if read == 0 {
|
|
321
|
-
return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "failed to fill whole buffer"));
|
|
322
|
-
}
|
|
323
|
-
self.offset += read as u64;
|
|
324
|
-
written += read;
|
|
325
|
-
self.maybe_drop_consumed();
|
|
326
|
-
}
|
|
327
|
-
Ok(written)
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
fn read_stream_exact(&mut self, buf: &mut [u8]) -> Result<(), std::io::Error> {
|
|
331
|
-
let got = self.read_stream_bytes(buf)?;
|
|
332
|
-
if got == buf.len() {
|
|
333
|
-
return Ok(());
|
|
334
|
-
}
|
|
335
|
-
Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "failed to fill whole buffer"))
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
fn skip_stream_bytes(&mut self, count: usize) -> Result<(), std::io::Error> {
|
|
339
|
-
let mut remaining = count;
|
|
340
|
-
while remaining > 0 {
|
|
341
|
-
if !self.advance_range()? {
|
|
342
|
-
return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "failed to fill whole buffer"));
|
|
343
|
-
}
|
|
344
|
-
let available = usize::try_from(self.range_end - self.offset).unwrap_or(remaining);
|
|
345
|
-
if available == 0 {
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
let take = available.min(remaining);
|
|
349
|
-
self.file.seek(SeekFrom::Current(take as i64))?;
|
|
350
|
-
self.offset += take as u64;
|
|
351
|
-
remaining -= take;
|
|
352
|
-
self.maybe_drop_consumed();
|
|
353
|
-
}
|
|
354
|
-
Ok(())
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
fn ensure_block(&mut self) -> Result<(), std::io::Error> {
|
|
358
|
-
if self.block_remaining > 0 {
|
|
359
|
-
return Ok(());
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
let mut header = [0u8; 5];
|
|
363
|
-
self.read_stream_exact(&mut header)?;
|
|
364
|
-
|
|
365
|
-
let len_lo = header[1] as usize;
|
|
366
|
-
let len_hi = header[2] as usize;
|
|
367
|
-
|
|
368
|
-
self.block_remaining = len_lo | (len_hi << 8);
|
|
369
|
-
Ok(())
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
fn copy_raw_bytes(&mut self, buf: &mut [u8], count: usize) -> Result<usize, std::io::Error> {
|
|
373
|
-
let mut written = 0;
|
|
374
|
-
while written < count {
|
|
375
|
-
self.ensure_block()?;
|
|
376
|
-
let avail = self.block_remaining.min(count - written);
|
|
377
|
-
if avail == 0 {
|
|
378
|
-
break;
|
|
379
|
-
}
|
|
380
|
-
let got = self.read_stream_bytes(&mut buf[written..written + avail])?;
|
|
381
|
-
if got == 0 {
|
|
382
|
-
break;
|
|
383
|
-
}
|
|
384
|
-
self.block_remaining -= got;
|
|
385
|
-
written += got;
|
|
386
|
-
}
|
|
387
|
-
Ok(written)
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
fn skip_raw_bytes(&mut self, count: usize) -> Result<(), std::io::Error> {
|
|
391
|
-
let mut remaining = count;
|
|
392
|
-
while remaining > 0 {
|
|
393
|
-
self.ensure_block()?;
|
|
394
|
-
let skip = self.block_remaining.min(remaining);
|
|
395
|
-
if skip == 0 {
|
|
396
|
-
break;
|
|
397
|
-
}
|
|
398
|
-
self.skip_stream_bytes(skip)?;
|
|
399
|
-
self.block_remaining -= skip;
|
|
400
|
-
remaining -= skip;
|
|
401
|
-
}
|
|
402
|
-
Ok(())
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
impl Read for DeflatePixelReader {
|
|
407
|
-
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
408
|
-
let mut filled = 0;
|
|
409
|
-
|
|
410
|
-
while filled < buf.len() {
|
|
411
|
-
if self.current_row >= self.height {
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if self.scanline_filter_pending {
|
|
416
|
-
self.skip_raw_bytes(1)?;
|
|
417
|
-
self.scanline_filter_pending = false;
|
|
418
|
-
self.col_in_row = 0;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
if self.col_in_row >= self.row_bytes {
|
|
422
|
-
self.current_row += 1;
|
|
423
|
-
self.scanline_filter_pending = true;
|
|
424
|
-
continue;
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
let remaining_in_row = self.row_bytes - self.col_in_row;
|
|
428
|
-
let remaining_in_buf = buf.len() - filled;
|
|
429
|
-
let to_read = remaining_in_row.min(remaining_in_buf);
|
|
430
|
-
|
|
431
|
-
let got = self.copy_raw_bytes(&mut buf[filled..filled + to_read], to_read)?;
|
|
432
|
-
filled += got;
|
|
433
|
-
self.col_in_row += got;
|
|
434
|
-
if got == 0 {
|
|
435
|
-
break;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
Ok(filled)
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
struct CtrDecryptReader<R: Read> {
|
|
444
|
-
inner: R,
|
|
445
|
-
cipher: Aes256Ctr,
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
impl<R: Read> CtrDecryptReader<R> {
|
|
449
|
-
fn new(inner: R, cipher: Aes256Ctr) -> Self {
|
|
450
|
-
Self { inner, cipher }
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
impl<R: Read> Read for CtrDecryptReader<R> {
|
|
455
|
-
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
|
456
|
-
let n = self.inner.read(buf)?;
|
|
457
|
-
if n > 0 {
|
|
458
|
-
self.cipher.apply_keystream(&mut buf[..n]);
|
|
459
|
-
}
|
|
460
|
-
Ok(n)
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
fn tar_unpack_from_reader_with_progress<R: Read>(
|
|
465
|
-
reader: R,
|
|
466
|
-
output_dir: &Path,
|
|
467
|
-
files_opt: Option<&[String]>,
|
|
468
|
-
progress: Option<DecodeProgressCallback>,
|
|
469
|
-
total_expected: u64,
|
|
470
|
-
) -> Result<Vec<String>, String> {
|
|
471
|
-
let buf_reader = std::io::BufReader::with_capacity(8 * 1024 * 1024, reader);
|
|
472
|
-
let mut archive = tar::Archive::new(buf_reader);
|
|
473
|
-
let mut written = Vec::new();
|
|
474
|
-
let mut created_dirs = std::collections::HashSet::new();
|
|
475
|
-
let mut bytes_extracted: u64 = 0;
|
|
476
|
-
let mut last_pct: u64 = 10;
|
|
477
|
-
let files_filter: Option<std::collections::HashSet<&str>> = files_opt.map(|files| files.iter().map(|file| file.as_str()).collect());
|
|
478
|
-
let mut remaining = files_filter.as_ref().map(|files| files.len()).unwrap_or(usize::MAX);
|
|
479
|
-
|
|
480
|
-
let entries = archive.entries().map_err(|e| format!("tar entries: {}", e))?;
|
|
481
|
-
for entry in entries {
|
|
482
|
-
let mut entry = entry.map_err(|e| format!("tar entry: {}", e))?;
|
|
483
|
-
let entry_size = entry.size();
|
|
484
|
-
let path = entry.path().map_err(|e| format!("tar path: {}", e))?.to_path_buf();
|
|
485
|
-
let logical_path = path.to_string_lossy().replace('\\', "/");
|
|
486
|
-
let should_write = files_filter.as_ref().map(|files| files.contains(logical_path.as_str())).unwrap_or(true);
|
|
487
|
-
|
|
488
|
-
let mut safe = std::path::PathBuf::new();
|
|
489
|
-
for comp in path.components() {
|
|
490
|
-
if let std::path::Component::Normal(osstr) = comp {
|
|
491
|
-
safe.push(osstr);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
if safe.as_os_str().is_empty() {
|
|
495
|
-
continue;
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
if !should_write {
|
|
499
|
-
std::io::copy(&mut entry, &mut std::io::sink()).map_err(|e| format!("skip {:?}: {}", safe, e))?;
|
|
500
|
-
bytes_extracted += entry_size;
|
|
501
|
-
if let Some(ref cb) = progress {
|
|
502
|
-
let pct = if total_expected > 0 {
|
|
503
|
-
10 + (bytes_extracted * 89 / total_expected).min(89)
|
|
504
|
-
} else {
|
|
505
|
-
(10 + (bytes_extracted / (1024 * 1024))).min(99)
|
|
506
|
-
};
|
|
507
|
-
if pct > last_pct {
|
|
508
|
-
last_pct = pct;
|
|
509
|
-
cb(pct, 100, "extracting");
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
let dest = output_dir.join(&safe);
|
|
516
|
-
if let Some(parent) = dest.parent() {
|
|
517
|
-
if created_dirs.insert(parent.to_path_buf()) {
|
|
518
|
-
std::fs::create_dir_all(parent).map_err(|e| format!("mkdir {:?}: {}", parent, e))?;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// NTFS optimization: larger buffer reduces syscalls (16MB max, 256KB min)
|
|
523
|
-
let buffer_size = (entry_size as usize)
|
|
524
|
-
.min(16 * 1024 * 1024) // 16MB max buffer
|
|
525
|
-
.max(256 * 1024); // 256KB min buffer for NTFS
|
|
526
|
-
let mut f = std::io::BufWriter::with_capacity(
|
|
527
|
-
buffer_size,
|
|
528
|
-
std::fs::File::create(&dest).map_err(|e| format!("create {:?}: {}", dest, e))?,
|
|
529
|
-
);
|
|
530
|
-
std::io::copy(&mut entry, &mut f).map_err(|e| format!("write {:?}: {}", dest, e))?;
|
|
531
|
-
let file = f.into_inner().map_err(|e| format!("flush {:?}: {}", dest, e.error()))?;
|
|
532
|
-
crate::io_advice::sync_and_drop(&file, entry_size);
|
|
533
|
-
written.push(safe.to_string_lossy().to_string());
|
|
534
|
-
if files_filter.is_some() {
|
|
535
|
-
remaining = remaining.saturating_sub(1);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
bytes_extracted += entry_size;
|
|
539
|
-
if let Some(ref cb) = progress {
|
|
540
|
-
let pct = if total_expected > 0 {
|
|
541
|
-
10 + (bytes_extracted * 89 / total_expected).min(89)
|
|
542
|
-
} else {
|
|
543
|
-
(10 + (bytes_extracted / (1024 * 1024))).min(99)
|
|
544
|
-
};
|
|
545
|
-
if pct > last_pct {
|
|
546
|
-
last_pct = pct;
|
|
547
|
-
cb(pct, 100, "extracting");
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
if remaining == 0 {
|
|
551
|
-
break;
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if let Some(ref cb) = progress {
|
|
556
|
-
cb(99, 100, "finishing");
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
Ok(written)
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
#[cfg(test)]
|
|
563
|
-
mod tests {
|
|
564
|
-
use super::*;
|
|
565
|
-
use std::time::{SystemTime, UNIX_EPOCH};
|
|
566
|
-
|
|
567
|
-
#[test]
|
|
568
|
-
fn parse_png_header_collects_all_idat_ranges() {
|
|
569
|
-
let mut png = Vec::new();
|
|
570
|
-
png.extend_from_slice(&[137, 80, 78, 71, 13, 10, 26, 10]);
|
|
571
|
-
|
|
572
|
-
let mut ihdr = [0u8; 13];
|
|
573
|
-
ihdr[0..4].copy_from_slice(&1u32.to_be_bytes());
|
|
574
|
-
ihdr[4..8].copy_from_slice(&1u32.to_be_bytes());
|
|
575
|
-
ihdr[8] = 8;
|
|
576
|
-
ihdr[9] = 2;
|
|
577
|
-
|
|
578
|
-
crate::png_chunk_writer::write_png_chunk(&mut png, b"IHDR", &ihdr).unwrap();
|
|
579
|
-
crate::png_chunk_writer::write_png_chunk(&mut png, b"IDAT", &[1, 2, 3]).unwrap();
|
|
580
|
-
crate::png_chunk_writer::write_png_chunk(&mut png, b"IDAT", &[4, 5]).unwrap();
|
|
581
|
-
crate::png_chunk_writer::write_png_chunk(&mut png, b"IEND", &[]).unwrap();
|
|
582
|
-
|
|
583
|
-
let ms = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis();
|
|
584
|
-
let path = std::env::temp_dir().join(format!("rox_png_header_test_{}.png", ms));
|
|
585
|
-
std::fs::write(&path, &png).unwrap();
|
|
586
|
-
|
|
587
|
-
let mut file = File::open(&path).unwrap();
|
|
588
|
-
let (_, _, ranges, _) = parse_png_metadata(&mut file).unwrap();
|
|
589
|
-
|
|
590
|
-
assert_eq!(ranges.len(), 2);
|
|
591
|
-
assert_eq!(&png[ranges[0].0 as usize..ranges[0].1 as usize], &[1, 2, 3]);
|
|
592
|
-
assert_eq!(&png[ranges[1].0 as usize..ranges[1].1 as usize], &[4, 5]);
|
|
593
|
-
|
|
594
|
-
let _ = std::fs::remove_file(path);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
#[test]
|
|
598
|
-
fn deflate_reader_reads_across_idat_boundaries() {
|
|
599
|
-
let scanline = [0u8, 10, 11, 12, 13, 14, 15];
|
|
600
|
-
let mut deflate = vec![0x78, 0x01, 0x01, 0x07, 0x00, 0xF8, 0xFF];
|
|
601
|
-
deflate.extend_from_slice(&scanline);
|
|
602
|
-
deflate.extend_from_slice(&crate::core::adler32_bytes(&scanline).to_be_bytes());
|
|
603
|
-
|
|
604
|
-
let mut data = Vec::new();
|
|
605
|
-
data.extend_from_slice(&deflate[0..4]);
|
|
606
|
-
data.extend_from_slice(&[200, 201, 202]);
|
|
607
|
-
data.extend_from_slice(&deflate[4..12]);
|
|
608
|
-
data.extend_from_slice(&[203, 204]);
|
|
609
|
-
data.extend_from_slice(&deflate[12..]);
|
|
610
|
-
|
|
611
|
-
let ms = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis();
|
|
612
|
-
let path = std::env::temp_dir().join(format!("rox_deflate_reader_test_{}.bin", ms));
|
|
613
|
-
std::fs::write(&path, &data).unwrap();
|
|
614
|
-
|
|
615
|
-
let ranges = vec![(0, 4), (7, 15), (17, 23)];
|
|
616
|
-
let file = File::open(&path).unwrap();
|
|
617
|
-
let mut reader = DeflatePixelReader::new(file, 2, 1, ranges).unwrap();
|
|
618
|
-
let mut out = Vec::new();
|
|
619
|
-
reader.read_to_end(&mut out).unwrap();
|
|
620
|
-
|
|
621
|
-
assert_eq!(out, vec![10, 11, 12, 13, 14, 15]);
|
|
622
|
-
|
|
623
|
-
let _ = std::fs::remove_file(path);
|
|
624
|
-
}
|
|
625
|
-
}
|