#!/usr/bin/env python3 """Finaler PCC-Decoder fuer Kellogg's Tony and Friends (2026-07-22, ARIA). Format (verifiziert gegen MENU.PCC/RAUSER1-3/FACTOR5, visuell + byte-exakt): - Byte 0-15: echter PCX-Header-Anfang (Manufacturer=0x0A, Version=5, Encoding=1/RLE, BPP=8, dann Xmin/Ymin/Xmax/Ymax als LE16 bei Offset 4-11). WICHTIG (Bugfix 2026-07-22): Width/Height MUESSEN aus Xmax-Xmin+1 / Ymax-Ymin+1 berechnet werden. Die 2 LE16-Werte bei Offset 12-15 sehen fuer Vollbild-Screens (320x200) zufaellig identisch aus und wurden erst dafuer gehalten -- sind aber tatsaechlich NICHT die Bilddimensionen (vermutlich ein DPI/Reserved-Feld wie im echten 128-Byte-PCX-Header), sondern ein Konstantwert der bei kleinen Sprites/Logos (z.B. RAUSER1.PCC: 182x46, FAC0.PCC: 16x16) komplett falsch war und zu kaputten/leeren Bildern fuehrte. Mit dem Xmax/Ymax-Fix konsumiert z.B. FACTOR5.PCC exakt 100% der komprimierten Bytes (51609/51609) statt vorher mit falscher Hoehe (200 statt echten 199) zu ueberlaufen. - Byte 16 .. (len-769): RLE-komprimierte Pixel-Indexdaten, SCANLINE-weise decodiert (pro Zeile wird bis width Pixel gefuellt, ueberschuessige Pixel eines Runs am Zeilenende werden verworfen -- klassisches PCX-Verhalten). RLE-Tupel: Byte mit oberen 2 Bits gesetzt (0xC0-0xFF) = Lauflaenge (&0x3F), gefolgt von einem Wert-Byte. Sonst literaler Pixel. - Letzte 769 Bytes: 0x0C-Marker + 768 Byte (256 x RGB) eingebettete Palette (klassische PCX-v5-256-Farben-Erweiterung). PRO DATEI eigene Palette, keine globale Palette noetig. BEKANNTER OFFENER BUG (Stand 2026-07-22, noch nicht geloest): KELLOGGS.PCC und KARTE.PCC (volle 320x200-Screens) zeigen weiterhin einen lokalen Deko-Fehler (ein "Kerbe"/Notch-Artefakt rechts neben dem Kellogg's- Schriftzug, roter/gelber Fleck der nicht ins Referenz-Screenshot passt). Ausgeschlossen als Ursache: (a) horizontales Rollen/Verschieben des ganzen Bilds -- getestet mit 18 Shift-Kandidaten, Artefakt bleibt IMMER an derselben Position relativ zum Bildinhalt, nicht zur Leinwand -> kein Rotations-/Scroll-Bug. (b) Zeilenweises Verwerfen von RLE-Overshoot -- zeilenbasierte und "kontinuierliche" (ohne Row-Reset) Decodierung liefern BYTE-IDENTISCHE Ergebnisse fuer diese Dateien. Vermutung: entweder eine Palette-Fehlzuordnung fuer einzelne Indizes, oder eine RLE-Ambiguitaet bei literalen Pixelwerten >= 0xC0 (die durch (b&0xC0)==0xC0 faelschlich als Lauflaengen-Token statt als literaler Indexwert interpretiert werden koennten) -- noch nicht verifiziert. FACTOR5.PCC's "Geister"-Doppellogo ist dagegen vermutlich KEIN Bug, sondern ein Reflexions-/Schatten- Designelement (Byte-Konsum ist bei diesem File exakt 100%). Nutzung: python3 pcc_to_png.py Schreibt ein PPM und konvertiert via ImageMagick `convert` zu PNG. """ import sys, struct, subprocess, os def decode_pcc(data): manuf, version, encoding, bpp = data[0], data[1], data[2], data[3] xmin, ymin, xmax, ymax = struct.unpack('= n: break val = pixel_region[i]; i += 1 row_out.extend([val] * count) else: row_out.append(b) row_out = row_out[:width] if len(row_out) < width: row_out.extend([0] * (width - len(row_out))) out.extend(row_out) return dict(width=width, height=height, marker_ok=(marker == 0x0C), pixels=bytes(out), palette=palette, consumed=i, pixel_region_len=n) def write_png(pixels, palette, width, height, out_png): ppm = out_png + '.ppm' with open(ppm, 'wb') as f: f.write(f'P6\n{width} {height}\n255\n'.encode()) buf = bytearray() for px in pixels[:width*height]: r, g, b = palette[px] buf.extend([r, g, b]) f.write(bytes(buf)) subprocess.run(['convert', ppm, out_png], check=True) os.remove(ppm) def main(): inp, outp = sys.argv[1], sys.argv[2] with open(inp, 'rb') as f: data = f.read() res = decode_pcc(data) print(f"{inp}: {res['width']}x{res['height']} marker_ok={res['marker_ok']} " f"consumed={res['consumed']}/{res['pixel_region_len']}") write_png(res['pixels'], res['palette'], res['width'], res['height'], outp) print(f"-> {outp}") if __name__ == '__main__': main()