220 lines
8.7 KiB
Rust
220 lines
8.7 KiB
Rust
|
|
#![allow(dead_code, unused_imports, unused_variables, unused_macros, unreachable_patterns)]
|
||
|
|
|
||
|
|
fn main() {
|
||
|
|
let orig_bytes = std::fs::read("_images/_psd_stil_test/inner_shadow/black_triangle_green_inner_shadow_normal_blend.psd").unwrap();
|
||
|
|
let saved_bytes = std::fs::read("_tmp/roundtrip_output.psd").unwrap();
|
||
|
|
|
||
|
|
// Extract lfx2 block from Layer 1 (Black Triangle) of both files
|
||
|
|
println!("=== LFX2 BLOCK COMPARISON ===\n");
|
||
|
|
|
||
|
|
let orig_lfx2 = extract_block(&orig_bytes, "lfx2", 1);
|
||
|
|
let saved_lfx2 = extract_block(&saved_bytes, "lfx2", 1);
|
||
|
|
|
||
|
|
match (&orig_lfx2, &saved_lfx2) {
|
||
|
|
(Some(o), Some(s)) => {
|
||
|
|
println!("ORIG lfx2: {} bytes", o.len());
|
||
|
|
println!("SAVED lfx2: {} bytes", s.len());
|
||
|
|
println!("Diff: {} bytes", o.len() as i64 - s.len() as i64);
|
||
|
|
|
||
|
|
// Byte-by-byte diff
|
||
|
|
let min_len = o.len().min(s.len());
|
||
|
|
for i in 0..min_len {
|
||
|
|
if o[i] != s[i] {
|
||
|
|
println!(" FIRST DIFF at byte {}: orig=0x{:02X} saved=0x{:02X}", i, o[i], s[i]);
|
||
|
|
let start = i.saturating_sub(16);
|
||
|
|
let end = (i + 48).min(min_len);
|
||
|
|
println!(" ORIG : {}", hex_dump(&o[start..end]));
|
||
|
|
println!(" SAVED: {}", hex_dump(&s[start..end]));
|
||
|
|
|
||
|
|
// Also show as much descriptor text as possible
|
||
|
|
println!("\n ORIG context (text): {}", text_view(&o[start..end]));
|
||
|
|
println!(" SAVED context (text): {}", text_view(&s[start..end]));
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if o.len() != s.len() {
|
||
|
|
// Show last 32 bytes of each
|
||
|
|
println!("\n ORIG tail (last 32): {}", hex_dump(&o[o.len().saturating_sub(32)..]));
|
||
|
|
println!(" SAVED tail (last 32): {}", hex_dump(&s[s.len().saturating_sub(32)..]));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
(Some(o), None) => println!("ORIG has lfx2 ({} bytes), SAVED missing", o.len()),
|
||
|
|
(None, Some(s)) => println!("SAVED has lfx2 ({} bytes), ORIG missing", s.len()),
|
||
|
|
(None, None) => println!("Neither has lfx2"),
|
||
|
|
}
|
||
|
|
|
||
|
|
// Extract shmd block from Layer 1
|
||
|
|
println!("\n=== SHMD BLOCK COMPARISON ===\n");
|
||
|
|
let orig_shmd = extract_block(&orig_bytes, "shmd", 1);
|
||
|
|
let saved_shmd = extract_block(&saved_bytes, "shmd", 1);
|
||
|
|
|
||
|
|
match (&orig_shmd, &saved_shmd) {
|
||
|
|
(Some(o), Some(s)) => {
|
||
|
|
println!("ORIG shmd: {} bytes", o.len());
|
||
|
|
println!("SAVED shmd: {} bytes", s.len());
|
||
|
|
|
||
|
|
let min_len = o.len().min(s.len());
|
||
|
|
for i in 0..min_len {
|
||
|
|
if o[i] != s[i] {
|
||
|
|
println!(" FIRST DIFF at byte {}: orig=0x{:02X} saved=0x{:02X}", i, o[i], s[i]);
|
||
|
|
let start = i.saturating_sub(16);
|
||
|
|
let end = (i + 48).min(min_len);
|
||
|
|
println!(" ORIG : {}", hex_dump(&o[start..end]));
|
||
|
|
println!(" SAVED: {}", hex_dump(&s[start..end]));
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_ => println!("Missing shmd in one or both"),
|
||
|
|
}
|
||
|
|
|
||
|
|
// Extract full Layer 1 extra data
|
||
|
|
println!("\n=== LAYER 1 FULL EXTRA DATA COMPARISON ===\n");
|
||
|
|
let orig_extra = extract_layer_extra(&orig_bytes, 1);
|
||
|
|
let saved_extra = extract_layer_extra(&saved_bytes, 1);
|
||
|
|
|
||
|
|
match (&orig_extra, &saved_extra) {
|
||
|
|
(Some(o), Some(s)) => {
|
||
|
|
println!("ORIG extra: {} bytes", o.len());
|
||
|
|
dump_extra_blocks("ORIG", o);
|
||
|
|
println!("\nSAVED extra: {} bytes", s.len());
|
||
|
|
dump_extra_blocks("SAVED", s);
|
||
|
|
}
|
||
|
|
_ => println!("Could not extract extra data"),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn extract_block(bytes: &[u8], key: &str, _layer_idx: usize) -> Option<Vec<u8>> {
|
||
|
|
let lmi = get_lmi(bytes)?;
|
||
|
|
let li_len = u32::from_be_bytes([lmi[0], lmi[1], lmi[2], lmi[3]]) as usize;
|
||
|
|
let li = &lmi[4..4+li_len];
|
||
|
|
let layer_count = i16::from_be_bytes([li[0], li[1]]).unsigned_abs() as usize;
|
||
|
|
|
||
|
|
let mut pos = 2;
|
||
|
|
for _ in 0..layer_count {
|
||
|
|
if pos + 18 > li.len() { return None; }
|
||
|
|
let ch_count = u16::from_be_bytes([li[pos+16], li[pos+17]]) as usize;
|
||
|
|
pos += 18;
|
||
|
|
pos += ch_count * 6;
|
||
|
|
if pos + 12 > li.len() { return None; }
|
||
|
|
pos += 12; // blend+opacity+clipping+flags+filler
|
||
|
|
let extra_len = u32::from_be_bytes([li[pos], li[pos+1], li[pos+2], li[pos+3]]) as usize;
|
||
|
|
pos += 4;
|
||
|
|
let _extra_start = pos;
|
||
|
|
let extra_end = pos + extra_len;
|
||
|
|
|
||
|
|
// Skip mask and blending ranges
|
||
|
|
if pos + 4 > extra_end { return None; }
|
||
|
|
let mask_len = u32::from_be_bytes([li[pos], li[pos+1], li[pos+2], li[pos+3]]) as usize;
|
||
|
|
pos += 4 + mask_len;
|
||
|
|
if pos + 4 > extra_end { return None; }
|
||
|
|
let blend_len = u32::from_be_bytes([li[pos], li[pos+1], li[pos+2], li[pos+3]]) as usize;
|
||
|
|
pos += 4 + blend_len;
|
||
|
|
// Skip name
|
||
|
|
if pos >= extra_end { return None; }
|
||
|
|
let name_len = li[pos] as usize;
|
||
|
|
pos += 1 + name_len;
|
||
|
|
let bytes_mod_4 = (name_len + 1) % 4;
|
||
|
|
pos += (4 - bytes_mod_4) % 4;
|
||
|
|
|
||
|
|
// Scan for target block
|
||
|
|
while pos + 8 <= extra_end {
|
||
|
|
let sig = &li[pos..pos+4];
|
||
|
|
if sig != b"8BIM" && sig != b"8B64" { break; }
|
||
|
|
let block_key = String::from_utf8_lossy(&li[pos+4..pos+8]);
|
||
|
|
let block_len = u32::from_be_bytes([li[pos+8], li[pos+9], li[pos+10], li[pos+11]]) as usize;
|
||
|
|
if block_key == key {
|
||
|
|
return Some(li[pos+12..pos+12+block_len].to_vec());
|
||
|
|
}
|
||
|
|
pos += 12 + block_len;
|
||
|
|
if block_len % 2 != 0 { pos += 1; }
|
||
|
|
}
|
||
|
|
|
||
|
|
pos = extra_end;
|
||
|
|
}
|
||
|
|
None
|
||
|
|
}
|
||
|
|
|
||
|
|
fn extract_layer_extra(bytes: &[u8], layer_idx: usize) -> Option<Vec<u8>> {
|
||
|
|
let lmi = get_lmi(bytes)?;
|
||
|
|
let li_len = u32::from_be_bytes([lmi[0], lmi[1], lmi[2], lmi[3]]) as usize;
|
||
|
|
let li = &lmi[4..4+li_len];
|
||
|
|
let layer_count = i16::from_be_bytes([li[0], li[1]]).unsigned_abs() as usize;
|
||
|
|
|
||
|
|
let mut pos = 2;
|
||
|
|
for i in 0..layer_count {
|
||
|
|
if pos + 18 > li.len() { return None; }
|
||
|
|
let ch_count = u16::from_be_bytes([li[pos+16], li[pos+17]]) as usize;
|
||
|
|
pos += 18;
|
||
|
|
pos += ch_count * 6;
|
||
|
|
if pos + 12 > li.len() { return None; }
|
||
|
|
pos += 12;
|
||
|
|
let extra_len = u32::from_be_bytes([li[pos], li[pos+1], li[pos+2], li[pos+3]]) as usize;
|
||
|
|
pos += 4;
|
||
|
|
let extra_start = pos;
|
||
|
|
|
||
|
|
if i == layer_idx {
|
||
|
|
return Some(li[extra_start..extra_start+extra_len].to_vec());
|
||
|
|
}
|
||
|
|
pos += extra_len;
|
||
|
|
}
|
||
|
|
None
|
||
|
|
}
|
||
|
|
|
||
|
|
fn dump_extra_blocks(_label: &str, extra: &[u8]) {
|
||
|
|
let mut pos = 0;
|
||
|
|
// Skip mask
|
||
|
|
if pos + 4 > extra.len() { return; }
|
||
|
|
let mask_len = u32::from_be_bytes([extra[pos], extra[pos+1], extra[pos+2], extra[pos+3]]) as usize;
|
||
|
|
println!(" mask_data: {} bytes", mask_len);
|
||
|
|
pos += 4 + mask_len;
|
||
|
|
|
||
|
|
// Skip blending ranges
|
||
|
|
if pos + 4 > extra.len() { return; }
|
||
|
|
let blend_len = u32::from_be_bytes([extra[pos], extra[pos+1], extra[pos+2], extra[pos+3]]) as usize;
|
||
|
|
println!(" blend_ranges: {} bytes", blend_len);
|
||
|
|
pos += 4 + blend_len;
|
||
|
|
|
||
|
|
// Name
|
||
|
|
if pos >= extra.len() { return; }
|
||
|
|
let name_len = extra[pos] as usize;
|
||
|
|
pos += 1;
|
||
|
|
if pos + name_len > extra.len() { return; }
|
||
|
|
let name = String::from_utf8_lossy(&extra[pos..pos+name_len]);
|
||
|
|
println!(" name: '{}' ({} bytes + padding)", name, name_len);
|
||
|
|
pos += name_len;
|
||
|
|
let bytes_mod_4 = (name_len + 1) % 4;
|
||
|
|
pos += (4 - bytes_mod_4) % 4;
|
||
|
|
|
||
|
|
// Tagged blocks
|
||
|
|
while pos + 8 <= extra.len() {
|
||
|
|
let sig = &extra[pos..pos+4];
|
||
|
|
if sig != b"8BIM" && sig != b"8B64" {
|
||
|
|
println!(" BAD SIG at {}: {:02X?}", pos, &extra[pos..pos+4.min(extra.len()-pos)]);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
let key = String::from_utf8_lossy(&extra[pos+4..pos+8]);
|
||
|
|
let block_len = u32::from_be_bytes([extra[pos+8], extra[pos+9], extra[pos+10], extra[pos+11]]) as usize;
|
||
|
|
println!(" block '{}' len={} @{}", key, block_len, pos);
|
||
|
|
pos += 12 + block_len;
|
||
|
|
if block_len % 2 != 0 { pos += 1; }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fn get_lmi(bytes: &[u8]) -> Option<&[u8]> {
|
||
|
|
let cmd_len = u32::from_be_bytes([bytes[26], bytes[27], bytes[28], bytes[29]]) as usize;
|
||
|
|
let ir_start = 30 + cmd_len;
|
||
|
|
let ir_len = u32::from_be_bytes([bytes[ir_start], bytes[ir_start+1], bytes[ir_start+2], bytes[ir_start+3]]) as usize;
|
||
|
|
let lmi_start = ir_start + 4 + ir_len;
|
||
|
|
let lmi_len = u32::from_be_bytes([bytes[lmi_start], bytes[lmi_start+1], bytes[lmi_start+2], bytes[lmi_start+3]]) as usize;
|
||
|
|
Some(&bytes[lmi_start+4..lmi_start+4+lmi_len])
|
||
|
|
}
|
||
|
|
|
||
|
|
fn hex_dump(data: &[u8]) -> String {
|
||
|
|
data.iter().take(64).map(|b| format!("{:02X}", b)).collect::<Vec<_>>().join(" ")
|
||
|
|
}
|
||
|
|
|
||
|
|
fn text_view(data: &[u8]) -> String {
|
||
|
|
data.iter().map(|&b| if b >= 0x20 && b < 0x7F { b as char } else { '.' }).collect()
|
||
|
|
}
|