roxify 1.11.0 → 1.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +1 -1
- package/dist/roxify_native +0 -0
- package/dist/roxify_native.exe +0 -0
- package/libroxify_native-x86_64-unknown-linux-gnu.node +0 -0
- package/native/archive.rs +61 -17
- package/native/core.rs +97 -12
- package/native/crypto.rs +1 -1
- package/native/encoder.rs +110 -58
- package/native/hybrid.rs +1 -1
- package/native/lib.rs +1 -0
- package/native/main.rs +55 -29
- package/native/streaming.rs +214 -0
- package/package.json +1 -1
- package/roxify_native-x86_64-pc-windows-msvc.node +0 -0
- package/roxify_native-x86_64-unknown-linux-gnu.node +0 -0
package/Cargo.toml
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/native/archive.rs
CHANGED
|
@@ -4,7 +4,12 @@ use rayon::prelude::*;
|
|
|
4
4
|
use tar::{Archive, Builder, Header};
|
|
5
5
|
use walkdir::WalkDir;
|
|
6
6
|
|
|
7
|
-
pub
|
|
7
|
+
pub struct TarPackResult {
|
|
8
|
+
pub data: Vec<u8>,
|
|
9
|
+
pub file_list: Vec<(String, u64)>,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
pub fn tar_pack_directory_with_list(dir_path: &Path) -> Result<TarPackResult, String> {
|
|
8
13
|
let base = dir_path;
|
|
9
14
|
|
|
10
15
|
let entries: Vec<_> = WalkDir::new(dir_path)
|
|
@@ -27,6 +32,10 @@ pub fn tar_pack_directory(dir_path: &Path) -> Result<Vec<u8>, String> {
|
|
|
27
32
|
})
|
|
28
33
|
.collect();
|
|
29
34
|
|
|
35
|
+
let file_list: Vec<(String, u64)> = file_data.iter()
|
|
36
|
+
.map(|(name, data)| (name.clone(), data.len() as u64))
|
|
37
|
+
.collect();
|
|
38
|
+
|
|
30
39
|
let total_estimate: usize = file_data.iter().map(|(n, d)| 512 + d.len() + 512 + n.len()).sum();
|
|
31
40
|
let mut buf = Vec::with_capacity(total_estimate + 1024);
|
|
32
41
|
{
|
|
@@ -42,20 +51,42 @@ pub fn tar_pack_directory(dir_path: &Path) -> Result<Vec<u8>, String> {
|
|
|
42
51
|
}
|
|
43
52
|
builder.finish().map_err(|e| format!("tar finish: {}", e))?;
|
|
44
53
|
}
|
|
45
|
-
Ok(buf)
|
|
54
|
+
Ok(TarPackResult { data: buf, file_list })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
pub fn tar_pack_directory(dir_path: &Path) -> Result<Vec<u8>, String> {
|
|
58
|
+
tar_pack_directory_with_list(dir_path).map(|r| r.data)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub fn tar_file_list_fast(tar_data: &[u8]) -> Vec<(String, u64)> {
|
|
62
|
+
let mut list = Vec::new();
|
|
63
|
+
let mut pos = 0;
|
|
64
|
+
while pos + 512 <= tar_data.len() {
|
|
65
|
+
let header = &tar_data[pos..pos + 512];
|
|
66
|
+
if header.iter().all(|&b| b == 0) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
let name_end = header[..100].iter().position(|&b| b == 0).unwrap_or(100);
|
|
70
|
+
let name = String::from_utf8_lossy(&header[..name_end]).to_string();
|
|
71
|
+
let size_str = String::from_utf8_lossy(&header[124..136]);
|
|
72
|
+
let size = u64::from_str_radix(size_str.trim().trim_matches('\0'), 8).unwrap_or(0);
|
|
73
|
+
if !name.is_empty() {
|
|
74
|
+
list.push((name, size));
|
|
75
|
+
}
|
|
76
|
+
let data_blocks = (size as usize + 511) / 512;
|
|
77
|
+
pos += 512 + data_blocks * 512;
|
|
78
|
+
}
|
|
79
|
+
list
|
|
46
80
|
}
|
|
47
81
|
|
|
48
82
|
pub fn tar_unpack(tar_data: &[u8], output_dir: &Path) -> Result<Vec<String>, String> {
|
|
49
83
|
let mut archive = Archive::new(Cursor::new(tar_data));
|
|
50
|
-
let mut
|
|
84
|
+
let mut entries_data: Vec<(std::path::PathBuf, Vec<u8>)> = Vec::new();
|
|
51
85
|
|
|
52
86
|
let entries = archive.entries().map_err(|e| format!("tar entries: {}", e))?;
|
|
53
87
|
for entry in entries {
|
|
54
88
|
let mut entry = entry.map_err(|e| format!("tar entry: {}", e))?;
|
|
55
|
-
let path = entry
|
|
56
|
-
.path()
|
|
57
|
-
.map_err(|e| format!("tar entry path: {}", e))?
|
|
58
|
-
.to_path_buf();
|
|
89
|
+
let path = entry.path().map_err(|e| format!("tar entry path: {}", e))?.to_path_buf();
|
|
59
90
|
|
|
60
91
|
let mut safe = std::path::PathBuf::new();
|
|
61
92
|
for comp in path.components() {
|
|
@@ -63,23 +94,36 @@ pub fn tar_unpack(tar_data: &[u8], output_dir: &Path) -> Result<Vec<String>, Str
|
|
|
63
94
|
safe.push(osstr);
|
|
64
95
|
}
|
|
65
96
|
}
|
|
66
|
-
|
|
67
97
|
if safe.as_os_str().is_empty() {
|
|
68
98
|
continue;
|
|
69
99
|
}
|
|
70
100
|
|
|
71
|
-
let
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
101
|
+
let mut data = Vec::with_capacity(entry.size() as usize);
|
|
102
|
+
std::io::Read::read_to_end(&mut entry, &mut data)
|
|
103
|
+
.map_err(|e| format!("tar read {:?}: {}", safe, e))?;
|
|
104
|
+
entries_data.push((safe, data));
|
|
105
|
+
}
|
|
76
106
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
107
|
+
let dirs: std::collections::HashSet<_> = entries_data.iter()
|
|
108
|
+
.filter_map(|(p, _)| {
|
|
109
|
+
let dest = output_dir.join(p);
|
|
110
|
+
dest.parent().map(|d| d.to_path_buf())
|
|
111
|
+
})
|
|
112
|
+
.collect();
|
|
113
|
+
for dir in &dirs {
|
|
114
|
+
std::fs::create_dir_all(dir).map_err(|e| format!("mkdir {:?}: {}", dir, e))?;
|
|
81
115
|
}
|
|
82
116
|
|
|
117
|
+
let written: Vec<String> = entries_data.par_iter()
|
|
118
|
+
.filter_map(|(safe, data)| {
|
|
119
|
+
let dest = output_dir.join(safe);
|
|
120
|
+
match std::fs::write(&dest, data) {
|
|
121
|
+
Ok(_) => Some(safe.to_string_lossy().to_string()),
|
|
122
|
+
Err(_) => None,
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
.collect();
|
|
126
|
+
|
|
83
127
|
Ok(written)
|
|
84
128
|
}
|
|
85
129
|
|
package/native/core.rs
CHANGED
|
@@ -85,15 +85,55 @@ pub fn crc32_bytes(buf: &[u8]) -> u32 {
|
|
|
85
85
|
|
|
86
86
|
pub fn adler32_bytes(buf: &[u8]) -> u32 {
|
|
87
87
|
const MOD: u32 = 65521;
|
|
88
|
+
const NMAX: usize = 5552;
|
|
89
|
+
|
|
90
|
+
if buf.len() > 4 * 1024 * 1024 {
|
|
91
|
+
return adler32_parallel(buf);
|
|
92
|
+
}
|
|
93
|
+
|
|
88
94
|
let mut a: u32 = 1;
|
|
89
95
|
let mut b: u32 = 0;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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;
|
|
93
104
|
}
|
|
105
|
+
|
|
94
106
|
(b << 16) | a
|
|
95
107
|
}
|
|
96
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)
|
|
135
|
+
}
|
|
136
|
+
|
|
97
137
|
pub fn delta_encode_bytes(buf: &[u8]) -> Vec<u8> {
|
|
98
138
|
let len = buf.len();
|
|
99
139
|
if len == 0 {
|
|
@@ -173,40 +213,85 @@ pub fn train_zstd_dictionary(sample_paths: &[PathBuf], dict_size: usize) -> Resu
|
|
|
173
213
|
/// For large buffers (>50 MiB) without a dictionary, multiple chunk sizes
|
|
174
214
|
/// are benchmarked on a sample and the best is selected automatically.
|
|
175
215
|
pub fn zstd_compress_bytes(buf: &[u8], level: i32, dict: Option<&[u8]>) -> std::result::Result<Vec<u8>, String> {
|
|
216
|
+
zstd_compress_with_prefix(buf, level, dict, &[])
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
pub fn zstd_compress_with_prefix(buf: &[u8], level: i32, dict: Option<&[u8]>, prefix: &[u8]) -> std::result::Result<Vec<u8>, String> {
|
|
176
220
|
use std::io::Write;
|
|
177
221
|
|
|
178
222
|
let actual_level = level.min(22).max(1);
|
|
223
|
+
let total_len = prefix.len() + buf.len();
|
|
224
|
+
|
|
225
|
+
let adaptive_level = if total_len > 2 * 1024 * 1024 * 1024 {
|
|
226
|
+
actual_level.min(1)
|
|
227
|
+
} else if total_len > 1024 * 1024 * 1024 {
|
|
228
|
+
actual_level.min(3)
|
|
229
|
+
} else if total_len > 256 * 1024 * 1024 {
|
|
230
|
+
actual_level.min(6)
|
|
231
|
+
} else if total_len > 64 * 1024 * 1024 {
|
|
232
|
+
actual_level.min(12)
|
|
233
|
+
} else {
|
|
234
|
+
actual_level
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
if dict.is_none() && total_len < 4 * 1024 * 1024 {
|
|
238
|
+
if prefix.is_empty() {
|
|
239
|
+
return zstd::bulk::compress(buf, adaptive_level)
|
|
240
|
+
.map_err(|e| format!("zstd bulk compress error: {}", e));
|
|
241
|
+
}
|
|
242
|
+
let mut combined = Vec::with_capacity(total_len);
|
|
243
|
+
combined.extend_from_slice(prefix);
|
|
244
|
+
combined.extend_from_slice(buf);
|
|
245
|
+
return zstd::bulk::compress(&combined, adaptive_level)
|
|
246
|
+
.map_err(|e| format!("zstd bulk compress error: {}", e));
|
|
247
|
+
}
|
|
248
|
+
|
|
179
249
|
let mut encoder = if let Some(d) = dict {
|
|
180
|
-
zstd::stream::Encoder::with_dictionary(Vec::
|
|
250
|
+
zstd::stream::Encoder::with_dictionary(Vec::with_capacity(total_len / 2), adaptive_level, d)
|
|
181
251
|
.map_err(|e| format!("zstd encoder init error: {}", e))?
|
|
182
252
|
} else {
|
|
183
|
-
zstd::stream::Encoder::new(Vec::
|
|
253
|
+
zstd::stream::Encoder::new(Vec::with_capacity(total_len / 2), adaptive_level)
|
|
184
254
|
.map_err(|e| format!("zstd encoder init error: {}", e))?
|
|
185
255
|
};
|
|
186
256
|
|
|
187
257
|
let threads = num_cpus::get() as u32;
|
|
188
258
|
if threads > 1 {
|
|
189
|
-
let max_threads = if
|
|
259
|
+
let max_threads = if adaptive_level >= 20 { threads.min(4) } else { threads };
|
|
190
260
|
let _ = encoder.multithread(max_threads);
|
|
191
261
|
}
|
|
192
262
|
|
|
193
|
-
if
|
|
263
|
+
if total_len > 256 * 1024 && adaptive_level >= 3 {
|
|
194
264
|
let _ = encoder.long_distance_matching(true);
|
|
195
|
-
|
|
196
|
-
|
|
265
|
+
}
|
|
266
|
+
if total_len > 256 * 1024 {
|
|
267
|
+
let wlog = if total_len > 1024 * 1024 * 1024 { 30 }
|
|
268
|
+
else if total_len > 512 * 1024 * 1024 { 29 }
|
|
269
|
+
else if total_len > 64 * 1024 * 1024 { 28 }
|
|
270
|
+
else if total_len > 8 * 1024 * 1024 { 27 }
|
|
197
271
|
else { 26 };
|
|
198
272
|
let _ = encoder.window_log(wlog);
|
|
199
273
|
}
|
|
200
274
|
|
|
201
|
-
let _ = encoder.set_pledged_src_size(Some(
|
|
275
|
+
let _ = encoder.set_pledged_src_size(Some(total_len as u64));
|
|
276
|
+
|
|
277
|
+
if !prefix.is_empty() {
|
|
278
|
+
encoder.write_all(prefix).map_err(|e| format!("zstd write prefix error: {}", e))?;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let chunk_size = if total_len > 256 * 1024 * 1024 { 16 * 1024 * 1024 }
|
|
282
|
+
else if total_len > 64 * 1024 * 1024 { 8 * 1024 * 1024 }
|
|
283
|
+
else { buf.len() };
|
|
284
|
+
|
|
285
|
+
for chunk in buf.chunks(chunk_size) {
|
|
286
|
+
encoder.write_all(chunk).map_err(|e| format!("zstd write error: {}", e))?;
|
|
287
|
+
}
|
|
202
288
|
|
|
203
|
-
encoder.write_all(buf).map_err(|e| format!("zstd write error: {}", e))?;
|
|
204
289
|
encoder.finish().map_err(|e| format!("zstd finish error: {}", e))
|
|
205
290
|
}
|
|
206
291
|
|
|
207
292
|
pub fn zstd_decompress_bytes(buf: &[u8], dict: Option<&[u8]>) -> std::result::Result<Vec<u8>, String> {
|
|
208
293
|
use std::io::Read;
|
|
209
|
-
let mut out = Vec::
|
|
294
|
+
let mut out = Vec::with_capacity(buf.len() * 2);
|
|
210
295
|
if let Some(d) = dict {
|
|
211
296
|
let mut decoder = zstd::stream::Decoder::with_dictionary(std::io::Cursor::new(buf), d)
|
|
212
297
|
.map_err(|e| format!("zstd decoder init error: {}", e))?;
|
package/native/crypto.rs
CHANGED
|
@@ -10,7 +10,7 @@ use sha2::Sha256;
|
|
|
10
10
|
const ENC_NONE: u8 = 0x00;
|
|
11
11
|
const ENC_AES: u8 = 0x01;
|
|
12
12
|
const ENC_XOR: u8 = 0x02;
|
|
13
|
-
const PBKDF2_ITERS: u32 =
|
|
13
|
+
const PBKDF2_ITERS: u32 = 600_000;
|
|
14
14
|
|
|
15
15
|
pub fn encrypt_xor(data: &[u8], passphrase: &str) -> Vec<u8> {
|
|
16
16
|
let key = passphrase.as_bytes();
|
package/native/encoder.rs
CHANGED
|
@@ -2,7 +2,6 @@ use anyhow::Result;
|
|
|
2
2
|
use std::process::{Command, Stdio};
|
|
3
3
|
|
|
4
4
|
const MAGIC: &[u8] = b"ROX1";
|
|
5
|
-
const ENC_NONE: u8 = 0x00;
|
|
6
5
|
const PIXEL_MAGIC: &[u8] = b"PXL1";
|
|
7
6
|
const PNG_HEADER: &[u8] = &[137, 80, 78, 71, 13, 10, 26, 10];
|
|
8
7
|
|
|
@@ -112,9 +111,7 @@ pub fn encode_to_wav_with_encryption_name_and_filelist(
|
|
|
112
111
|
file_list: Option<&str>,
|
|
113
112
|
) -> Result<Vec<u8>> {
|
|
114
113
|
// Same compression + encryption pipeline as PNG
|
|
115
|
-
let
|
|
116
|
-
|
|
117
|
-
let compressed = crate::core::zstd_compress_bytes(&payload_input, compression_level, None)
|
|
114
|
+
let compressed = crate::core::zstd_compress_with_prefix(data, compression_level, None, MAGIC)
|
|
118
115
|
.map_err(|e| anyhow::anyhow!("Compression failed: {}", e))?;
|
|
119
116
|
|
|
120
117
|
let encrypted = if let Some(pass) = passphrase {
|
|
@@ -180,9 +177,7 @@ fn encode_to_png_with_encryption_name_and_filelist_internal(
|
|
|
180
177
|
file_list: Option<&str>,
|
|
181
178
|
dict: Option<&[u8]>,
|
|
182
179
|
) -> Result<Vec<u8>> {
|
|
183
|
-
let
|
|
184
|
-
|
|
185
|
-
let compressed = crate::core::zstd_compress_bytes(&payload_input, compression_level, dict)
|
|
180
|
+
let compressed = crate::core::zstd_compress_with_prefix(data, compression_level, dict, MAGIC)
|
|
186
181
|
.map_err(|e| anyhow::anyhow!("Compression failed: {}", e))?;
|
|
187
182
|
|
|
188
183
|
let encrypted = if let Some(pass) = passphrase {
|
|
@@ -194,65 +189,73 @@ fn encode_to_png_with_encryption_name_and_filelist_internal(
|
|
|
194
189
|
} else {
|
|
195
190
|
crate::crypto::no_encryption(&compressed)
|
|
196
191
|
};
|
|
192
|
+
drop(compressed);
|
|
197
193
|
|
|
198
194
|
let meta_pixel = build_meta_pixel_with_name_and_filelist(&encrypted, name, file_list)?;
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
let padding_needed = (3 - (data_without_markers.len() % 3)) % 3;
|
|
202
|
-
let padded_data = if padding_needed > 0 {
|
|
203
|
-
[&data_without_markers[..], &vec![0u8; padding_needed]].concat()
|
|
204
|
-
} else {
|
|
205
|
-
data_without_markers
|
|
206
|
-
};
|
|
195
|
+
drop(encrypted);
|
|
207
196
|
|
|
208
|
-
let
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
marker_bytes.extend_from_slice(&[MARKER_ZSTD.0, MARKER_ZSTD.1, MARKER_ZSTD.2]);
|
|
197
|
+
let raw_payload_len = PIXEL_MAGIC.len() + meta_pixel.len();
|
|
198
|
+
let padding_needed = (3 - (raw_payload_len % 3)) % 3;
|
|
199
|
+
let padded_len = raw_payload_len + padding_needed;
|
|
213
200
|
|
|
214
|
-
let
|
|
201
|
+
let marker_start_len = 12;
|
|
202
|
+
let marker_end_len = 9;
|
|
215
203
|
|
|
216
|
-
let
|
|
217
|
-
|
|
218
|
-
marker_end_bytes.extend_from_slice(&[m.0, m.1, m.2]);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
let data_pixels = (data_with_markers.len() + 2) / 3;
|
|
204
|
+
let data_with_markers_len = marker_start_len + padded_len;
|
|
205
|
+
let data_pixels = (data_with_markers_len + 2) / 3;
|
|
222
206
|
let end_marker_pixels = 3;
|
|
223
207
|
let total_pixels = data_pixels + end_marker_pixels;
|
|
224
208
|
|
|
225
209
|
let side = (total_pixels as f64).sqrt().ceil() as usize;
|
|
226
210
|
let side = side.max(end_marker_pixels);
|
|
227
|
-
|
|
228
211
|
let width = side;
|
|
229
212
|
let height = side;
|
|
230
213
|
|
|
231
214
|
let total_data_bytes = width * height * 3;
|
|
232
|
-
let
|
|
215
|
+
let marker_end_pos = (height - 1) * width * 3 + (width - end_marker_pixels) * 3;
|
|
233
216
|
|
|
234
|
-
let
|
|
217
|
+
let flat = build_flat_pixel_buffer(&meta_pixel, padding_needed, marker_end_pos, marker_end_len, total_data_bytes);
|
|
218
|
+
drop(meta_pixel);
|
|
235
219
|
|
|
236
|
-
let
|
|
237
|
-
|
|
220
|
+
let row_bytes = width * 3;
|
|
221
|
+
let idat_data = create_raw_deflate_from_rows(&flat, row_bytes, height);
|
|
222
|
+
drop(flat);
|
|
238
223
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
.copy_from_slice(&marker_end_bytes[..end_len]);
|
|
242
|
-
|
|
243
|
-
let stride = width * 3 + 1;
|
|
244
|
-
let mut scanlines = Vec::with_capacity(height * stride);
|
|
224
|
+
build_png(width, height, &idat_data, file_list)
|
|
225
|
+
}
|
|
245
226
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
227
|
+
fn build_flat_pixel_buffer(
|
|
228
|
+
meta_pixel: &[u8],
|
|
229
|
+
padding_needed: usize,
|
|
230
|
+
marker_end_pos: usize,
|
|
231
|
+
marker_end_len: usize,
|
|
232
|
+
total_data_bytes: usize,
|
|
233
|
+
) -> Vec<u8> {
|
|
234
|
+
let header_len = 12 + PIXEL_MAGIC.len() + meta_pixel.len() + padding_needed;
|
|
235
|
+
let mut flat = vec![0u8; total_data_bytes];
|
|
236
|
+
|
|
237
|
+
let mut pos = 0;
|
|
238
|
+
for m in &MARKER_START {
|
|
239
|
+
flat[pos] = m.0; flat[pos + 1] = m.1; flat[pos + 2] = m.2;
|
|
240
|
+
pos += 3;
|
|
251
241
|
}
|
|
242
|
+
flat[pos] = MARKER_ZSTD.0; flat[pos + 1] = MARKER_ZSTD.1; flat[pos + 2] = MARKER_ZSTD.2;
|
|
243
|
+
pos += 3;
|
|
252
244
|
|
|
253
|
-
|
|
245
|
+
flat[pos..pos + PIXEL_MAGIC.len()].copy_from_slice(PIXEL_MAGIC);
|
|
246
|
+
pos += PIXEL_MAGIC.len();
|
|
254
247
|
|
|
255
|
-
|
|
248
|
+
flat[pos..pos + meta_pixel.len()].copy_from_slice(meta_pixel);
|
|
249
|
+
pos += meta_pixel.len();
|
|
250
|
+
|
|
251
|
+
if marker_end_pos + marker_end_len <= total_data_bytes {
|
|
252
|
+
for (i, m) in MARKER_END.iter().enumerate() {
|
|
253
|
+
let off = marker_end_pos + i * 3;
|
|
254
|
+
flat[off] = m.0; flat[off + 1] = m.1; flat[off + 2] = m.2;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
flat
|
|
256
259
|
}
|
|
257
260
|
|
|
258
261
|
fn build_meta_pixel(payload: &[u8]) -> Result<Vec<u8>> {
|
|
@@ -323,33 +326,37 @@ fn write_chunk(out: &mut Vec<u8>, chunk_type: &[u8; 4], data: &[u8]) -> Result<(
|
|
|
323
326
|
out.extend_from_slice(chunk_type);
|
|
324
327
|
out.extend_from_slice(data);
|
|
325
328
|
|
|
326
|
-
let mut
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
let crc =
|
|
329
|
+
let mut hasher = crc32fast::Hasher::new();
|
|
330
|
+
hasher.update(chunk_type);
|
|
331
|
+
hasher.update(data);
|
|
332
|
+
let crc = hasher.finalize();
|
|
330
333
|
|
|
331
334
|
out.extend_from_slice(&crc.to_be_bytes());
|
|
332
335
|
Ok(())
|
|
333
336
|
}
|
|
334
337
|
|
|
335
338
|
fn create_raw_deflate(data: &[u8]) -> Vec<u8> {
|
|
336
|
-
|
|
339
|
+
const MAX_BLOCK: usize = 65535;
|
|
340
|
+
let num_blocks = (data.len() + MAX_BLOCK - 1) / MAX_BLOCK;
|
|
341
|
+
let total_size = 2 + num_blocks * 5 + data.len() + 4;
|
|
342
|
+
let mut result = Vec::with_capacity(total_size);
|
|
337
343
|
|
|
338
344
|
result.push(0x78);
|
|
339
345
|
result.push(0x01);
|
|
340
346
|
|
|
341
347
|
let mut offset = 0;
|
|
342
348
|
while offset < data.len() {
|
|
343
|
-
let chunk_size = (data.len() - offset).min(
|
|
349
|
+
let chunk_size = (data.len() - offset).min(MAX_BLOCK);
|
|
344
350
|
let is_last = offset + chunk_size >= data.len();
|
|
345
351
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
352
|
+
let header = [
|
|
353
|
+
if is_last { 0x01 } else { 0x00 },
|
|
354
|
+
chunk_size as u8,
|
|
355
|
+
(chunk_size >> 8) as u8,
|
|
356
|
+
!chunk_size as u8,
|
|
357
|
+
(!(chunk_size >> 8)) as u8,
|
|
358
|
+
];
|
|
359
|
+
result.extend_from_slice(&header);
|
|
353
360
|
result.extend_from_slice(&data[offset..offset + chunk_size]);
|
|
354
361
|
offset += chunk_size;
|
|
355
362
|
}
|
|
@@ -360,6 +367,51 @@ fn create_raw_deflate(data: &[u8]) -> Vec<u8> {
|
|
|
360
367
|
result
|
|
361
368
|
}
|
|
362
369
|
|
|
370
|
+
fn create_raw_deflate_from_rows(flat: &[u8], row_bytes: usize, height: usize) -> Vec<u8> {
|
|
371
|
+
let stride = row_bytes + 1;
|
|
372
|
+
let scanlines_total = height * stride;
|
|
373
|
+
|
|
374
|
+
let mut scanlines = vec![0u8; scanlines_total];
|
|
375
|
+
for row in 0..height {
|
|
376
|
+
let flat_start = row * row_bytes;
|
|
377
|
+
let flat_end = (flat_start + row_bytes).min(flat.len());
|
|
378
|
+
let copy_len = flat_end.saturating_sub(flat_start);
|
|
379
|
+
if copy_len > 0 {
|
|
380
|
+
let dst_start = row * stride + 1;
|
|
381
|
+
scanlines[dst_start..dst_start + copy_len].copy_from_slice(&flat[flat_start..flat_end]);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const MAX_BLOCK: usize = 65535;
|
|
386
|
+
let num_blocks = (scanlines_total + MAX_BLOCK - 1) / MAX_BLOCK;
|
|
387
|
+
let total_size = 2 + num_blocks * 5 + scanlines_total + 4;
|
|
388
|
+
let mut result = Vec::with_capacity(total_size);
|
|
389
|
+
|
|
390
|
+
result.push(0x78);
|
|
391
|
+
result.push(0x01);
|
|
392
|
+
|
|
393
|
+
let mut offset = 0;
|
|
394
|
+
while offset < scanlines.len() {
|
|
395
|
+
let chunk_size = (scanlines.len() - offset).min(MAX_BLOCK);
|
|
396
|
+
let is_last = offset + chunk_size >= scanlines.len();
|
|
397
|
+
let header = [
|
|
398
|
+
if is_last { 0x01 } else { 0x00 },
|
|
399
|
+
chunk_size as u8,
|
|
400
|
+
(chunk_size >> 8) as u8,
|
|
401
|
+
!chunk_size as u8,
|
|
402
|
+
(!(chunk_size >> 8)) as u8,
|
|
403
|
+
];
|
|
404
|
+
result.extend_from_slice(&header);
|
|
405
|
+
result.extend_from_slice(&scanlines[offset..offset + chunk_size]);
|
|
406
|
+
offset += chunk_size;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
let adler = crate::core::adler32_bytes(&scanlines);
|
|
410
|
+
result.extend_from_slice(&adler.to_be_bytes());
|
|
411
|
+
|
|
412
|
+
result
|
|
413
|
+
}
|
|
414
|
+
|
|
363
415
|
fn predict_best_format(data: &[u8]) -> ImageFormat {
|
|
364
416
|
if data.len() < 2048 {
|
|
365
417
|
return ImageFormat::Png;
|
package/native/hybrid.rs
CHANGED
|
@@ -5,7 +5,7 @@ use crate::mtf::{mtf_encode, mtf_decode, rle0_encode, rle0_decode};
|
|
|
5
5
|
use crate::rans_byte::{SymbolStats, rans_encode_block, rans_decode_block};
|
|
6
6
|
use crate::context_mixing::analyze_entropy;
|
|
7
7
|
|
|
8
|
-
const BLOCK_SIZE: usize =
|
|
8
|
+
const BLOCK_SIZE: usize = 1024 * 1024;
|
|
9
9
|
|
|
10
10
|
const BLOCK_FLAG_BWT: u8 = 0;
|
|
11
11
|
const BLOCK_FLAG_ZSTD: u8 = 1;
|
package/native/lib.rs
CHANGED
package/native/main.rs
CHANGED
|
@@ -14,6 +14,7 @@ mod png_utils;
|
|
|
14
14
|
mod audio;
|
|
15
15
|
mod reconstitution;
|
|
16
16
|
mod archive;
|
|
17
|
+
mod streaming;
|
|
17
18
|
|
|
18
19
|
use crate::encoder::ImageFormat;
|
|
19
20
|
use std::path::PathBuf;
|
|
@@ -104,15 +105,28 @@ enum Commands {
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
fn read_all(path: &PathBuf) -> anyhow::Result<Vec<u8>> {
|
|
107
|
-
let
|
|
108
|
-
let
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
let metadata = std::fs::metadata(path)?;
|
|
109
|
+
let size = metadata.len() as usize;
|
|
110
|
+
|
|
111
|
+
if size > 256 * 1024 * 1024 {
|
|
112
|
+
let file = File::open(path)?;
|
|
113
|
+
let mmap = unsafe { memmap2::Mmap::map(&file)? };
|
|
114
|
+
Ok(mmap.to_vec())
|
|
115
|
+
} else {
|
|
116
|
+
let mut f = File::open(path)?;
|
|
117
|
+
let mut buf = Vec::with_capacity(size);
|
|
118
|
+
f.read_to_end(&mut buf)?;
|
|
119
|
+
Ok(buf)
|
|
120
|
+
}
|
|
111
121
|
}
|
|
112
122
|
|
|
113
123
|
fn write_all(path: &PathBuf, data: &[u8]) -> anyhow::Result<()> {
|
|
114
|
-
let
|
|
115
|
-
|
|
124
|
+
let f = File::create(path)?;
|
|
125
|
+
let buf_size = if data.len() > 64 * 1024 * 1024 { 16 * 1024 * 1024 }
|
|
126
|
+
else { (8 * 1024 * 1024).min(data.len().max(8192)) };
|
|
127
|
+
let mut writer = std::io::BufWriter::with_capacity(buf_size, f);
|
|
128
|
+
writer.write_all(data)?;
|
|
129
|
+
writer.flush()?;
|
|
116
130
|
Ok(())
|
|
117
131
|
}
|
|
118
132
|
|
|
@@ -144,14 +158,12 @@ fn main() -> anyhow::Result<()> {
|
|
|
144
158
|
Commands::Encode { input, output, level, passphrase, encrypt, name, dict } => {
|
|
145
159
|
let is_dir = input.is_dir();
|
|
146
160
|
let (payload, file_list_json) = if is_dir {
|
|
147
|
-
let
|
|
161
|
+
let result = archive::tar_pack_directory_with_list(&input)
|
|
148
162
|
.map_err(|e| anyhow::anyhow!(e))?;
|
|
149
|
-
let
|
|
150
|
-
.map_err(|e| anyhow::anyhow!(e))?;
|
|
151
|
-
let json_list: Vec<serde_json::Value> = list.iter()
|
|
163
|
+
let json_list: Vec<serde_json::Value> = result.file_list.iter()
|
|
152
164
|
.map(|(name, size)| serde_json::json!({"name": name, "size": size}))
|
|
153
165
|
.collect();
|
|
154
|
-
(
|
|
166
|
+
(result.data, Some(serde_json::to_string(&json_list)?))
|
|
155
167
|
} else {
|
|
156
168
|
let pack_result = packer::pack_path_with_metadata(&input)?;
|
|
157
169
|
(pack_result.data, pack_result.file_list_json)
|
|
@@ -165,31 +177,45 @@ fn main() -> anyhow::Result<()> {
|
|
|
165
177
|
None => None,
|
|
166
178
|
};
|
|
167
179
|
|
|
168
|
-
let
|
|
169
|
-
|
|
180
|
+
let use_streaming = payload.len() > 64 * 1024 * 1024;
|
|
181
|
+
|
|
182
|
+
if use_streaming {
|
|
183
|
+
streaming::encode_to_png_file(
|
|
170
184
|
&payload,
|
|
185
|
+
&output,
|
|
171
186
|
level,
|
|
172
|
-
|
|
187
|
+
passphrase.as_deref(),
|
|
173
188
|
Some(&encrypt),
|
|
174
|
-
ImageFormat::Png,
|
|
175
189
|
file_name,
|
|
176
190
|
file_list_json.as_deref(),
|
|
177
191
|
dict_bytes.as_deref(),
|
|
178
|
-
)
|
|
192
|
+
)?;
|
|
179
193
|
} else {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
194
|
+
let png = if let Some(ref pass) = passphrase {
|
|
195
|
+
encoder::encode_to_png_with_encryption_name_and_format_and_filelist(
|
|
196
|
+
&payload,
|
|
197
|
+
level,
|
|
198
|
+
Some(pass),
|
|
199
|
+
Some(&encrypt),
|
|
200
|
+
ImageFormat::Png,
|
|
201
|
+
file_name,
|
|
202
|
+
file_list_json.as_deref(),
|
|
203
|
+
dict_bytes.as_deref(),
|
|
204
|
+
)?
|
|
205
|
+
} else {
|
|
206
|
+
encoder::encode_to_png_with_encryption_name_and_format_and_filelist(
|
|
207
|
+
&payload,
|
|
208
|
+
level,
|
|
209
|
+
None,
|
|
210
|
+
None,
|
|
211
|
+
ImageFormat::Png,
|
|
212
|
+
file_name,
|
|
213
|
+
file_list_json.as_deref(),
|
|
214
|
+
dict_bytes.as_deref(),
|
|
215
|
+
)?
|
|
216
|
+
};
|
|
217
|
+
write_all(&output, &png)?;
|
|
218
|
+
}
|
|
193
219
|
|
|
194
220
|
if file_list_json.is_some() {
|
|
195
221
|
if is_dir {
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
use std::io::{Write, BufWriter};
|
|
2
|
+
use std::fs::File;
|
|
3
|
+
use std::path::Path;
|
|
4
|
+
|
|
5
|
+
const PNG_HEADER: &[u8] = &[137, 80, 78, 71, 13, 10, 26, 10];
|
|
6
|
+
const PIXEL_MAGIC: &[u8] = b"PXL1";
|
|
7
|
+
const MARKER_START: [(u8, u8, u8); 3] = [(255, 0, 0), (0, 255, 0), (0, 0, 255)];
|
|
8
|
+
const MARKER_END: [(u8, u8, u8); 3] = [(0, 0, 255), (0, 255, 0), (255, 0, 0)];
|
|
9
|
+
const MARKER_ZSTD: (u8, u8, u8) = (0, 255, 0);
|
|
10
|
+
const MAGIC: &[u8] = b"ROX1";
|
|
11
|
+
|
|
12
|
+
pub fn encode_to_png_file(
|
|
13
|
+
data: &[u8],
|
|
14
|
+
output_path: &Path,
|
|
15
|
+
compression_level: i32,
|
|
16
|
+
passphrase: Option<&str>,
|
|
17
|
+
encrypt_type: Option<&str>,
|
|
18
|
+
name: Option<&str>,
|
|
19
|
+
file_list: Option<&str>,
|
|
20
|
+
dict: Option<&[u8]>,
|
|
21
|
+
) -> anyhow::Result<()> {
|
|
22
|
+
let compressed = crate::core::zstd_compress_with_prefix(data, compression_level, dict, MAGIC)
|
|
23
|
+
.map_err(|e| anyhow::anyhow!("Compression failed: {}", e))?;
|
|
24
|
+
|
|
25
|
+
let encrypted = if let Some(pass) = passphrase {
|
|
26
|
+
match encrypt_type.unwrap_or("aes") {
|
|
27
|
+
"xor" => crate::crypto::encrypt_xor(&compressed, pass),
|
|
28
|
+
"aes" => crate::crypto::encrypt_aes(&compressed, pass)?,
|
|
29
|
+
_ => crate::crypto::encrypt_aes(&compressed, pass)?,
|
|
30
|
+
}
|
|
31
|
+
} else {
|
|
32
|
+
crate::crypto::no_encryption(&compressed)
|
|
33
|
+
};
|
|
34
|
+
drop(compressed);
|
|
35
|
+
|
|
36
|
+
let meta_pixel = build_meta_pixel(&encrypted, name, file_list)?;
|
|
37
|
+
drop(encrypted);
|
|
38
|
+
|
|
39
|
+
let raw_payload_len = PIXEL_MAGIC.len() + meta_pixel.len();
|
|
40
|
+
let padding_needed = (3 - (raw_payload_len % 3)) % 3;
|
|
41
|
+
let padded_len = raw_payload_len + padding_needed;
|
|
42
|
+
|
|
43
|
+
let marker_start_len = 12;
|
|
44
|
+
let data_with_markers_len = marker_start_len + padded_len;
|
|
45
|
+
let data_pixels = (data_with_markers_len + 2) / 3;
|
|
46
|
+
let end_marker_pixels = 3;
|
|
47
|
+
let total_pixels = data_pixels + end_marker_pixels;
|
|
48
|
+
|
|
49
|
+
let side = (total_pixels as f64).sqrt().ceil() as usize;
|
|
50
|
+
let side = side.max(end_marker_pixels);
|
|
51
|
+
let width = side;
|
|
52
|
+
let height = side;
|
|
53
|
+
let row_bytes = width * 3;
|
|
54
|
+
let total_data_bytes = width * height * 3;
|
|
55
|
+
let marker_end_pos = (height - 1) * width * 3 + (width - end_marker_pixels) * 3;
|
|
56
|
+
|
|
57
|
+
let flat = build_flat_buffer(&meta_pixel, padding_needed, marker_end_pos, total_data_bytes);
|
|
58
|
+
drop(meta_pixel);
|
|
59
|
+
|
|
60
|
+
let stride = row_bytes + 1;
|
|
61
|
+
let scanlines_total = height * stride;
|
|
62
|
+
|
|
63
|
+
let mut scanlines = vec![0u8; scanlines_total];
|
|
64
|
+
for row in 0..height {
|
|
65
|
+
let flat_start = row * row_bytes;
|
|
66
|
+
let flat_end = (flat_start + row_bytes).min(flat.len());
|
|
67
|
+
let copy_len = flat_end.saturating_sub(flat_start);
|
|
68
|
+
if copy_len > 0 {
|
|
69
|
+
let dst = row * stride + 1;
|
|
70
|
+
scanlines[dst..dst + copy_len].copy_from_slice(&flat[flat_start..flat_end]);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
drop(flat);
|
|
74
|
+
|
|
75
|
+
let adler = crate::core::adler32_bytes(&scanlines);
|
|
76
|
+
|
|
77
|
+
const MAX_BLOCK: usize = 65535;
|
|
78
|
+
let num_blocks = (scanlines_total + MAX_BLOCK - 1) / MAX_BLOCK;
|
|
79
|
+
let idat_len = 2 + num_blocks * 5 + scanlines_total + 4;
|
|
80
|
+
|
|
81
|
+
let f = File::create(output_path)?;
|
|
82
|
+
let mut w = BufWriter::with_capacity(16 * 1024 * 1024, f);
|
|
83
|
+
|
|
84
|
+
w.write_all(PNG_HEADER)?;
|
|
85
|
+
|
|
86
|
+
let mut ihdr = [0u8; 13];
|
|
87
|
+
ihdr[0..4].copy_from_slice(&(width as u32).to_be_bytes());
|
|
88
|
+
ihdr[4..8].copy_from_slice(&(height as u32).to_be_bytes());
|
|
89
|
+
ihdr[8] = 8;
|
|
90
|
+
ihdr[9] = 2;
|
|
91
|
+
write_chunk_small(&mut w, b"IHDR", &ihdr)?;
|
|
92
|
+
|
|
93
|
+
write_idat_direct(&mut w, &scanlines, idat_len, adler)?;
|
|
94
|
+
drop(scanlines);
|
|
95
|
+
|
|
96
|
+
if let Some(fl) = file_list {
|
|
97
|
+
write_chunk_small(&mut w, b"rXFL", fl.as_bytes())?;
|
|
98
|
+
}
|
|
99
|
+
write_chunk_small(&mut w, b"IEND", &[])?;
|
|
100
|
+
w.flush()?;
|
|
101
|
+
|
|
102
|
+
Ok(())
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn write_idat_direct<W: Write>(
|
|
106
|
+
w: &mut W,
|
|
107
|
+
scanlines: &[u8],
|
|
108
|
+
idat_len: usize,
|
|
109
|
+
adler: u32,
|
|
110
|
+
) -> anyhow::Result<()> {
|
|
111
|
+
const MAX_BLOCK: usize = 65535;
|
|
112
|
+
|
|
113
|
+
w.write_all(&(idat_len as u32).to_be_bytes())?;
|
|
114
|
+
w.write_all(b"IDAT")?;
|
|
115
|
+
|
|
116
|
+
let mut crc = crc32fast::Hasher::new();
|
|
117
|
+
crc.update(b"IDAT");
|
|
118
|
+
|
|
119
|
+
let zlib = [0x78u8, 0x01];
|
|
120
|
+
w.write_all(&zlib)?;
|
|
121
|
+
crc.update(&zlib);
|
|
122
|
+
|
|
123
|
+
let mut offset = 0;
|
|
124
|
+
while offset < scanlines.len() {
|
|
125
|
+
let chunk_size = (scanlines.len() - offset).min(MAX_BLOCK);
|
|
126
|
+
let is_last = offset + chunk_size >= scanlines.len();
|
|
127
|
+
let header = [
|
|
128
|
+
if is_last { 0x01 } else { 0x00 },
|
|
129
|
+
chunk_size as u8,
|
|
130
|
+
(chunk_size >> 8) as u8,
|
|
131
|
+
!chunk_size as u8,
|
|
132
|
+
(!(chunk_size >> 8)) as u8,
|
|
133
|
+
];
|
|
134
|
+
w.write_all(&header)?;
|
|
135
|
+
crc.update(&header);
|
|
136
|
+
let slice = &scanlines[offset..offset + chunk_size];
|
|
137
|
+
w.write_all(slice)?;
|
|
138
|
+
crc.update(slice);
|
|
139
|
+
offset += chunk_size;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let adler_bytes = adler.to_be_bytes();
|
|
143
|
+
w.write_all(&adler_bytes)?;
|
|
144
|
+
crc.update(&adler_bytes);
|
|
145
|
+
|
|
146
|
+
w.write_all(&crc.finalize().to_be_bytes())?;
|
|
147
|
+
Ok(())
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fn build_flat_buffer(
|
|
151
|
+
meta_pixel: &[u8],
|
|
152
|
+
_padding_needed: usize,
|
|
153
|
+
marker_end_pos: usize,
|
|
154
|
+
total_data_bytes: usize,
|
|
155
|
+
) -> Vec<u8> {
|
|
156
|
+
let mut flat = vec![0u8; total_data_bytes];
|
|
157
|
+
|
|
158
|
+
let mut pos = 0;
|
|
159
|
+
for m in &MARKER_START {
|
|
160
|
+
flat[pos] = m.0; flat[pos + 1] = m.1; flat[pos + 2] = m.2;
|
|
161
|
+
pos += 3;
|
|
162
|
+
}
|
|
163
|
+
flat[pos] = MARKER_ZSTD.0; flat[pos + 1] = MARKER_ZSTD.1; flat[pos + 2] = MARKER_ZSTD.2;
|
|
164
|
+
pos += 3;
|
|
165
|
+
flat[pos..pos + PIXEL_MAGIC.len()].copy_from_slice(PIXEL_MAGIC);
|
|
166
|
+
pos += PIXEL_MAGIC.len();
|
|
167
|
+
flat[pos..pos + meta_pixel.len()].copy_from_slice(meta_pixel);
|
|
168
|
+
|
|
169
|
+
if marker_end_pos + 9 <= total_data_bytes {
|
|
170
|
+
for (i, m) in MARKER_END.iter().enumerate() {
|
|
171
|
+
let off = marker_end_pos + i * 3;
|
|
172
|
+
flat[off] = m.0; flat[off + 1] = m.1; flat[off + 2] = m.2;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
flat
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
fn build_meta_pixel(payload: &[u8], name: Option<&str>, file_list: Option<&str>) -> anyhow::Result<Vec<u8>> {
|
|
180
|
+
let version = 1u8;
|
|
181
|
+
let name_bytes = name.map(|n| n.as_bytes()).unwrap_or(&[]);
|
|
182
|
+
let name_len = name_bytes.len().min(255) as u8;
|
|
183
|
+
let payload_len_bytes = (payload.len() as u32).to_be_bytes();
|
|
184
|
+
|
|
185
|
+
let mut result = Vec::with_capacity(1 + 1 + name_len as usize + 4 + payload.len() + 256);
|
|
186
|
+
result.push(version);
|
|
187
|
+
result.push(name_len);
|
|
188
|
+
if name_len > 0 {
|
|
189
|
+
result.extend_from_slice(&name_bytes[..name_len as usize]);
|
|
190
|
+
}
|
|
191
|
+
result.extend_from_slice(&payload_len_bytes);
|
|
192
|
+
result.extend_from_slice(payload);
|
|
193
|
+
|
|
194
|
+
if let Some(fl) = file_list {
|
|
195
|
+
result.extend_from_slice(b"rXFL");
|
|
196
|
+
let json_bytes = fl.as_bytes();
|
|
197
|
+
result.extend_from_slice(&(json_bytes.len() as u32).to_be_bytes());
|
|
198
|
+
result.extend_from_slice(json_bytes);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
Ok(result)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
fn write_chunk_small<W: Write>(w: &mut W, chunk_type: &[u8; 4], data: &[u8]) -> anyhow::Result<()> {
|
|
205
|
+
w.write_all(&(data.len() as u32).to_be_bytes())?;
|
|
206
|
+
w.write_all(chunk_type)?;
|
|
207
|
+
w.write_all(data)?;
|
|
208
|
+
|
|
209
|
+
let mut h = crc32fast::Hasher::new();
|
|
210
|
+
h.update(chunk_type);
|
|
211
|
+
h.update(data);
|
|
212
|
+
w.write_all(&h.finalize().to_be_bytes())?;
|
|
213
|
+
Ok(())
|
|
214
|
+
}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|