roxify 1.13.8 → 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 +10 -1
- package/dist/utils/decoder.js +111 -7
- package/dist/utils/ecc.js +0 -1
- 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,146 +0,0 @@
|
|
|
1
|
-
use std::io::{self, Write};
|
|
2
|
-
|
|
3
|
-
pub const MAX_PNG_CHUNK_DATA_LEN: usize = 64 * 1024 * 1024;
|
|
4
|
-
|
|
5
|
-
pub fn write_png_chunk<W: Write>(writer: &mut W, chunk_type: &[u8; 4], data: &[u8]) -> anyhow::Result<()> {
|
|
6
|
-
let len = u32::try_from(data.len())
|
|
7
|
-
.map_err(|_| anyhow::anyhow!("chunk too large: {}", data.len()))?;
|
|
8
|
-
writer.write_all(&len.to_be_bytes())?;
|
|
9
|
-
writer.write_all(chunk_type)?;
|
|
10
|
-
writer.write_all(data)?;
|
|
11
|
-
|
|
12
|
-
let mut hasher = crc32fast::Hasher::new();
|
|
13
|
-
hasher.update(chunk_type);
|
|
14
|
-
hasher.update(data);
|
|
15
|
-
writer.write_all(&hasher.finalize().to_be_bytes())?;
|
|
16
|
-
Ok(())
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
pub fn write_chunked_idat_bytes<W: Write>(writer: &mut W, data: &[u8]) -> anyhow::Result<()> {
|
|
20
|
-
write_chunked_idat_bytes_with_limit(writer, data, MAX_PNG_CHUNK_DATA_LEN)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
fn write_chunked_idat_bytes_with_limit<W: Write>(writer: &mut W, data: &[u8], max_chunk_len: usize) -> anyhow::Result<()> {
|
|
24
|
-
anyhow::ensure!(max_chunk_len > 0, "max_chunk_len must be > 0");
|
|
25
|
-
for chunk in data.chunks(max_chunk_len) {
|
|
26
|
-
write_png_chunk(writer, b"IDAT", chunk)?;
|
|
27
|
-
}
|
|
28
|
-
Ok(())
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
pub struct ChunkedIdatWriter<'a, W: Write> {
|
|
32
|
-
writer: &'a mut W,
|
|
33
|
-
buffer: Vec<u8>,
|
|
34
|
-
max_chunk_len: usize,
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
impl<'a, W: Write> ChunkedIdatWriter<'a, W> {
|
|
38
|
-
pub fn new(writer: &'a mut W) -> Self {
|
|
39
|
-
Self::with_max_chunk_len(writer, MAX_PNG_CHUNK_DATA_LEN)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
fn with_max_chunk_len(writer: &'a mut W, max_chunk_len: usize) -> Self {
|
|
43
|
-
Self {
|
|
44
|
-
writer,
|
|
45
|
-
buffer: Vec::with_capacity(max_chunk_len.max(1).min(MAX_PNG_CHUNK_DATA_LEN)),
|
|
46
|
-
max_chunk_len: max_chunk_len.max(1),
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
fn flush_chunk(&mut self) -> anyhow::Result<()> {
|
|
51
|
-
if self.buffer.is_empty() {
|
|
52
|
-
return Ok(());
|
|
53
|
-
}
|
|
54
|
-
write_png_chunk(self.writer, b"IDAT", &self.buffer)?;
|
|
55
|
-
self.buffer.clear();
|
|
56
|
-
Ok(())
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
pub fn finish(mut self) -> anyhow::Result<()> {
|
|
60
|
-
self.flush_chunk()
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
impl<W: Write> Write for ChunkedIdatWriter<'_, W> {
|
|
65
|
-
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
66
|
-
let mut offset = 0;
|
|
67
|
-
while offset < buf.len() {
|
|
68
|
-
if self.buffer.len() == self.max_chunk_len {
|
|
69
|
-
self.flush_chunk().map_err(io_error)?;
|
|
70
|
-
}
|
|
71
|
-
let space = self.max_chunk_len - self.buffer.len();
|
|
72
|
-
let take = space.min(buf.len() - offset);
|
|
73
|
-
self.buffer.extend_from_slice(&buf[offset..offset + take]);
|
|
74
|
-
offset += take;
|
|
75
|
-
}
|
|
76
|
-
Ok(buf.len())
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
fn flush(&mut self) -> io::Result<()> {
|
|
80
|
-
self.flush_chunk().map_err(io_error)?;
|
|
81
|
-
self.writer.flush()
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
fn io_error(err: anyhow::Error) -> io::Error {
|
|
86
|
-
io::Error::other(err.to_string())
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
#[cfg(test)]
|
|
90
|
-
mod tests {
|
|
91
|
-
use super::*;
|
|
92
|
-
|
|
93
|
-
#[test]
|
|
94
|
-
fn split_large_idat_stream_into_multiple_chunks() {
|
|
95
|
-
let mut png = Vec::new();
|
|
96
|
-
png.extend_from_slice(&[137, 80, 78, 71, 13, 10, 26, 10]);
|
|
97
|
-
|
|
98
|
-
let mut ihdr = [0u8; 13];
|
|
99
|
-
ihdr[0..4].copy_from_slice(&1u32.to_be_bytes());
|
|
100
|
-
ihdr[4..8].copy_from_slice(&1u32.to_be_bytes());
|
|
101
|
-
ihdr[8] = 8;
|
|
102
|
-
ihdr[9] = 2;
|
|
103
|
-
|
|
104
|
-
write_png_chunk(&mut png, b"IHDR", &ihdr).unwrap();
|
|
105
|
-
write_chunked_idat_bytes_with_limit(&mut png, &[1, 2, 3, 4, 5, 6, 7, 8, 9], 4).unwrap();
|
|
106
|
-
write_png_chunk(&mut png, b"IEND", &[]).unwrap();
|
|
107
|
-
|
|
108
|
-
let chunks = crate::png_utils::extract_png_chunks(&png).unwrap();
|
|
109
|
-
let idat_sizes: Vec<usize> = chunks.into_iter()
|
|
110
|
-
.filter(|chunk| chunk.name == "IDAT")
|
|
111
|
-
.map(|chunk| chunk.data.len())
|
|
112
|
-
.collect();
|
|
113
|
-
|
|
114
|
-
assert_eq!(idat_sizes, vec![4, 4, 1]);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
#[test]
|
|
118
|
-
fn chunked_idat_writer_flushes_multiple_chunks() {
|
|
119
|
-
let mut png = Vec::new();
|
|
120
|
-
png.extend_from_slice(&[137, 80, 78, 71, 13, 10, 26, 10]);
|
|
121
|
-
|
|
122
|
-
let mut ihdr = [0u8; 13];
|
|
123
|
-
ihdr[0..4].copy_from_slice(&1u32.to_be_bytes());
|
|
124
|
-
ihdr[4..8].copy_from_slice(&1u32.to_be_bytes());
|
|
125
|
-
ihdr[8] = 8;
|
|
126
|
-
ihdr[9] = 2;
|
|
127
|
-
|
|
128
|
-
write_png_chunk(&mut png, b"IHDR", &ihdr).unwrap();
|
|
129
|
-
{
|
|
130
|
-
let mut writer = ChunkedIdatWriter::with_max_chunk_len(&mut png, 3);
|
|
131
|
-
writer.write_all(&[1, 2]).unwrap();
|
|
132
|
-
writer.write_all(&[3, 4, 5]).unwrap();
|
|
133
|
-
writer.write_all(&[6, 7]).unwrap();
|
|
134
|
-
writer.finish().unwrap();
|
|
135
|
-
}
|
|
136
|
-
write_png_chunk(&mut png, b"IEND", &[]).unwrap();
|
|
137
|
-
|
|
138
|
-
let chunks = crate::png_utils::extract_png_chunks(&png).unwrap();
|
|
139
|
-
let idat_sizes: Vec<usize> = chunks.into_iter()
|
|
140
|
-
.filter(|chunk| chunk.name == "IDAT")
|
|
141
|
-
.map(|chunk| chunk.data.len())
|
|
142
|
-
.collect();
|
|
143
|
-
|
|
144
|
-
assert_eq!(idat_sizes, vec![3, 3, 1]);
|
|
145
|
-
}
|
|
146
|
-
}
|
package/native/png_utils.rs
DELETED
|
@@ -1,554 +0,0 @@
|
|
|
1
|
-
use bytemuck::{Pod, Zeroable};
|
|
2
|
-
use image::ImageReader;
|
|
3
|
-
use std::io::{Cursor, Read, Seek, SeekFrom};
|
|
4
|
-
|
|
5
|
-
#[repr(C)]
|
|
6
|
-
#[derive(Clone, Copy, Pod, Zeroable)]
|
|
7
|
-
struct PngSignature([u8; 8]);
|
|
8
|
-
|
|
9
|
-
const PNG_SIG: PngSignature = PngSignature([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
10
|
-
const HEADER_VERSION_V1: u8 = 1;
|
|
11
|
-
const HEADER_VERSION_V2: u8 = 2;
|
|
12
|
-
|
|
13
|
-
#[derive(Debug, Clone)]
|
|
14
|
-
pub struct PngChunk {
|
|
15
|
-
pub name: String,
|
|
16
|
-
pub data: Vec<u8>,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
fn read_u32_be(data: &[u8]) -> u32 {
|
|
20
|
-
u32::from_be_bytes([data[0], data[1], data[2], data[3]])
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
fn write_u32_be(val: u32) -> [u8; 4] {
|
|
24
|
-
val.to_be_bytes()
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
pub fn extract_png_chunks_streaming<R: Read + Seek>(reader: &mut R) -> Result<Vec<PngChunk>, String> {
|
|
28
|
-
let mut sig = [0u8; 8];
|
|
29
|
-
reader.read_exact(&mut sig).map_err(|e| format!("read sig: {}", e))?;
|
|
30
|
-
if sig != PNG_SIG.0 {
|
|
31
|
-
return Err("Invalid PNG signature".to_string());
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let mut chunks = Vec::new();
|
|
35
|
-
let mut header = [0u8; 8];
|
|
36
|
-
|
|
37
|
-
loop {
|
|
38
|
-
if reader.read_exact(&mut header).is_err() {
|
|
39
|
-
break;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
let length = u32::from_be_bytes([header[0], header[1], header[2], header[3]]) as usize;
|
|
43
|
-
let chunk_type = [header[4], header[5], header[6], header[7]];
|
|
44
|
-
let name = String::from_utf8_lossy(&chunk_type).to_string();
|
|
45
|
-
|
|
46
|
-
if name == "IDAT" {
|
|
47
|
-
reader.seek(SeekFrom::Current(length as i64 + 4)).map_err(|e| format!("seek: {}", e))?;
|
|
48
|
-
} else {
|
|
49
|
-
let mut data = vec![0u8; length];
|
|
50
|
-
reader.read_exact(&mut data).map_err(|e| format!("read chunk {}: {}", name, e))?;
|
|
51
|
-
reader.seek(SeekFrom::Current(4)).map_err(|e| format!("seek crc: {}", e))?;
|
|
52
|
-
chunks.push(PngChunk { name: name.clone(), data });
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if &chunk_type == b"IEND" {
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
Ok(chunks)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
pub fn extract_png_chunks(png_data: &[u8]) -> Result<Vec<PngChunk>, String> {
|
|
64
|
-
if png_data.len() < 8 || &png_data[..8] != &PNG_SIG.0 {
|
|
65
|
-
return Err("Invalid PNG signature".to_string());
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
let mut chunks = Vec::new();
|
|
69
|
-
let mut pos = 8;
|
|
70
|
-
|
|
71
|
-
while pos + 12 <= png_data.len() {
|
|
72
|
-
let length = read_u32_be(&png_data[pos..pos + 4]) as usize;
|
|
73
|
-
let chunk_type = &png_data[pos + 4..pos + 8];
|
|
74
|
-
|
|
75
|
-
if pos + 12 + length > png_data.len() {
|
|
76
|
-
break;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
let chunk_data = &png_data[pos + 8..pos + 8 + length];
|
|
80
|
-
|
|
81
|
-
let name = String::from_utf8_lossy(chunk_type).to_string();
|
|
82
|
-
chunks.push(PngChunk {
|
|
83
|
-
name,
|
|
84
|
-
data: chunk_data.to_vec(),
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
pos += 12 + length;
|
|
88
|
-
|
|
89
|
-
if chunk_type == b"IEND" {
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
Ok(chunks)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
pub fn encode_png_chunks(chunks: &[PngChunk]) -> Result<Vec<u8>, String> {
|
|
98
|
-
let mut output = Vec::new();
|
|
99
|
-
|
|
100
|
-
output.extend_from_slice(&PNG_SIG.0);
|
|
101
|
-
|
|
102
|
-
for chunk in chunks {
|
|
103
|
-
let chunk_type = chunk.name.as_bytes();
|
|
104
|
-
if chunk_type.len() != 4 {
|
|
105
|
-
return Err(format!("Invalid chunk type length: {}", chunk.name));
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
let length = chunk.data.len() as u32;
|
|
109
|
-
output.extend_from_slice(&write_u32_be(length));
|
|
110
|
-
output.extend_from_slice(chunk_type);
|
|
111
|
-
output.extend_from_slice(&chunk.data);
|
|
112
|
-
|
|
113
|
-
let mut crc_data = Vec::new();
|
|
114
|
-
crc_data.extend_from_slice(chunk_type);
|
|
115
|
-
crc_data.extend_from_slice(&chunk.data);
|
|
116
|
-
let crc = crate::core::crc32_bytes(&crc_data);
|
|
117
|
-
output.extend_from_slice(&write_u32_be(crc));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
Ok(output)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
pub fn get_png_metadata(png_data: &[u8]) -> Result<(u32, u32, u8, u8), String> {
|
|
124
|
-
let chunks = extract_png_chunks(png_data)?;
|
|
125
|
-
|
|
126
|
-
let ihdr = chunks.iter()
|
|
127
|
-
.find(|c| c.name == "IHDR")
|
|
128
|
-
.ok_or("IHDR chunk not found")?;
|
|
129
|
-
|
|
130
|
-
if ihdr.data.len() < 13 {
|
|
131
|
-
return Err("Invalid IHDR chunk".to_string());
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
let width = read_u32_be(&ihdr.data[0..4]);
|
|
135
|
-
let height = read_u32_be(&ihdr.data[4..8]);
|
|
136
|
-
let bit_depth = ihdr.data[8];
|
|
137
|
-
let color_type = ihdr.data[9];
|
|
138
|
-
|
|
139
|
-
Ok((width, height, bit_depth, color_type))
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
pub fn extract_payload_from_png(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
143
|
-
if let Ok(payload) = extract_payload_direct(png_data) {
|
|
144
|
-
if validate_payload_deep(&payload) {
|
|
145
|
-
return Ok(payload);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if let Ok(reconst) = crate::reconstitution::crop_and_reconstitute(png_data) {
|
|
149
|
-
if let Ok(payload) = extract_payload_direct(&reconst) {
|
|
150
|
-
if validate_payload_deep(&payload) {
|
|
151
|
-
return Ok(payload);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
if let Ok(unstretched) = crate::reconstitution::unstretch_nn(&reconst) {
|
|
155
|
-
if let Ok(payload) = extract_payload_direct(&unstretched) {
|
|
156
|
-
if validate_payload_deep(&payload) {
|
|
157
|
-
return Ok(payload);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
if let Ok(unstretched) = crate::reconstitution::unstretch_nn(png_data) {
|
|
163
|
-
if let Ok(payload) = extract_payload_direct(&unstretched) {
|
|
164
|
-
if validate_payload_deep(&payload) {
|
|
165
|
-
return Ok(payload);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
if let Ok(payload) = extract_payload_from_embedded_nn(png_data) {
|
|
170
|
-
if validate_payload_deep(&payload) {
|
|
171
|
-
return Ok(payload);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
Err("No valid payload found after all extraction attempts".to_string())
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
fn validate_payload_deep(payload: &[u8]) -> bool {
|
|
178
|
-
if payload.len() < 5 { return false; }
|
|
179
|
-
if payload[0] == 0x01 || payload[0] == 0x02 || payload[0] == 0x03 { return true; }
|
|
180
|
-
let compressed = if payload[0] == 0x00 { &payload[1..] } else { payload };
|
|
181
|
-
if compressed.starts_with(b"ROX1") { return true; }
|
|
182
|
-
crate::core::zstd_decompress_bytes(compressed, None).is_ok()
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
fn find_pixel_header(raw: &[u8]) -> Result<usize, String> {
|
|
186
|
-
let magic = b"PXL1";
|
|
187
|
-
for i in 0..(raw.len().saturating_sub(magic.len())) {
|
|
188
|
-
if &raw[i..i + magic.len()] == magic {
|
|
189
|
-
return Ok(i);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
Err("PIXEL_MAGIC not found".to_string())
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
fn decode_to_rgb(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
196
|
-
let mut reader = ImageReader::new(Cursor::new(png_data))
|
|
197
|
-
.with_guessed_format()
|
|
198
|
-
.map_err(|e| format!("format guess error: {}", e))?;
|
|
199
|
-
reader.no_limits();
|
|
200
|
-
let img = reader.decode().map_err(|e| format!("image decode error: {}", e))?;
|
|
201
|
-
Ok(img.to_rgb8().into_raw())
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
fn decode_to_rgba_grid(png_data: &[u8]) -> Result<(Vec<[u8; 4]>, u32, u32), String> {
|
|
205
|
-
let mut reader = ImageReader::new(Cursor::new(png_data))
|
|
206
|
-
.with_guessed_format()
|
|
207
|
-
.map_err(|e| format!("format guess error: {}", e))?;
|
|
208
|
-
reader.no_limits();
|
|
209
|
-
let img = reader.decode().map_err(|e| format!("image decode error: {}", e))?;
|
|
210
|
-
let rgba = img.to_rgba8();
|
|
211
|
-
let w = rgba.width();
|
|
212
|
-
let h = rgba.height();
|
|
213
|
-
let pixels: Vec<[u8; 4]> = rgba.pixels().map(|p| [p[0], p[1], p[2], p[3]]).collect();
|
|
214
|
-
Ok((pixels, w, h))
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
fn reconstruct_logical_pixels_from_nn(
|
|
218
|
-
pixels: &[[u8; 4]], width: u32, height: u32
|
|
219
|
-
) -> Result<Vec<u8>, String> {
|
|
220
|
-
let w = width as usize;
|
|
221
|
-
let h = height as usize;
|
|
222
|
-
let get = |x: usize, y: usize| -> [u8; 4] { pixels[y * w + x] };
|
|
223
|
-
|
|
224
|
-
let magic = [b'P', b'X', b'L', b'1'];
|
|
225
|
-
|
|
226
|
-
let mut header_row = None;
|
|
227
|
-
let mut header_col = None;
|
|
228
|
-
'outer: for y in 0..h {
|
|
229
|
-
for x in 0..w.saturating_sub(1) {
|
|
230
|
-
let p0 = get(x, y);
|
|
231
|
-
let p1 = get(x + 1, y);
|
|
232
|
-
let seq = [p0[0], p0[1], p0[2], p1[0], p1[1], p1[2]];
|
|
233
|
-
for start in 0..3 {
|
|
234
|
-
if start + 4 <= 6 && seq[start] == magic[0] && seq[start+1] == magic[1]
|
|
235
|
-
&& seq[start+2] == magic[2] && seq[start+3] == magic[3]
|
|
236
|
-
{
|
|
237
|
-
header_row = Some(y);
|
|
238
|
-
header_col = Some(x);
|
|
239
|
-
break 'outer;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
let header_row = header_row.ok_or("PXL1 not found in 2D pixel scan")?;
|
|
245
|
-
let header_col = header_col.ok_or("PXL1 column not found")?;
|
|
246
|
-
|
|
247
|
-
let mut scale_y = 1usize;
|
|
248
|
-
for dy in 1..h - header_row {
|
|
249
|
-
let y2 = header_row + dy;
|
|
250
|
-
let mut same = true;
|
|
251
|
-
for x in header_col..(header_col + 4).min(w) {
|
|
252
|
-
if get(x, y2) != get(x, header_row) { same = false; break; }
|
|
253
|
-
}
|
|
254
|
-
if same { scale_y += 1; } else { break; }
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
let cur = get(header_col, header_row);
|
|
258
|
-
let mut block_start = header_col;
|
|
259
|
-
while block_start > 0 && get(block_start - 1, header_row) == cur {
|
|
260
|
-
block_start -= 1;
|
|
261
|
-
}
|
|
262
|
-
let mut block_end = header_col + 1;
|
|
263
|
-
while block_end < w && get(block_end, header_row) == cur {
|
|
264
|
-
block_end += 1;
|
|
265
|
-
}
|
|
266
|
-
let scale_x = block_end - block_start;
|
|
267
|
-
if scale_x < 2 {
|
|
268
|
-
return Err("Could not determine NN scale_x".to_string());
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
let ref_y = header_row;
|
|
272
|
-
let mut embed_left = block_start;
|
|
273
|
-
loop {
|
|
274
|
-
if embed_left < scale_x { break; }
|
|
275
|
-
let candidate = embed_left - scale_x;
|
|
276
|
-
let c0 = get(candidate, ref_y);
|
|
277
|
-
let mut is_block = true;
|
|
278
|
-
for dx in 1..scale_x {
|
|
279
|
-
if candidate + dx >= w || get(candidate + dx, ref_y) != c0 {
|
|
280
|
-
is_block = false;
|
|
281
|
-
break;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
if !is_block { break; }
|
|
285
|
-
if candidate + scale_x < w && get(candidate + scale_x, ref_y) == c0 {
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
embed_left = candidate;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
let mut embed_top = header_row;
|
|
292
|
-
loop {
|
|
293
|
-
if embed_top < scale_y { break; }
|
|
294
|
-
let candidate = embed_top - scale_y;
|
|
295
|
-
let mut is_block = true;
|
|
296
|
-
for dy in 0..scale_y {
|
|
297
|
-
if candidate + dy >= h { is_block = false; break; }
|
|
298
|
-
if dy > 0 && get(embed_left, candidate + dy) != get(embed_left, candidate) {
|
|
299
|
-
is_block = false;
|
|
300
|
-
break;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
if !is_block { break; }
|
|
304
|
-
embed_top = candidate;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
let mut logical_cols: Vec<usize> = Vec::new();
|
|
308
|
-
let mut x = embed_left;
|
|
309
|
-
while x < w {
|
|
310
|
-
logical_cols.push(x);
|
|
311
|
-
let c = get(x, ref_y);
|
|
312
|
-
let mut nx = x + 1;
|
|
313
|
-
while nx < w && get(nx, ref_y) == c {
|
|
314
|
-
nx += 1;
|
|
315
|
-
}
|
|
316
|
-
if nx >= w { break; }
|
|
317
|
-
let blk = nx - x;
|
|
318
|
-
if blk < scale_x.saturating_sub(2) || blk > scale_x + 2 {
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
x = nx;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
let mut logical_rows: Vec<usize> = Vec::new();
|
|
325
|
-
let mut y = embed_top;
|
|
326
|
-
while y < h {
|
|
327
|
-
logical_rows.push(y);
|
|
328
|
-
let c = get(embed_left, y);
|
|
329
|
-
let mut ny = y + 1;
|
|
330
|
-
while ny < h && get(embed_left, ny) == c {
|
|
331
|
-
ny += 1;
|
|
332
|
-
}
|
|
333
|
-
if ny >= h { break; }
|
|
334
|
-
let blk = ny - y;
|
|
335
|
-
if blk < scale_y.saturating_sub(2) || blk > scale_y + 2 {
|
|
336
|
-
break;
|
|
337
|
-
}
|
|
338
|
-
y = ny;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
if logical_cols.len() < 3 || logical_rows.len() < 3 {
|
|
342
|
-
return Err("Embedded region too small".to_string());
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
let img_w = logical_cols.len();
|
|
346
|
-
let mut logical_rgb = Vec::with_capacity(img_w * logical_rows.len() * 3);
|
|
347
|
-
for &ry in &logical_rows {
|
|
348
|
-
for &cx in &logical_cols {
|
|
349
|
-
let p = get(cx, ry);
|
|
350
|
-
logical_rgb.push(p[0]);
|
|
351
|
-
logical_rgb.push(p[1]);
|
|
352
|
-
logical_rgb.push(p[2]);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
Ok(logical_rgb)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
fn extract_payload_from_embedded_nn(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
359
|
-
let (pixels, width, height) = decode_to_rgba_grid(png_data)?;
|
|
360
|
-
let logical_rgb = reconstruct_logical_pixels_from_nn(&pixels, width, height)?;
|
|
361
|
-
let pos = {
|
|
362
|
-
let magic = b"PXL1";
|
|
363
|
-
let mut found = None;
|
|
364
|
-
for i in 0..logical_rgb.len().saturating_sub(4) {
|
|
365
|
-
if &logical_rgb[i..i+4] == magic {
|
|
366
|
-
found = Some(i);
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
found.ok_or("PXL1 not found in reconstructed pixels")?
|
|
371
|
-
};
|
|
372
|
-
let header = parse_pixel_payload_header(&logical_rgb, pos)?;
|
|
373
|
-
let end = header.payload_offset + header.payload_len;
|
|
374
|
-
if end > logical_rgb.len() { return Err("Truncated payload in embedded NN".to_string()); }
|
|
375
|
-
Ok(logical_rgb[header.payload_offset..end].to_vec())
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
pub fn extract_name_from_png(png_data: &[u8]) -> Option<String> {
|
|
379
|
-
if let Some(name) = extract_name_direct(png_data) {
|
|
380
|
-
return Some(name);
|
|
381
|
-
}
|
|
382
|
-
if let Ok(reconst) = crate::reconstitution::crop_and_reconstitute(png_data) {
|
|
383
|
-
if let Some(name) = extract_name_direct(&reconst) {
|
|
384
|
-
return Some(name);
|
|
385
|
-
}
|
|
386
|
-
if let Ok(unstretched) = crate::reconstitution::unstretch_nn(&reconst) {
|
|
387
|
-
if let Some(name) = extract_name_direct(&unstretched) {
|
|
388
|
-
return Some(name);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
if let Ok(unstretched) = crate::reconstitution::unstretch_nn(png_data) {
|
|
393
|
-
if let Some(name) = extract_name_direct(&unstretched) {
|
|
394
|
-
return Some(name);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
if let Ok(name) = extract_name_from_embedded_nn(png_data) {
|
|
398
|
-
return Some(name);
|
|
399
|
-
}
|
|
400
|
-
None
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
fn extract_name_direct(png_data: &[u8]) -> Option<String> {
|
|
404
|
-
let raw = decode_to_rgb(png_data).ok()?;
|
|
405
|
-
let pos = find_pixel_header(&raw).ok()?;
|
|
406
|
-
let mut idx = pos + 4;
|
|
407
|
-
if idx + 2 > raw.len() { return None; }
|
|
408
|
-
idx += 1;
|
|
409
|
-
let name_len = raw[idx] as usize; idx += 1;
|
|
410
|
-
if name_len == 0 || idx + name_len > raw.len() { return None; }
|
|
411
|
-
String::from_utf8(raw[idx..idx + name_len].to_vec()).ok()
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
struct PixelPayloadHeader {
|
|
415
|
-
payload_offset: usize,
|
|
416
|
-
payload_len: usize,
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
fn parse_pixel_payload_header(buf: &[u8], pos: usize) -> Result<PixelPayloadHeader, String> {
|
|
420
|
-
let mut idx = pos + 4;
|
|
421
|
-
if idx + 2 > buf.len() {
|
|
422
|
-
return Err("Truncated header".to_string());
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
let version = buf[idx];
|
|
426
|
-
idx += 1;
|
|
427
|
-
let name_len = buf[idx] as usize;
|
|
428
|
-
idx += 1;
|
|
429
|
-
if idx + name_len > buf.len() {
|
|
430
|
-
return Err("Truncated name".to_string());
|
|
431
|
-
}
|
|
432
|
-
idx += name_len;
|
|
433
|
-
|
|
434
|
-
let payload_len = match version {
|
|
435
|
-
HEADER_VERSION_V1 => {
|
|
436
|
-
if idx + 4 > buf.len() {
|
|
437
|
-
return Err("Truncated payload length".to_string());
|
|
438
|
-
}
|
|
439
|
-
let len = u32::from_be_bytes([buf[idx], buf[idx + 1], buf[idx + 2], buf[idx + 3]]) as u64;
|
|
440
|
-
idx += 4;
|
|
441
|
-
len
|
|
442
|
-
}
|
|
443
|
-
HEADER_VERSION_V2 => {
|
|
444
|
-
if idx + 8 > buf.len() {
|
|
445
|
-
return Err("Truncated payload length64".to_string());
|
|
446
|
-
}
|
|
447
|
-
let len = u64::from_be_bytes([
|
|
448
|
-
buf[idx],
|
|
449
|
-
buf[idx + 1],
|
|
450
|
-
buf[idx + 2],
|
|
451
|
-
buf[idx + 3],
|
|
452
|
-
buf[idx + 4],
|
|
453
|
-
buf[idx + 5],
|
|
454
|
-
buf[idx + 6],
|
|
455
|
-
buf[idx + 7],
|
|
456
|
-
]);
|
|
457
|
-
idx += 8;
|
|
458
|
-
len
|
|
459
|
-
}
|
|
460
|
-
other => return Err(format!("Unsupported header version {}", other)),
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
let payload_len = usize::try_from(payload_len)
|
|
464
|
-
.map_err(|_| "Payload too large for this platform".to_string())?;
|
|
465
|
-
|
|
466
|
-
Ok(PixelPayloadHeader {
|
|
467
|
-
payload_offset: idx,
|
|
468
|
-
payload_len,
|
|
469
|
-
})
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
fn extract_name_from_embedded_nn(png_data: &[u8]) -> Result<String, String> {
|
|
473
|
-
let (pixels, width, height) = decode_to_rgba_grid(png_data)?;
|
|
474
|
-
let logical_rgb = reconstruct_logical_pixels_from_nn(&pixels, width, height)?;
|
|
475
|
-
let pos = find_pixel_header(&logical_rgb)?;
|
|
476
|
-
let mut idx = pos + 4;
|
|
477
|
-
if idx + 2 > logical_rgb.len() { return Err("Truncated".to_string()); }
|
|
478
|
-
idx += 1;
|
|
479
|
-
let name_len = logical_rgb[idx] as usize; idx += 1;
|
|
480
|
-
if name_len == 0 || idx + name_len > logical_rgb.len() { return Err("Truncated name".to_string()); }
|
|
481
|
-
String::from_utf8(logical_rgb[idx..idx + name_len].to_vec()).map_err(|e| e.to_string())
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
fn extract_payload_direct(png_data: &[u8]) -> Result<Vec<u8>, String> {
|
|
485
|
-
let raw = decode_to_rgb(png_data)?;
|
|
486
|
-
let pos = find_pixel_header(&raw)?;
|
|
487
|
-
let header = parse_pixel_payload_header(&raw, pos)?;
|
|
488
|
-
let end = header.payload_offset + header.payload_len;
|
|
489
|
-
if end > raw.len() { return Err("Truncated payload".to_string()); }
|
|
490
|
-
let payload = raw[header.payload_offset..end].to_vec();
|
|
491
|
-
Ok(payload)
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
pub fn extract_file_list_from_pixels(png_data: &[u8]) -> Result<String, String> {
|
|
495
|
-
if let Ok(result) = extract_file_list_direct(png_data) {
|
|
496
|
-
return Ok(result);
|
|
497
|
-
}
|
|
498
|
-
if let Ok(reconst) = crate::reconstitution::crop_and_reconstitute(png_data) {
|
|
499
|
-
if let Ok(result) = extract_file_list_direct(&reconst) {
|
|
500
|
-
return Ok(result);
|
|
501
|
-
}
|
|
502
|
-
if let Ok(unstretched) = crate::reconstitution::unstretch_nn(&reconst) {
|
|
503
|
-
if let Ok(result) = extract_file_list_direct(&unstretched) {
|
|
504
|
-
return Ok(result);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
if let Ok(unstretched) = crate::reconstitution::unstretch_nn(png_data) {
|
|
509
|
-
if let Ok(result) = extract_file_list_direct(&unstretched) {
|
|
510
|
-
return Ok(result);
|
|
511
|
-
}
|
|
512
|
-
}
|
|
513
|
-
if let Ok(result) = extract_file_list_from_embedded_nn(png_data) {
|
|
514
|
-
return Ok(result);
|
|
515
|
-
}
|
|
516
|
-
Err("No file list found after all extraction attempts".to_string())
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
fn extract_file_list_from_embedded_nn(png_data: &[u8]) -> Result<String, String> {
|
|
520
|
-
let (pixels, width, height) = decode_to_rgba_grid(png_data)?;
|
|
521
|
-
let logical_rgb = reconstruct_logical_pixels_from_nn(&pixels, width, height)?;
|
|
522
|
-
let pos = find_pixel_header(&logical_rgb)?;
|
|
523
|
-
let header = parse_pixel_payload_header(&logical_rgb, pos)?;
|
|
524
|
-
let mut idx = header.payload_offset + header.payload_len;
|
|
525
|
-
if idx + 8 > logical_rgb.len() { return Err("No file list in embedded NN".to_string()); }
|
|
526
|
-
if &logical_rgb[idx..idx + 4] != b"rXFL" { return Err("No rXFL marker in embedded NN".to_string()); }
|
|
527
|
-
idx += 4;
|
|
528
|
-
let json_len = ((logical_rgb[idx] as u32) << 24)
|
|
529
|
-
| ((logical_rgb[idx+1] as u32) << 16)
|
|
530
|
-
| ((logical_rgb[idx+2] as u32) << 8)
|
|
531
|
-
| (logical_rgb[idx+3] as u32);
|
|
532
|
-
idx += 4;
|
|
533
|
-
let json_end = idx + json_len as usize;
|
|
534
|
-
if json_end > logical_rgb.len() { return Err("Truncated file list in embedded NN".to_string()); }
|
|
535
|
-
String::from_utf8(logical_rgb[idx..json_end].to_vec()).map_err(|e| format!("Invalid UTF-8: {}", e))
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
fn extract_file_list_direct(png_data: &[u8]) -> Result<String, String> {
|
|
539
|
-
let raw = decode_to_rgb(png_data)?;
|
|
540
|
-
let pos = find_pixel_header(&raw)?;
|
|
541
|
-
let header = parse_pixel_payload_header(&raw, pos)?;
|
|
542
|
-
let mut idx = header.payload_offset + header.payload_len;
|
|
543
|
-
if idx + 8 > raw.len() { return Err("No file list in pixel data".to_string()); }
|
|
544
|
-
if &raw[idx..idx + 4] != b"rXFL" { return Err("No rXFL marker in pixel data".to_string()); }
|
|
545
|
-
idx += 4;
|
|
546
|
-
let json_len = ((raw[idx] as u32) << 24)
|
|
547
|
-
| ((raw[idx+1] as u32) << 16)
|
|
548
|
-
| ((raw[idx+2] as u32) << 8)
|
|
549
|
-
| (raw[idx+3] as u32);
|
|
550
|
-
idx += 4;
|
|
551
|
-
let json_end = idx + json_len as usize;
|
|
552
|
-
if json_end > raw.len() { return Err("Truncated file list JSON".to_string()); }
|
|
553
|
-
String::from_utf8(raw[idx..json_end].to_vec()).map_err(|e| format!("Invalid UTF-8 in file list: {}", e))
|
|
554
|
-
}
|