roxify 1.12.6 → 1.12.8

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/native/main.rs ADDED
@@ -0,0 +1,534 @@
1
+ #![allow(dead_code)]
2
+ use clap::{Parser, Subcommand};
3
+ use std::fs::File;
4
+ use std::io::{Read, Write};
5
+
6
+ #[global_allocator]
7
+ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
8
+
9
+ mod core;
10
+ mod encoder;
11
+ mod packer;
12
+ mod crypto;
13
+ mod png_utils;
14
+ mod audio;
15
+ mod reconstitution;
16
+ mod archive;
17
+ mod streaming;
18
+ mod streaming_decode;
19
+ mod streaming_encode;
20
+
21
+ use crate::encoder::ImageFormat;
22
+ use std::path::PathBuf;
23
+
24
+ #[derive(Parser)]
25
+ #[command(author, version)]
26
+ struct Cli {
27
+ #[command(subcommand)]
28
+ command: Commands,
29
+ }
30
+
31
+ #[derive(Subcommand)]
32
+ enum Commands {
33
+ Encode {
34
+ input: PathBuf,
35
+ output: PathBuf,
36
+ #[arg(short, long, default_value_t = 3)]
37
+ level: i32,
38
+ #[arg(short, long)]
39
+ passphrase: Option<String>,
40
+ #[arg(short, long, default_value = "aes")]
41
+ encrypt: String,
42
+ #[arg(short, long)]
43
+ name: Option<String>,
44
+ /// optional zstd dictionary file for payload compression
45
+ #[arg(long, value_name = "FILE")]
46
+ dict: Option<PathBuf>,
47
+ },
48
+
49
+ List {
50
+ input: PathBuf,
51
+ },
52
+ Havepassphrase {
53
+ input: PathBuf,
54
+ },
55
+ Scan {
56
+ input: PathBuf,
57
+ #[arg(short, long, value_name = "FILE")]
58
+ #[arg(short, long, default_value_t = 4)]
59
+ channels: usize,
60
+ #[arg(short, long, value_delimiter = ',')]
61
+ markers: Vec<String>,
62
+ },
63
+ DeltaEncode {
64
+ input: PathBuf,
65
+ output: Option<PathBuf>,
66
+ },
67
+ DeltaDecode {
68
+ input: PathBuf,
69
+ output: Option<PathBuf>,
70
+ },
71
+ Compress {
72
+ input: PathBuf,
73
+ output: Option<PathBuf>,
74
+ #[arg(short, long, default_value_t = 19)]
75
+ level: i32,
76
+ /// optional zstd dictionary file to use for compression
77
+ #[arg(long, value_name = "FILE")]
78
+ dict: Option<PathBuf>,
79
+ },
80
+ TrainDict {
81
+ /// sample files used to train the dictionary
82
+ #[arg(short, long, value_name = "FILE", required = true)]
83
+ samples: Vec<PathBuf>,
84
+ /// desired dictionary size in bytes
85
+ #[arg(short, long, default_value_t = 112640)]
86
+ size: usize,
87
+ /// output dictionary file
88
+ output: PathBuf,
89
+ },
90
+ Decompress {
91
+ input: PathBuf,
92
+ output: Option<PathBuf>,
93
+ #[arg(long)]
94
+ files: Option<String>,
95
+ #[arg(short, long)]
96
+ passphrase: Option<String>,
97
+ /// optional dictionary file used during decompression
98
+ #[arg(long, value_name = "FILE")]
99
+ dict: Option<PathBuf>,
100
+ },
101
+ Crc32 {
102
+ input: PathBuf,
103
+ },
104
+ Adler32 {
105
+ input: PathBuf,
106
+ },
107
+ }
108
+
109
+ fn read_all(path: &PathBuf) -> anyhow::Result<Vec<u8>> {
110
+ let metadata = std::fs::metadata(path)?;
111
+ let size = metadata.len() as usize;
112
+
113
+ if size > 256 * 1024 * 1024 {
114
+ let file = File::open(path)?;
115
+ let mmap = unsafe { memmap2::Mmap::map(&file)? };
116
+ Ok(mmap.to_vec())
117
+ } else {
118
+ let mut f = File::open(path)?;
119
+ let mut buf = Vec::with_capacity(size);
120
+ f.read_to_end(&mut buf)?;
121
+ Ok(buf)
122
+ }
123
+ }
124
+
125
+ fn write_all(path: &PathBuf, data: &[u8]) -> anyhow::Result<()> {
126
+ let f = File::create(path)?;
127
+ let buf_size = if data.len() > 64 * 1024 * 1024 { 16 * 1024 * 1024 }
128
+ else { (8 * 1024 * 1024).min(data.len().max(8192)) };
129
+ let mut writer = std::io::BufWriter::with_capacity(buf_size, f);
130
+ writer.write_all(data)?;
131
+ writer.flush()?;
132
+ Ok(())
133
+ }
134
+
135
+ fn parse_markers(v: &[String]) -> Option<Vec<u8>> {
136
+ if v.is_empty() {
137
+ return None;
138
+ }
139
+ let mut out = Vec::new();
140
+ for s in v {
141
+ let parts: Vec<&str> = s.split(|c| c == ':' || c == ',' ).collect();
142
+ if parts.len() >= 3 {
143
+ if let (Ok(r), Ok(g), Ok(b)) = (parts[0].parse::<u8>(), parts[1].parse::<u8>(), parts[2].parse::<u8>()) {
144
+ out.push(r); out.push(g); out.push(b);
145
+ }
146
+ }
147
+ }
148
+ if out.is_empty() { None } else { Some(out) }
149
+ }
150
+
151
+ fn main() -> anyhow::Result<()> {
152
+ let cli = Cli::parse();
153
+ match cli.command {
154
+ Commands::TrainDict { samples, size, output } => {
155
+ let dict = core::train_zstd_dictionary(&samples, size)?;
156
+ write_all(&output, &dict)?;
157
+ println!("wrote {} bytes dictionary to {:?}", dict.len(), output);
158
+ return Ok(());
159
+ }
160
+ Commands::Encode { input, output, level, passphrase, encrypt, name, dict } => {
161
+ let is_dir = input.is_dir();
162
+
163
+ let file_name = name.as_deref()
164
+ .or_else(|| input.file_name().and_then(|n| n.to_str()));
165
+
166
+ if is_dir && dict.is_none() {
167
+ streaming_encode::encode_dir_to_png_encrypted(
168
+ &input,
169
+ &output,
170
+ level,
171
+ file_name,
172
+ passphrase.as_deref(),
173
+ Some(&encrypt),
174
+ )?;
175
+ println!("(TAR archive, rXFL chunk embedded)");
176
+ return Ok(());
177
+ }
178
+
179
+ let (payload, file_list_json) = if is_dir {
180
+ let result = archive::tar_pack_directory_with_list(&input)
181
+ .map_err(|e| anyhow::anyhow!(e))?;
182
+ let json_list: Vec<serde_json::Value> = result.file_list.iter()
183
+ .map(|(name, size)| serde_json::json!({"name": name, "size": size}))
184
+ .collect();
185
+ (result.data, Some(serde_json::to_string(&json_list)?))
186
+ } else {
187
+ let pack_result = packer::pack_path_with_metadata(&input)?;
188
+ (pack_result.data, pack_result.file_list_json)
189
+ };
190
+
191
+ let dict_bytes: Option<Vec<u8>> = match dict {
192
+ Some(path) => Some(read_all(&path)?),
193
+ None => None,
194
+ };
195
+
196
+ let use_streaming = payload.len() > 64 * 1024 * 1024;
197
+
198
+ if use_streaming {
199
+ streaming::encode_to_png_file(
200
+ &payload,
201
+ &output,
202
+ level,
203
+ passphrase.as_deref(),
204
+ Some(&encrypt),
205
+ file_name,
206
+ file_list_json.as_deref(),
207
+ dict_bytes.as_deref(),
208
+ )?;
209
+ } else {
210
+ let png = if let Some(ref pass) = passphrase {
211
+ encoder::encode_to_png_with_encryption_name_and_format_and_filelist(
212
+ &payload,
213
+ level,
214
+ Some(pass),
215
+ Some(&encrypt),
216
+ ImageFormat::Png,
217
+ file_name,
218
+ file_list_json.as_deref(),
219
+ dict_bytes.as_deref(),
220
+ )?
221
+ } else {
222
+ encoder::encode_to_png_with_encryption_name_and_format_and_filelist(
223
+ &payload,
224
+ level,
225
+ None,
226
+ None,
227
+ ImageFormat::Png,
228
+ file_name,
229
+ file_list_json.as_deref(),
230
+ dict_bytes.as_deref(),
231
+ )?
232
+ };
233
+ write_all(&output, &png)?;
234
+ }
235
+
236
+ if file_list_json.is_some() {
237
+ if is_dir {
238
+ println!("(TAR archive, rXFL chunk embedded)");
239
+ } else {
240
+ println!("(rXFL chunk embedded)");
241
+ }
242
+ }
243
+ }
244
+ Commands::List { input } => {
245
+ let mut file = File::open(&input)?;
246
+ let chunks = png_utils::extract_png_chunks_streaming(&mut file).map_err(|e| anyhow::anyhow!(e))?;
247
+
248
+ if let Some(rxfl_chunk) = chunks.iter().find(|c| c.name == "rXFL") {
249
+ println!("{}", String::from_utf8_lossy(&rxfl_chunk.data));
250
+ return Ok(());
251
+ }
252
+
253
+ if let Some(meta_chunk) = chunks.iter().find(|c| c.name == "rOXm") {
254
+ if let Some(pos) = meta_chunk.data.windows(4).position(|w| w == b"rXFL") {
255
+ if pos + 8 <= meta_chunk.data.len() {
256
+ let json_len = u32::from_be_bytes([
257
+ meta_chunk.data[pos + 4],
258
+ meta_chunk.data[pos + 5],
259
+ meta_chunk.data[pos + 6],
260
+ meta_chunk.data[pos + 7],
261
+ ]) as usize;
262
+
263
+ let json_start = pos + 8;
264
+ let json_end = json_start + json_len;
265
+
266
+ if json_end <= meta_chunk.data.len() {
267
+ println!("{}", String::from_utf8_lossy(&meta_chunk.data[json_start..json_end]));
268
+ return Ok(());
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ let png_data = std::fs::read(&input)?;
275
+ match png_utils::extract_file_list_from_pixels(&png_data) {
276
+ Ok(json) => {
277
+ println!("{}", json);
278
+ return Ok(());
279
+ }
280
+ Err(_) => {}
281
+ }
282
+
283
+ eprintln!("No file list found in PNG");
284
+ std::process::exit(1);
285
+ }
286
+ Commands::Havepassphrase { input } => {
287
+ let buf = read_all(&input)?;
288
+ let is_png = buf.len() >= 8 && &buf[0..8] == &[137, 80, 78, 71, 13, 10, 26, 10];
289
+ if is_png {
290
+ let payload = png_utils::extract_payload_from_png(&buf).map_err(|e| anyhow::anyhow!(e))?;
291
+ if !payload.is_empty() && (payload[0] == 0x01 || payload[0] == 0x02 || payload[0] == 0x03) {
292
+ println!("Passphrase detected.");
293
+ } else {
294
+ println!("No passphrase detected.");
295
+ }
296
+ } else {
297
+ if !buf.is_empty() && (buf[0] == 0x01 || buf[0] == 0x02 || buf[0] == 0x03) {
298
+ println!("Passphrase detected.");
299
+ } else {
300
+ println!("No passphrase detected.");
301
+ }
302
+ }
303
+ }
304
+ Commands::Scan { input, channels, markers } => {
305
+ let buf = read_all(&input)?;
306
+ let marker_bytes = parse_markers(&markers);
307
+ let res = crate::core::scan_pixels_bytes(&buf, channels, marker_bytes.as_deref());
308
+ println!("magic_positions: {:?}", res.magic_positions);
309
+ println!("marker_positions: {:?}", res.marker_positions);
310
+ }
311
+ Commands::DeltaEncode { input, output } => {
312
+ let buf = read_all(&input)?;
313
+ let out = crate::core::delta_encode_bytes(&buf);
314
+ let dest = output.unwrap_or_else(|| PathBuf::from("delta.bin"));
315
+ write_all(&dest, &out)?;
316
+ }
317
+ Commands::DeltaDecode { input, output } => {
318
+ let buf = read_all(&input)?;
319
+ let out = crate::core::delta_decode_bytes(&buf);
320
+ let dest = output.unwrap_or_else(|| PathBuf::from("raw.bin"));
321
+ write_all(&dest, &out)?;
322
+ }
323
+ Commands::Compress { input, output, level, dict } => {
324
+ let buf = read_all(&input)?;
325
+ let dict_bytes: Option<Vec<u8>> = match dict {
326
+ Some(path) => Some(read_all(&path)?),
327
+ None => None,
328
+ };
329
+ let out = crate::core::zstd_compress_bytes(
330
+ &buf,
331
+ level,
332
+ dict_bytes.as_deref(),
333
+ )
334
+ .map_err(|e: String| anyhow::anyhow!(e))?;
335
+ let dest = output.unwrap_or_else(|| PathBuf::from("out.zst"));
336
+ write_all(&dest, &out)?;
337
+ }
338
+ Commands::Decompress { input, output, files, passphrase, dict } => {
339
+ let file_size = std::fs::metadata(&input).map(|m| m.len()).unwrap_or(0);
340
+ let is_png_file = input.extension().map(|e| e == "png").unwrap_or(false)
341
+ || (file_size >= 8 && {
342
+ let mut sig = [0u8; 8];
343
+ std::fs::File::open(&input).and_then(|mut f| { use std::io::Read; f.read_exact(&mut sig) }).is_ok()
344
+ && sig == [137, 80, 78, 71, 13, 10, 26, 10]
345
+ });
346
+
347
+ if is_png_file && files.is_none() && dict.is_none() && file_size > 100_000_000 {
348
+ let out_dir = output.clone().unwrap_or_else(|| PathBuf::from("out.raw"));
349
+ match streaming_decode::streaming_decode_to_dir_encrypted(&input, &out_dir, passphrase.as_deref()) {
350
+ Ok(written) => {
351
+ println!("Unpacked {} files (TAR)", written.len());
352
+ return Ok(());
353
+ }
354
+ Err(e) => {
355
+ eprintln!("Streaming decode failed ({}), falling back to in-memory", e);
356
+ }
357
+ }
358
+ }
359
+
360
+ let buf = read_all(&input)?;
361
+ let dict_bytes: Option<Vec<u8>> = match dict {
362
+ Some(path) => Some(read_all(&path)?),
363
+ None => None,
364
+ };
365
+ if let Some(files_str) = files {
366
+ let file_list: Option<Vec<String>> = if files_str.trim_start().starts_with('[') {
367
+ match serde_json::from_str::<Vec<String>>(&files_str) {
368
+ Ok(v) => Some(v),
369
+ Err(e) => {
370
+ eprintln!("Invalid JSON for --files: {}", e);
371
+ std::process::exit(1);
372
+ }
373
+ }
374
+ } else {
375
+ let list = files_str.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect::<Vec<_>>();
376
+ Some(list)
377
+ };
378
+
379
+ let is_png = buf.len() >= 8 && &buf[0..8] == &[137, 80, 78, 71, 13, 10, 26, 10];
380
+
381
+ use std::io::Cursor;
382
+ let normalized: Vec<u8> = if is_png {
383
+ let payload = png_utils::extract_payload_from_png(&buf).map_err(|e| anyhow::anyhow!(e))?;
384
+ if payload.is_empty() { return Err(anyhow::anyhow!("Empty payload")); }
385
+ if payload[0] == 0x00u8 {
386
+ payload[1..].to_vec()
387
+ } else {
388
+ let pass = passphrase.as_ref().map(|s: &String| s.as_str());
389
+ match crate::crypto::try_decrypt(&payload, pass) {
390
+ Ok(v) => v,
391
+ Err(e) => return Err(anyhow::anyhow!("Encrypted payload: {}", e)),
392
+ }
393
+ }
394
+ } else {
395
+ if buf[0] == 0x00u8 {
396
+ buf[1..].to_vec()
397
+ } else if buf.starts_with(b"ROX1") {
398
+ buf[4..].to_vec()
399
+ } else if buf[0] == 0x01u8 || buf[0] == 0x02u8 || buf[0] == 0x03u8 {
400
+ let pass = passphrase.as_ref().map(|s: &String| s.as_str());
401
+ match crate::crypto::try_decrypt(&buf, pass) {
402
+ Ok(v) => v,
403
+ Err(e) => return Err(anyhow::anyhow!("Encrypted payload: {}", e)),
404
+ }
405
+ } else {
406
+ buf.to_vec()
407
+ }
408
+ };
409
+
410
+ let mut reader: Box<dyn std::io::Read> = if normalized.starts_with(b"ROX1") {
411
+ Box::new(Cursor::new(normalized[4..].to_vec()))
412
+ } else {
413
+ let mut dec = zstd::stream::Decoder::new(Cursor::new(normalized)).map_err(|e| anyhow::anyhow!("zstd decoder init: {}", e))?;
414
+ dec.window_log_max(31).map_err(|e| anyhow::anyhow!("zstd window_log_max: {}", e))?;
415
+ Box::new(dec)
416
+ };
417
+
418
+ let out_dir = output.unwrap_or_else(|| PathBuf::from("."));
419
+ std::fs::create_dir_all(&out_dir).map_err(|e| anyhow::anyhow!("Cannot create output directory {:?}: {}", out_dir, e))?;
420
+ let files_slice = file_list.as_ref().map(|v| v.as_slice());
421
+
422
+ let written = packer::unpack_stream_to_dir(&mut reader, &out_dir, files_slice).map_err(|e| anyhow::anyhow!(e))?;
423
+ println!("Unpacked {} files", written.len());
424
+ } else {
425
+ let is_png = buf.len() >= 8 && &buf[0..8] == &[137, 80, 78, 71, 13, 10, 26, 10];
426
+ let out_bytes = if is_png {
427
+ let payload = png_utils::extract_payload_from_png(&buf).map_err(|e| anyhow::anyhow!(e))?;
428
+ if payload.is_empty() { return Err(anyhow::anyhow!("Empty payload")); }
429
+ let first = payload[0];
430
+ if first == 0x00u8 {
431
+ let compressed = payload[1..].to_vec();
432
+ match crate::core::zstd_decompress_bytes(&compressed, dict_bytes.as_deref()) {
433
+ Ok(mut o) => {
434
+ if o.starts_with(b"ROX1") { o = o[4..].to_vec(); }
435
+ o
436
+ }
437
+ Err(e) => {
438
+ if compressed.starts_with(b"ROX1") {
439
+ eprintln!("⚠️ zstd decompress failed ({}), but payload starts with ROX1: falling back to raw pack", e);
440
+ compressed[4..].to_vec()
441
+ } else {
442
+ return Err(anyhow::anyhow!("zstd decompress error: {}", e));
443
+ }
444
+ }
445
+ }
446
+ } else {
447
+ let pass = passphrase.as_ref().map(|s| s.as_str());
448
+ match crate::crypto::try_decrypt(&payload, pass) {
449
+ Ok(v) => {
450
+ let inner = if v.starts_with(b"ROX1") {
451
+ v[4..].to_vec()
452
+ } else {
453
+ v
454
+ };
455
+ match crate::core::zstd_decompress_bytes(&inner, dict_bytes.as_deref()) {
456
+ Ok(mut o) => {
457
+ if o.starts_with(b"ROX1") { o = o[4..].to_vec(); }
458
+ o
459
+ }
460
+ Err(_) => inner,
461
+ }
462
+ }
463
+ Err(e) => return Err(anyhow::anyhow!("Encrypted payload: {}", e)),
464
+ }
465
+ }
466
+ } else {
467
+ match crate::core::zstd_decompress_bytes(&buf, dict_bytes.as_deref()) {
468
+ Ok(mut x) => { if x.starts_with(b"ROX1") { x = x[4..].to_vec(); } x },
469
+ Err(e) => {
470
+ if buf.starts_with(b"ROX1") {
471
+ eprintln!("⚠️ zstd decompress failed ({}), but input already starts with ROX1: using raw pack", e);
472
+ buf[4..].to_vec()
473
+ } else {
474
+ return Err(anyhow::anyhow!("zstd decompress error: {}", e));
475
+ }
476
+ }
477
+ }
478
+ };
479
+
480
+ let dest = output.unwrap_or_else(|| PathBuf::from("out.raw"));
481
+
482
+ if archive::is_tar(&out_bytes) {
483
+ let out_dir = if dest.extension().is_none() || dest.is_dir() {
484
+ dest
485
+ } else {
486
+ PathBuf::from(".")
487
+ };
488
+ std::fs::create_dir_all(&out_dir)
489
+ .map_err(|e| anyhow::anyhow!("mkdir {:?}: {}", out_dir, e))?;
490
+ let written = archive::tar_unpack(&out_bytes, &out_dir)
491
+ .map_err(|e| anyhow::anyhow!(e))?;
492
+ println!("Unpacked {} files (TAR) to {:?}", written.len(), out_dir);
493
+ } else if out_bytes.len() >= 4
494
+ && (u32::from_be_bytes(out_bytes[0..4].try_into().unwrap()) == 0x524f5850u32
495
+ || u32::from_be_bytes(out_bytes[0..4].try_into().unwrap()) == 0x524f5849u32)
496
+ {
497
+ let out_dir = if dest.extension().is_none() || dest.is_dir() {
498
+ dest
499
+ } else {
500
+ PathBuf::from(".")
501
+ };
502
+ std::fs::create_dir_all(&out_dir)
503
+ .map_err(|e| anyhow::anyhow!("mkdir {:?}: {}", out_dir, e))?;
504
+ let written = packer::unpack_buffer_to_dir(&out_bytes, &out_dir, None)
505
+ .map_err(|e| anyhow::anyhow!(e))?;
506
+ println!("Unpacked {} files to {:?}", written.len(), out_dir);
507
+ } else if dest.is_dir() {
508
+ let fname = if is_png {
509
+ png_utils::extract_name_from_png(&buf)
510
+ } else {
511
+ None
512
+ }.unwrap_or_else(|| {
513
+ input.file_stem()
514
+ .map(|s| s.to_string_lossy().to_string())
515
+ .unwrap_or_else(|| "out.raw".to_string())
516
+ });
517
+ write_all(&dest.join(&fname), &out_bytes)?;
518
+ } else {
519
+ write_all(&dest, &out_bytes)?;
520
+ }
521
+ }
522
+ }
523
+ Commands::Crc32 { input } => {
524
+ let buf = read_all(&input)?;
525
+ println!("crc32: {}", crate::core::crc32_bytes(&buf));
526
+ }
527
+ Commands::Adler32 { input } => {
528
+ let buf = read_all(&input)?;
529
+ println!("adler32: {}", crate::core::adler32_bytes(&buf));
530
+ }
531
+ }
532
+ Ok(())
533
+ }
534
+
package/native/mtf.rs ADDED
@@ -0,0 +1,106 @@
1
+ pub fn mtf_encode(data: &[u8]) -> Vec<u8> {
2
+ let mut table = [0u8; 256];
3
+ for i in 0..256 {
4
+ table[i] = i as u8;
5
+ }
6
+ let mut output = Vec::with_capacity(data.len());
7
+
8
+ for &byte in data {
9
+ let pos = unsafe { table.iter().position(|&b| b == byte).unwrap_unchecked() };
10
+ output.push(pos as u8);
11
+ if pos > 0 {
12
+ let val = table[pos];
13
+ table.copy_within(0..pos, 1);
14
+ table[0] = val;
15
+ }
16
+ }
17
+
18
+ output
19
+ }
20
+
21
+ pub fn mtf_decode(data: &[u8]) -> Vec<u8> {
22
+ let mut table = [0u8; 256];
23
+ for i in 0..256 {
24
+ table[i] = i as u8;
25
+ }
26
+ let mut output = Vec::with_capacity(data.len());
27
+
28
+ for &idx in data {
29
+ let pos = idx as usize;
30
+ let byte = table[pos];
31
+ output.push(byte);
32
+ if pos > 0 {
33
+ let val = table[pos];
34
+ table.copy_within(0..pos, 1);
35
+ table[0] = val;
36
+ }
37
+ }
38
+
39
+ output
40
+ }
41
+
42
+ pub fn rle0_encode(data: &[u8]) -> Vec<u8> {
43
+ let mut output = Vec::with_capacity(data.len());
44
+ let mut i = 0;
45
+
46
+ while i < data.len() {
47
+ if data[i] == 0 {
48
+ let mut run = 0u32;
49
+ while i < data.len() && data[i] == 0 {
50
+ run += 1;
51
+ i += 1;
52
+ }
53
+ output.push(0);
54
+ if run <= 127 {
55
+ output.push(run as u8);
56
+ } else if run <= 16383 {
57
+ output.push(0x80 | ((run >> 8) as u8));
58
+ output.push((run & 0xFF) as u8);
59
+ } else {
60
+ output.push(0xC0 | ((run >> 16) as u8));
61
+ output.push(((run >> 8) & 0xFF) as u8);
62
+ output.push((run & 0xFF) as u8);
63
+ }
64
+ } else {
65
+ output.push(data[i]);
66
+ i += 1;
67
+ }
68
+ }
69
+
70
+ output
71
+ }
72
+
73
+ pub fn rle0_decode(data: &[u8]) -> Vec<u8> {
74
+ let mut output = Vec::with_capacity(data.len() * 2);
75
+ let mut i = 0;
76
+
77
+ while i < data.len() {
78
+ if data[i] == 0 {
79
+ i += 1;
80
+ if i >= data.len() {
81
+ break;
82
+ }
83
+ let run;
84
+ if data[i] & 0xC0 == 0xC0 {
85
+ let hi = (data[i] & 0x3F) as u32;
86
+ run = (hi << 16) | ((data[i + 1] as u32) << 8) | (data[i + 2] as u32);
87
+ i += 3;
88
+ } else if data[i] & 0x80 != 0 {
89
+ let hi = (data[i] & 0x7F) as u32;
90
+ run = (hi << 8) | (data[i + 1] as u32);
91
+ i += 2;
92
+ } else {
93
+ run = data[i] as u32;
94
+ i += 1;
95
+ }
96
+ for _ in 0..run {
97
+ output.push(0);
98
+ }
99
+ } else {
100
+ output.push(data[i]);
101
+ i += 1;
102
+ }
103
+ }
104
+
105
+ output
106
+ }