#![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> { 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> { 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::>().join(" ") } fn text_view(data: &[u8]) -> String { data.iter().map(|&b| if b >= 0x20 && b < 0x7F { b as char } else { '.' }).collect() }