109 lines
4.6 KiB
Rust
109 lines
4.6 KiB
Rust
#![allow(dead_code, unused_imports, unused_variables, unused_macros, unreachable_patterns, unused_assignments, clippy::cfg)]
|
|
fn main() {
|
|
let path = "/home/hc/Pictures/Forest Text Effect/Forest-Text-mini.psd";
|
|
let bytes = std::fs::read(path).unwrap();
|
|
|
|
let mut pos = 26;
|
|
let color_mode_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
|
|
pos += 4 + color_mode_len;
|
|
let img_res_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
|
|
pos += 4 + img_res_len;
|
|
let _layer_mask_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
|
|
pos += 4;
|
|
let _layer_info_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
|
|
pos += 4;
|
|
let layer_count = i16::from_be_bytes([bytes[pos], bytes[pos+1]]).abs() as usize;
|
|
pos += 2;
|
|
|
|
for _ in 0..layer_count {
|
|
pos += 16; // bounds
|
|
let channel_count = u16::from_be_bytes([bytes[pos], bytes[pos+1]]) as usize;
|
|
pos += 2 + channel_count * 6;
|
|
pos += 4; // blend sig
|
|
pos += 4; // blend key
|
|
pos += 1; // opacity
|
|
pos += 1; // clipping
|
|
pos += 1; // flags
|
|
pos += 1; // filler
|
|
let extra_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
|
|
pos += 4;
|
|
let extra_start = pos;
|
|
let mask_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
|
|
pos += 4 + mask_len;
|
|
let blend_range_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
|
|
pos += 4 + blend_range_len;
|
|
let name_len = bytes[pos] as usize;
|
|
pos += 1 + name_len;
|
|
let pad = (4 - ((name_len + 1) % 4)) % 4;
|
|
pos += pad;
|
|
|
|
let mut layer_name = String::new();
|
|
let mut has_lfx2 = false;
|
|
let mut has_lrfx = false;
|
|
let mut lfx2_offset = 0usize;
|
|
let mut lfx2_len = 0usize;
|
|
|
|
while pos < extra_start + extra_len {
|
|
let sig = &bytes[pos..pos+4];
|
|
if sig != b"8BIM" && sig != b"8B64" { break; }
|
|
pos += 4;
|
|
let key = &bytes[pos..pos+4];
|
|
pos += 4;
|
|
let block_len = u32::from_be_bytes([bytes[pos], bytes[pos+1], bytes[pos+2], bytes[pos+3]]) as usize;
|
|
pos += 4;
|
|
let block_start = pos;
|
|
let block_data = &bytes[pos..pos+block_len];
|
|
pos += block_len;
|
|
if block_len % 2 != 0 { pos += 1; }
|
|
|
|
let key_str = String::from_utf8_lossy(key);
|
|
if key_str == "lfx2" {
|
|
has_lfx2 = true;
|
|
lfx2_offset = block_start;
|
|
lfx2_len = block_len;
|
|
} else if key_str == "lrFX" {
|
|
has_lrfx = true;
|
|
} else if key_str == "TySh" || key_str == "luni" {
|
|
// unicode name
|
|
if block_data.len() >= 4 {
|
|
let char_count = u32::from_be_bytes([block_data[0], block_data[1], block_data[2], block_data[3]]) as usize;
|
|
if block_data.len() >= 4 + char_count * 2 {
|
|
let mut s = String::new();
|
|
for i in 0..char_count {
|
|
let c = u16::from_be_bytes([block_data[4 + i*2], block_data[5 + i*2]]);
|
|
if let Some(ch) = std::char::from_u32(c as u32) {
|
|
s.push(ch);
|
|
}
|
|
}
|
|
layer_name = s;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if has_lfx2 && (layer_name == "text" || layer_name.contains("text")) {
|
|
// Search for GrFl in the lfx2 block
|
|
let lfx2_data = &bytes[lfx2_offset..lfx2_offset+lfx2_len];
|
|
println!("\n=== Layer '{}' lfx2 block (len={}) ===", layer_name, lfx2_len);
|
|
let keys = [b"DrSh", b"IrSh", b"OrGl", b"IrGl", b"ebbl", b"lagl", b"SoFi", b"GrFl", b"PtFl", b"FrFX", b"ChFX"];
|
|
for key in &keys {
|
|
let mut found = false;
|
|
let mut offsets = Vec::new();
|
|
for i in 0..lfx2_data.len().saturating_sub(3) {
|
|
if &lfx2_data[i..i+4] == *key {
|
|
found = true;
|
|
offsets.push(i);
|
|
if offsets.len() > 5 { break; }
|
|
}
|
|
}
|
|
if found {
|
|
println!(" {}: found at offsets {:?}", std::str::from_utf8(*key).unwrap(), offsets);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Skip channel data (can't easily without dims, break)
|
|
break;
|
|
}
|
|
}
|