76 lines
2.6 KiB
Python
76 lines
2.6 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Decode the composite block and first layer block from a pm4n file,
|
||
report pixel counts and first/last pixel values.
|
||
Run: python3 decode_rle_blocks.py output/pm4n/Flow_Controller_Panel-Front.pm4n
|
||
"""
|
||
import struct, sys
|
||
|
||
path = sys.argv[1]
|
||
data = open(path, 'rb').read()
|
||
fsize = len(data)
|
||
print(f"File: {path} ({fsize} bytes)\n")
|
||
|
||
LAYE_HDR = 0x20
|
||
STRIDE = 0x20
|
||
|
||
laye = data.find(b'LAYE')
|
||
n_entries = struct.unpack_from('<I', data, laye + 0x10)[0]
|
||
composite_off = struct.unpack_from('<I', data, laye + 0x14)[0]
|
||
block_size = struct.unpack_from('<I', data, laye + 0x18)[0]
|
||
|
||
print(f"LAYE n_entries={n_entries} composite_off=0x{composite_off:06X} block_size={block_size}")
|
||
|
||
def decode_rle(data, offset, max_bytes):
|
||
"""Decode RLE starting at offset, stop after max_bytes read. Returns pixel list."""
|
||
pixels = []
|
||
i = offset
|
||
end = min(offset + max_bytes, len(data))
|
||
while i + 1 < end:
|
||
b0 = data[i]; b1 = data[i+1]
|
||
nibble = (b0 >> 4) & 0xF
|
||
colour = 255 if nibble == 0xF else 0
|
||
run = ((b0 & 0x0F) << 8) | b1
|
||
run += 1
|
||
pixels.extend([colour] * run)
|
||
i += 2
|
||
return pixels, i - offset
|
||
|
||
def summarize_block(name, offset, size):
|
||
print(f"\n{name} at 0x{offset:06X}, {size} bytes:")
|
||
if offset + size > fsize:
|
||
print(f" *** PAST EOF (file ends at 0x{fsize:06X}) ***")
|
||
size = max(0, fsize - offset)
|
||
|
||
# Show first 16 bytes raw
|
||
raw = data[offset:offset+16]
|
||
print(f" First 16 bytes: {raw.hex()}")
|
||
|
||
# Decode RLE
|
||
pixels, bytes_consumed = decode_rle(data, offset, size)
|
||
print(f" Decoded: {len(pixels)} pixels from {bytes_consumed} bytes")
|
||
print(f" Expected: {9024*5120} pixels ({9024}×{5120})")
|
||
|
||
if pixels:
|
||
whites = sum(1 for p in pixels if p >= 128)
|
||
blacks = len(pixels) - whites
|
||
print(f" White px: {whites} ({100*whites/len(pixels):.1f}%)")
|
||
print(f" Black px: {blacks} ({100*blacks/len(pixels):.1f}%)")
|
||
|
||
# Check if it decodes to correct pixel count
|
||
if len(pixels) == 9024 * 5120:
|
||
print(f" ✓ Correct pixel count")
|
||
else:
|
||
print(f" *** WRONG pixel count (off by {len(pixels) - 9024*5120})")
|
||
|
||
# Show first few runs
|
||
print(f" First pixels: {pixels[:20]}")
|
||
|
||
# Composite block
|
||
summarize_block("Composite block (block 0)", composite_off, block_size)
|
||
|
||
# First entry block
|
||
e0_base = laye + LAYE_HDR
|
||
e0_data_off = struct.unpack_from('<I', data, e0_base + 0x14)[0]
|
||
e0_data_sz = struct.unpack_from('<I', data, e0_base + 0x18)[0]
|
||
summarize_block("Entry[0] block", e0_data_off, e0_data_sz) |