72 lines
2.6 KiB
Python
72 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Decode the PREV (preview thumbnail) section from a pm4n file and save as PNG.
|
|
Run: python3 decode_prev.py <file.pm4n>
|
|
"""
|
|
import struct, sys
|
|
from pathlib import Path
|
|
|
|
path = sys.argv[1]
|
|
data = open(path, 'rb').read()
|
|
|
|
prev_off = data.find(b'PREV')
|
|
print(f"PREV at 0x{prev_off:06X}")
|
|
|
|
# PREV section layout (from ANYCUBIC format docs and common reverse-engineering):
|
|
# +0x00 "PREV"
|
|
# +0x04 u32 section_length
|
|
# +0x08 u32 image_width
|
|
# +0x0C u32 image_height
|
|
# +0x10 u32 image_data_length
|
|
# +0x14 image data (RGB565 or RLE)
|
|
|
|
for hdr_size in [0x14, 0x18, 0x10, 0x0C]:
|
|
w = struct.unpack_from('<I', data, prev_off + 0x08)[0]
|
|
h = struct.unpack_from('<I', data, prev_off + 0x0C)[0]
|
|
dlen = struct.unpack_from('<I', data, prev_off + 0x10)[0]
|
|
print(f" PREV+0x08: w={w}, h={h}, dlen={dlen}")
|
|
if 100 < w < 2000 and 100 < h < 2000:
|
|
print(f" -> plausible dimensions {w}x{h}, data at +0x14, {dlen} bytes")
|
|
break
|
|
|
|
# Show first 64 bytes of PREV section
|
|
print(f"\nPREV raw (first 64 bytes):")
|
|
chunk = data[prev_off:prev_off+64]
|
|
for i in range(0, 64, 16):
|
|
row = chunk[i:i+16]
|
|
hex_p = ' '.join(f'{b:02X}' for b in row)
|
|
u_vals = [struct.unpack_from('<I', row, j)[0] for j in range(0, min(16,len(row))-3, 4)]
|
|
print(f" +0x{i:02X} {hex_p:<48} {u_vals}")
|
|
|
|
# Try to decode as RGB565 (common for ANYCUBIC previews)
|
|
# Each pixel = 2 bytes, little-endian RGB565
|
|
# R = bits[15:11], G = bits[10:5], B = bits[4:0]
|
|
img_data_off = prev_off + 0x14
|
|
img_data_len = struct.unpack_from('<I', data, prev_off + 0x10)[0]
|
|
w = struct.unpack_from('<I', data, prev_off + 0x08)[0]
|
|
h = struct.unpack_from('<I', data, prev_off + 0x0C)[0]
|
|
|
|
print(f"\nAttempting RGB565 decode: {w}x{h}, {img_data_len} bytes at 0x{img_data_off:06X}")
|
|
if 0 < w < 2000 and 0 < h < 2000 and img_data_len == w * h * 2:
|
|
print(f" Size matches RGB565 ({w}*{h}*2={w*h*2}) ✓")
|
|
try:
|
|
from PIL import Image
|
|
pixels = []
|
|
raw = data[img_data_off:img_data_off + img_data_len]
|
|
for i in range(0, len(raw)-1, 2):
|
|
px = raw[i] | (raw[i+1] << 8)
|
|
r = ((px >> 11) & 0x1F) << 3
|
|
g = ((px >> 5) & 0x3F) << 2
|
|
b = (px & 0x1F) << 3
|
|
pixels.append((r, g, b))
|
|
img = Image.new('RGB', (w, h))
|
|
img.putdata(pixels)
|
|
out = Path(path).with_suffix('.prev_thumb.png')
|
|
img.save(out)
|
|
print(f" Saved thumbnail to {out}")
|
|
except Exception as e:
|
|
print(f" Error: {e}")
|
|
else:
|
|
print(f" Size mismatch or bad dims, skipping decode")
|
|
print(f" Expected {w*h*2} bytes for RGB565, got {img_data_len}")
|