diff --git a/NOTES.md b/NOTES.md index 1e4c669..83c57c0 100644 --- a/NOTES.md +++ b/NOTES.md @@ -535,15 +535,86 @@ Projekt gesichert unter `tools/wip_bob_sprite_research/` (noch nicht aufgeraeumt/nicht final, bewusst als "work in progress" separiert von den fertigen Tools `split_pre.py`/`pcc_to_png.py`). -## Naechste Schritte (Prioritaet, Stand 09:30) -1. **BOB/SRB-Sprite-Format weiter reversen** — siehe `tools/wip_bob_sprite_research/` - fuer den aktuellen (unfertigen) Exploration-Stand. Naechster konkreter - Schritt: `PCKELL.DAT`-Offset-Tabelle sauber mit den 32 `SRB.LST`-Namen - abgleichen, danach `A.BOB` (kleinstes Beispiel) testdecodieren. -2. Rauser-Intro-Sequenz (3 Frames) zu einer zusammengesetzten Vorschau +## KORREKTUR 2026-07-22 (nachmittags) — der "kontinuierliche" Fix oben war FALSCH + +Stefan hat sich das gepushte Repo angeschaut und den Bug SOFORT wieder +gesehen (Frosch links statt rechts, Kellogg's-Logo verschoben) — die +"pixel-perfect verifiziert"-Behauptung im Abschnitt direkt darueber war +**selbst ein Messfehler**, genau wie der davor schon korrigierte. Beide +eigenen Hand-RLE-Decoder (Row-Reset UND kontinuierlich) haben KARTE.PCC/ +KELLOGGS.PCC nachweislich sichtbar falsch decodiert (Bildinhalt dupliziert/ +verschoben) — mein visueller Vorher-Vergleich war schlicht nicht sorgfaeltig +genug, ich habe der eigenen Automatik zu frueh vertraut. + +**Stefans entscheidender Hinweis:** https://github.com/movAX13h/tony-and-friends-in-kelloggs-land +— ein bereits existierendes Reverse-Engineering-Projekt fuer GENAU dieses +Spiel (dort "Tony and Friends in Kellogg's Land" genannt, gleiche +PCKELL.DAT/PCKELL.SET-Assets). Enthaelt fertigen, funktionierenden C#-Code +fuer PCX/BOB/MAP/ARE/ICO/DAT — Quelle: `/tmp/tony-ref` (geklont, kein +LICENSE-File im Repo, daher NICHT 1:1-Code uebernommen, sondern nur als +Format-Referenz genutzt und in eigenem Python-Code neu umgesetzt). + +Deren `Formats/PCXFile.cs` bestaetigt: `.PCC` ist stinknormales PCX v5, +8bpp, RLE, **mit Row-Reset** (pro Zeile wird ein frischer RLE-Reader +erzeugt, ueberschuessige Pixel eines Runs am Zeilenende werden verworfen — +Standard-PCX-Verhalten, keine Sonderregel). Das war also urspruenglich +(Bugfix 1, vormittags) schon RICHTIG vermutet — der eigentliche Bug lag +tiefer in der eigenen Implementierung und wurde nie gefunden. + +**Der eigentliche Fix:** Decoder komplett ausgetauscht — `tools/pcc_to_png.py` +nutzt jetzt NICHT mehr eigenes RLE-Decoding, sondern `convert pcx:datei.PCC +out.png` (ImageMagick's ausgereifter PCX-Decoder, der die Datei einfach +per Standard-Format-Erkennung liest). Kein Workaround, sondern die richtige +Loesung: unser Format-Verstaendnis war im Kern korrekt, nur die +Handimplementierung hatte einen Bug den wir trotz zweier Anlaeufe nicht +gefunden haben — ein reifer, jahrzehntealter PCX-Decoder macht das +zuverlaessig richtig. + +**Neu verifiziert (diesmal wirklich sorgfaeltig, Bild angeschaut statt nur +Byte-Statistik):** `png_out/KELLOGGS.png` und `png_out/KARTE.png` sehen jetzt +visuell identisch zu den echten DOSBox-Screenshots aus +(`aria_kellogg_titlescreen.png`/`aria_kellogg_worldmap.png`) — Vogelkopf +oben links, Drache+Schloss oben rechts, Frosch im Teich, Tiger unten +rechts, Coco unten links, alles an der richtigen Stelle, kein Doppel-Bild, +keine Kerbe. `FACTOR5.PCC`s "Geister-Doppellogo" ist ueber ImageMagick +ebenfalls sauber decodiert und bestaetigt sich damit endgueltig als +Absicht (Reflexions-Design), nicht als Bug. Alle 77 PCC-Dateien mit dem +neuen Decoder neu erzeugt und liegen in `png_out/`. + +**Nebenfund:** `TONY.PCC`, `SMACKS.PCC`, `TOUCAN.PCC`, `COCO.PCC` (und +vermutlich weitere Charakter-Namen) decodieren zu winzigen 1x1-Pixel-Bildern +— das ist KEIN Extraktions-Bug. Laut `Form1.cs` der Referenz dienen manche +`.PCC`-Dateien nur als **Palette-Lieferant** fuer die zugehoerige `.ICO`/ +`.BOB`-Sprite-Datei (ICO/BOB haben selbst keine eingebettete Palette, +im Gegensatz zu vollen PCC-Bildschirmen). Wichtig fuers spaetere BOB/ICO- +Decoding: Palette kommt dann von der PCC-Datei mit gleichem Basisnamen +(z.B. `TONY.BOB` -> Palette aus `TONY.PCC`), nicht aus der Sprite-Datei +selbst. + +## Naechste Schritte (Prioritaet, Stand nachmittags 22.07.) +1. **BOB/SRB-Sprite-Format mit Hilfe der Referenz reversen.** Die Referenz + (`/tmp/tony-ref/Unpacker/Formats/BOBFile.cs`, `BOBPainter.cs`) hat das + schon geloest — README beschreibt BOB als "data and x86 code to display; + somewhat similar to BOB format of Turrican II". Naechster Schritt: deren + `BOBFile.cs` genau lesen und die Logik in eigenes Python uebersetzen + (nicht 1:1 kopieren, kein LICENSE im Repo), dann gegen `A.BOB` + (kleinstes Beispiel) verifizieren. Das sollte den bisherigen + `wip_bob_sprite_research/`-Blindflug ueberfluessig machen. +2. Genauso `MAPFile.cs`/`AREFile.cs`/`ICOFile.cs` fuer Level-Maps/Areas/ + Tilesets nutzen statt weiter blind zu raten — auch hier: Format-Wissen + uebernehmen, Code selbst neu schreiben. +3. Rauser-Intro-Sequenz (3 Frames) zu einer zusammengesetzten Vorschau rendern/dokumentieren, sobald Zeit ist — niedrige Prioritaet, Assets sind schon da. -3. MAP/ARE-Levelformat reversen. -4. Python/pygame-Projektstruktur aufsetzen (PCC-Loader ist fertig, muss nur - noch von PPM/ImageMagick-Zwischenschritt auf direktes Pillow-basiertes - PNG/Surface-Schreiben umgestellt werden fuer den finalen Port). +4. Python/pygame-Projektstruktur aufsetzen (PCC-Loader ist jetzt wirklich + fertig und verifiziert: `convert pcx:datei.PCC out.png` bzw. fuer den + finalen Port direkt Pillow's PCX-Reader nutzen — Pillow unterstuetzt + PCX nativ, damit brauchen wir zur Laufzeit kein externes ImageMagick + mehr). + +**Lektion fuer mich selbst (ARIA):** zweimal hintereinander eine "pixel- +perfect verifiziert"-Behauptung aufgestellt, die beim tatsaechlichen +Hinschauen (Bild anzeigen, nicht nur Bytes vergleichen) klar falsch war. +Naechstes Mal: bei jeder Verifikations-Behauptung IMMER das decodierte Bild +selbst anschauen (Read-Tool, multimodal), nicht nur Byte-/Pixel-Statistiken +vertrauen, bevor ich "geloest"/"verifiziert" sage. diff --git a/png_out/ANTS.png b/png_out/ANTS.png index 682c58a..b577ee2 100644 Binary files a/png_out/ANTS.png and b/png_out/ANTS.png differ diff --git a/png_out/CHOCOS.png b/png_out/CHOCOS.png index aeb2f4f..e3baa87 100644 Binary files a/png_out/CHOCOS.png and b/png_out/CHOCOS.png differ diff --git a/png_out/CHOCOS10.png b/png_out/CHOCOS10.png index 2095dd4..35195dd 100644 Binary files a/png_out/CHOCOS10.png and b/png_out/CHOCOS10.png differ diff --git a/png_out/CHOCOS2.png b/png_out/CHOCOS2.png index 6328f64..05b7218 100644 Binary files a/png_out/CHOCOS2.png and b/png_out/CHOCOS2.png differ diff --git a/png_out/CHOCOS3.png b/png_out/CHOCOS3.png index 6328f64..67656c8 100644 Binary files a/png_out/CHOCOS3.png and b/png_out/CHOCOS3.png differ diff --git a/png_out/CHOCOS4.png b/png_out/CHOCOS4.png index 6328f64..340ae52 100644 Binary files a/png_out/CHOCOS4.png and b/png_out/CHOCOS4.png differ diff --git a/png_out/CHOCOS5.png b/png_out/CHOCOS5.png index 6328f64..812debc 100644 Binary files a/png_out/CHOCOS5.png and b/png_out/CHOCOS5.png differ diff --git a/png_out/CHOCOS6.png b/png_out/CHOCOS6.png index 2e00fcb..ff1b0e0 100644 Binary files a/png_out/CHOCOS6.png and b/png_out/CHOCOS6.png differ diff --git a/png_out/CHOCOS7.png b/png_out/CHOCOS7.png index 2e00fcb..fd1c89e 100644 Binary files a/png_out/CHOCOS7.png and b/png_out/CHOCOS7.png differ diff --git a/png_out/CHOCOS8.png b/png_out/CHOCOS8.png index 2095dd4..6d51365 100644 Binary files a/png_out/CHOCOS8.png and b/png_out/CHOCOS8.png differ diff --git a/png_out/CHOCOS9.png b/png_out/CHOCOS9.png index 2095dd4..1632178 100644 Binary files a/png_out/CHOCOS9.png and b/png_out/CHOCOS9.png differ diff --git a/png_out/COCO.png b/png_out/COCO.png index 682c58a..4c8fd79 100644 Binary files a/png_out/COCO.png and b/png_out/COCO.png differ diff --git a/png_out/FAC0.png b/png_out/FAC0.png index 171c3d3..be08e29 100644 Binary files a/png_out/FAC0.png and b/png_out/FAC0.png differ diff --git a/png_out/FAC1.png b/png_out/FAC1.png index 171c3d3..fdecbf5 100644 Binary files a/png_out/FAC1.png and b/png_out/FAC1.png differ diff --git a/png_out/FAC2.png b/png_out/FAC2.png index 171c3d3..9f61de4 100644 Binary files a/png_out/FAC2.png and b/png_out/FAC2.png differ diff --git a/png_out/FAC3.png b/png_out/FAC3.png index 171c3d3..8c4c056 100644 Binary files a/png_out/FAC3.png and b/png_out/FAC3.png differ diff --git a/png_out/FACTOR5.png b/png_out/FACTOR5.png index 5337990..6ba6dc1 100644 Binary files a/png_out/FACTOR5.png and b/png_out/FACTOR5.png differ diff --git a/png_out/FON_1.png b/png_out/FON_1.png index a4388bc..63e0df3 100644 Binary files a/png_out/FON_1.png and b/png_out/FON_1.png differ diff --git a/png_out/FON_1B.png b/png_out/FON_1B.png index 9ec9de9..d19a2dc 100644 Binary files a/png_out/FON_1B.png and b/png_out/FON_1B.png differ diff --git a/png_out/FON_2.png b/png_out/FON_2.png index a75a851..ecfaf9f 100644 Binary files a/png_out/FON_2.png and b/png_out/FON_2.png differ diff --git a/png_out/FRA_L.png b/png_out/FRA_L.png index e7d06f8..a2f4c9a 100644 Binary files a/png_out/FRA_L.png and b/png_out/FRA_L.png differ diff --git a/png_out/FRA_LO.png b/png_out/FRA_LO.png index e7d06f8..b3f0aea 100644 Binary files a/png_out/FRA_LO.png and b/png_out/FRA_LO.png differ diff --git a/png_out/FRA_LU.png b/png_out/FRA_LU.png index e7d06f8..3fe2001 100644 Binary files a/png_out/FRA_LU.png and b/png_out/FRA_LU.png differ diff --git a/png_out/FRA_M.png b/png_out/FRA_M.png index e7d06f8..76ecb0b 100644 Binary files a/png_out/FRA_M.png and b/png_out/FRA_M.png differ diff --git a/png_out/FRA_O.png b/png_out/FRA_O.png index e7d06f8..5fe0178 100644 Binary files a/png_out/FRA_O.png and b/png_out/FRA_O.png differ diff --git a/png_out/FRA_R.png b/png_out/FRA_R.png index e7d06f8..7f56b16 100644 Binary files a/png_out/FRA_R.png and b/png_out/FRA_R.png differ diff --git a/png_out/FRA_RO.png b/png_out/FRA_RO.png index e7d06f8..40882c0 100644 Binary files a/png_out/FRA_RO.png and b/png_out/FRA_RO.png differ diff --git a/png_out/FRA_RU.png b/png_out/FRA_RU.png index e7d06f8..82624e6 100644 Binary files a/png_out/FRA_RU.png and b/png_out/FRA_RU.png differ diff --git a/png_out/FRA_U.png b/png_out/FRA_U.png index e7d06f8..83da564 100644 Binary files a/png_out/FRA_U.png and b/png_out/FRA_U.png differ diff --git a/png_out/GAMEBAR.png b/png_out/GAMEBAR.png index 847626b..90ab254 100644 Binary files a/png_out/GAMEBAR.png and b/png_out/GAMEBAR.png differ diff --git a/png_out/ITE0.png b/png_out/ITE0.png index 171c3d3..3bc2a00 100644 Binary files a/png_out/ITE0.png and b/png_out/ITE0.png differ diff --git a/png_out/ITE1.png b/png_out/ITE1.png index 171c3d3..3bb1a3a 100644 Binary files a/png_out/ITE1.png and b/png_out/ITE1.png differ diff --git a/png_out/ITE2.png b/png_out/ITE2.png index fdff446..afa20c9 100644 Binary files a/png_out/ITE2.png and b/png_out/ITE2.png differ diff --git a/png_out/ITE3.png b/png_out/ITE3.png index b40675c..0f8a5ba 100644 Binary files a/png_out/ITE3.png and b/png_out/ITE3.png differ diff --git a/png_out/KARTE.png b/png_out/KARTE.png index 0646fa7..5a39083 100644 Binary files a/png_out/KARTE.png and b/png_out/KARTE.png differ diff --git a/png_out/KARTE0.png b/png_out/KARTE0.png index fedcaba..58db1f1 100644 Binary files a/png_out/KARTE0.png and b/png_out/KARTE0.png differ diff --git a/png_out/KARTE1.png b/png_out/KARTE1.png index 475ada2..2a91b6b 100644 Binary files a/png_out/KARTE1.png and b/png_out/KARTE1.png differ diff --git a/png_out/KARTE2.png b/png_out/KARTE2.png index fedcaba..069616e 100644 Binary files a/png_out/KARTE2.png and b/png_out/KARTE2.png differ diff --git a/png_out/KARTE3.png b/png_out/KARTE3.png index 475ada2..16a57a8 100644 Binary files a/png_out/KARTE3.png and b/png_out/KARTE3.png differ diff --git a/png_out/KARTE4.png b/png_out/KARTE4.png index afae554..e0dca1a 100644 Binary files a/png_out/KARTE4.png and b/png_out/KARTE4.png differ diff --git a/png_out/KARTE5.png b/png_out/KARTE5.png index afae554..20c9b2d 100644 Binary files a/png_out/KARTE5.png and b/png_out/KARTE5.png differ diff --git a/png_out/KARTE6.png b/png_out/KARTE6.png index 5f24de2..12359bf 100644 Binary files a/png_out/KARTE6.png and b/png_out/KARTE6.png differ diff --git a/png_out/KARTE7.png b/png_out/KARTE7.png index fcbbc9f..3b9d740 100644 Binary files a/png_out/KARTE7.png and b/png_out/KARTE7.png differ diff --git a/png_out/KARTE8.png b/png_out/KARTE8.png index 019ca15..f11c7fd 100644 Binary files a/png_out/KARTE8.png and b/png_out/KARTE8.png differ diff --git a/png_out/KARTE9.png b/png_out/KARTE9.png index d4ced9f..8cf02d0 100644 Binary files a/png_out/KARTE9.png and b/png_out/KARTE9.png differ diff --git a/png_out/KELL256A.png b/png_out/KELL256A.png index 04f1e6c..1cbe02e 100644 Binary files a/png_out/KELL256A.png and b/png_out/KELL256A.png differ diff --git a/png_out/KELL256B.png b/png_out/KELL256B.png index 341de95..2e13ed9 100644 Binary files a/png_out/KELL256B.png and b/png_out/KELL256B.png differ diff --git a/png_out/KELL256C.png b/png_out/KELL256C.png index 9414552..99424aa 100644 Binary files a/png_out/KELL256C.png and b/png_out/KELL256C.png differ diff --git a/png_out/KELL256D.png b/png_out/KELL256D.png index 02f041f..fe23e2a 100644 Binary files a/png_out/KELL256D.png and b/png_out/KELL256D.png differ diff --git a/png_out/KELLOGGS.png b/png_out/KELLOGGS.png index d92f669..1661a94 100644 Binary files a/png_out/KELLOGGS.png and b/png_out/KELLOGGS.png differ diff --git a/png_out/MENU.png b/png_out/MENU.png index e28e418..6fa2ec3 100644 Binary files a/png_out/MENU.png and b/png_out/MENU.png differ diff --git a/png_out/PARTY1.png b/png_out/PARTY1.png index 7908eec..cb639c1 100644 Binary files a/png_out/PARTY1.png and b/png_out/PARTY1.png differ diff --git a/png_out/PARTY2.png b/png_out/PARTY2.png index 0365318..41acf65 100644 Binary files a/png_out/PARTY2.png and b/png_out/PARTY2.png differ diff --git a/png_out/RAUSER1.png b/png_out/RAUSER1.png index ff78c99..4fc47e6 100644 Binary files a/png_out/RAUSER1.png and b/png_out/RAUSER1.png differ diff --git a/png_out/RAUSER2.png b/png_out/RAUSER2.png index 5ed2363..139ce37 100644 Binary files a/png_out/RAUSER2.png and b/png_out/RAUSER2.png differ diff --git a/png_out/RAUSER3.png b/png_out/RAUSER3.png index 55e090f..bea6757 100644 Binary files a/png_out/RAUSER3.png and b/png_out/RAUSER3.png differ diff --git a/png_out/SMACKS.png b/png_out/SMACKS.png index 9163634..a343eff 100644 Binary files a/png_out/SMACKS.png and b/png_out/SMACKS.png differ diff --git a/png_out/TONY.png b/png_out/TONY.png index 57269d2..4c8fd79 100644 Binary files a/png_out/TONY.png and b/png_out/TONY.png differ diff --git a/png_out/TOUCAN.png b/png_out/TOUCAN.png index 57269d2..297f703 100644 Binary files a/png_out/TOUCAN.png and b/png_out/TOUCAN.png differ diff --git a/png_out/VIT1.png b/png_out/VIT1.png index 0daf336..7c66707 100644 Binary files a/png_out/VIT1.png and b/png_out/VIT1.png differ diff --git a/png_out/VIT2.png b/png_out/VIT2.png index 0daf336..ff82bc4 100644 Binary files a/png_out/VIT2.png and b/png_out/VIT2.png differ diff --git a/png_out/VIT3.png b/png_out/VIT3.png index 0daf336..c95f6e3 100644 Binary files a/png_out/VIT3.png and b/png_out/VIT3.png differ diff --git a/png_out/VIT4.png b/png_out/VIT4.png index 0daf336..ebad180 100644 Binary files a/png_out/VIT4.png and b/png_out/VIT4.png differ diff --git a/png_out/VIT5.png b/png_out/VIT5.png index 0daf336..dc11131 100644 Binary files a/png_out/VIT5.png and b/png_out/VIT5.png differ diff --git a/png_out/VIT6.png b/png_out/VIT6.png index 0daf336..48f8191 100644 Binary files a/png_out/VIT6.png and b/png_out/VIT6.png differ diff --git a/png_out/W1.png b/png_out/W1.png index 57269d2..133a1d0 100644 Binary files a/png_out/W1.png and b/png_out/W1.png differ diff --git a/png_out/W2.png b/png_out/W2.png index 390b80a..fec30ee 100644 Binary files a/png_out/W2.png and b/png_out/W2.png differ diff --git a/png_out/W3.png b/png_out/W3.png index 2c06812..1587bfa 100644 Binary files a/png_out/W3.png and b/png_out/W3.png differ diff --git a/png_out/WECH_1.png b/png_out/WECH_1.png index c75b1e4..2f74c6f 100644 Binary files a/png_out/WECH_1.png and b/png_out/WECH_1.png differ diff --git a/png_out/WECH_2.png b/png_out/WECH_2.png index 7cd9507..3e91528 100644 Binary files a/png_out/WECH_2.png and b/png_out/WECH_2.png differ diff --git a/png_out/WECH_3.png b/png_out/WECH_3.png index 2061190..0e7e5bb 100644 Binary files a/png_out/WECH_3.png and b/png_out/WECH_3.png differ diff --git a/png_out/WECH_4.png b/png_out/WECH_4.png index de7d2cf..80a2786 100644 Binary files a/png_out/WECH_4.png and b/png_out/WECH_4.png differ diff --git a/png_out/WECH_5.png b/png_out/WECH_5.png index 8631c8e..4b5757a 100644 Binary files a/png_out/WECH_5.png and b/png_out/WECH_5.png differ diff --git a/png_out/WECH_6.png b/png_out/WECH_6.png index a361202..49c4b29 100644 Binary files a/png_out/WECH_6.png and b/png_out/WECH_6.png differ diff --git a/png_out/WECH_7.png b/png_out/WECH_7.png index 79ca3a5..74830f7 100644 Binary files a/png_out/WECH_7.png and b/png_out/WECH_7.png differ diff --git a/png_out/WECH_8.png b/png_out/WECH_8.png index 71669d7..771d572 100644 Binary files a/png_out/WECH_8.png and b/png_out/WECH_8.png differ diff --git a/png_out/WECH_9.png b/png_out/WECH_9.png index d74e5bf..42f719e 100644 Binary files a/png_out/WECH_9.png and b/png_out/WECH_9.png differ diff --git a/tools/pcc_to_png.py b/tools/pcc_to_png.py index 8030302..1ee1a32 100644 --- a/tools/pcc_to_png.py +++ b/tools/pcc_to_png.py @@ -1,109 +1,75 @@ #!/usr/bin/env python3 -"""Finaler PCC-Decoder fuer Kellogg's Tony and Friends (2026-07-22, ARIA). +"""PCC-Decoder fuer Kellogg's Tony and Friends (2026-07-22, ARIA) -- v3, ECHTER FIX. -Format (verifiziert gegen MENU.PCC/RAUSER1-3/FACTOR5, visuell + byte-exakt, -UND jetzt zusaetzlich Pixel-fuer-Pixel gegen echte DOSBox-Screenshots von -KARTE.PCC und KELLOGGS.PCC verglichen -- siehe Bugfix 2 unten): +Vorgeschichte (fuer die naechste Session / falls hier nochmal jemand ran will): +Wir hatten ZWEI eigene Hand-RLE-Decoder gebaut (v1: pro-Zeile-Reset/Trim, +v2: "kontinuierlich" ohne Zeilen-Reset) -- BEIDE haben KARTE.PCC/KELLOGGS.PCC +sichtbar kaputt dekodiert (Charaktere verschoben, Bild dupliziert/gespiegelt). +Stefan hat das beim Draufschauen zurecht bemaengelt ("Frosch waere rechts, +nicht links" / Logo verschoben). v2 wurde faelschlich als "pixel-perfect +verifiziert" dokumentiert -- das war ein Verifikations-Fehler, nicht die +Wahrheit; visuell war es klar erkennbar kaputt. -- 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 1, 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. +Fund via Stefans Hinweis auf https://github.com/movAX13h/tony-and-friends-in-kelloggs-land: +Deren PCXFile.cs bestaetigt .PCC = stinknormales PCX v5, 8bpp, RLE, EIGENE +256-Farb-Palette am Dateiende -- KEIN Custom-Format, KEINE Sonderregeln. +Das Game selbst listet es im README so: "PCC | Image | PCX version 5, +encoded, 8 bit per px". -- Byte 16 .. (len-769): RLE-komprimierte Pixel-Indexdaten. - WICHTIG (Bugfix 2, 2026-07-22): Die RLE-Runs werden NICHT pro Scanline - zurueckgesetzt/abgeschnitten. Frueher wurde nach `width` Pixeln pro Zeile - hart getrimmt und der Rest eines laufenden RLE-Runs verworfen ("row reset"). - Das war FALSCH: dieses Format haelt sich nicht an die klassische PCX-Regel - "ein Run ueberschreitet nie eine Scanline" -- Runs koennen frei ueber - Zeilengrenzen hinweglaufen. Der Beweis: mit Row-Reset waren KARTE.PCC und - KELLOGGS.PCC (die einzigen zwei echten 320x200-Vollbilder) sichtbar verwuerfelt - (Charaktere an falscher Position, "Kerbe" im Logo-Rahmen), obwohl der - Byte-Konsum fast vollstaendig war -- der Fehler kostet nur ~0.3-0.5% der - Pixel, aber genau die falschen, wodurch ganze Bildbereiche sichtbar - verrutschen. Mit kontinuierlicher Dekodierung (einfach `width*height` Pixel - am Stueck aus dem RLE-Strom lesen, OHNE pro-Zeile zu trimmen) sind beide - Bilder jetzt Pixel-fuer-Pixel identisch zu echten DOSBox-Screenshots - (verifiziert per ImageMagick-Vergleich, siehe NOTES.md). Kleine Sprites - waren von diesem Bug kaum betroffen, weil sie selten/nie einen Run ueber - eine Zeilengrenze hinweg haben -- deshalb fiel es dort nicht auf. - RLE-Tupel: Byte mit oberen 2 Bits gesetzt (0xC0-0xFF) = Lauflaenge (&0x3F), - gefolgt von einem Wert-Byte. Sonst literaler Pixel. +ECHTER FIX: statt eines eigenen RLE-Decoders nutzen wir ImageMagick's +ausgereiften, extrem gut getesteten PCX-Decoder direkt (`convert pcx:datei.PCC +out.png`). Das ist kein Umgehen des Problems, sondern die richtige Antwort -- +unser Format-Verstaendnis (Header, RLE-Tupel, Palette) war im Kern korrekt, +aber die Handimplementierung hatte einen Bug den wir trotz zweier Anlaeufe +nicht gefunden haben. ImageMagick beherrscht Standard-PCX seit Jahrzehnten +korrekt. Verifiziert: KARTE.PCC und KELLOGGS.PCC sehen damit jetzt WIRKLICH +identisch zu den echten DOSBox-Screenshots aus (Vogelkopf oben links, Drache+ +Schloss oben rechts, Frosch im Teich, Tiger unten rechts, Coco unten links -- +alles an der richtigen Stelle). -- 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. - -GELOEST (frueher "bekannter offener Bug", Stand vor 2026-07-22 Nachmittag): - Das "Kerbe"/Notch-Artefakt neben dem Kellogg's-Schriftzug und die verrutschten - Charaktere auf KARTE.PCC waren beide der gleiche Bug (Row-Reset, s.o.), NICHT - ein horizontales Rollen und NICHT eine Palette-Fehlzuordnung. Mit der - kontinuierlichen Dekodierung ist das Artefakt komplett weg. +Format-Doku (zur Referenz, nicht mehr fuers Decoding gebraucht): +- Byte 0-15: PCX-Header-Anfang (Manufacturer=0x0A, Version=5, Encoding=1/RLE, + BPP=8, Xmin/Ymin/Xmax/Ymax LE16 bei Offset 4-11). Breite/Hoehe = Xmax-Xmin+1 + / Ymax-Ymin+1. +- Byte 16..(len-769): RLE-Pixeldaten, klassisches PCX-RLE (Byte mit oberen + 2 Bits gesetzt = Lauflaenge&0x3F + Wert-Byte, sonst literaler Pixel), + PRO ZEILE auf die Bildbreite abgeschnitten (Standard-PCX-Regel: ein Run + ueberschreitet nie eine Scanline -- das war frueher unsere v1-Annahme + und war tatsaechlich richtig, nur unsere Implementierung hatte woanders + einen Bug). +- Letzte 769 Byte: 0x0C-Marker + 768 Byte (256 x RGB) eigene Palette pro Datei. Nutzung: python3 pcc_to_png.py -Schreibt ein PPM und konvertiert via ImageMagick `convert`/`magick` zu PNG. """ -import sys, struct, subprocess, os, shutil +import sys, struct, subprocess, shutil -def decode_pcc(data): +def read_header(data): manuf, version, encoding, bpp = data[0], data[1], data[2], data[3] xmin, ymin, xmax, ymax = struct.unpack('= 0 else None + return dict(manuf=manuf, version=version, encoding=encoding, bpp=bpp, + width=width, height=height, marker_ok=(marker == 0x0C)) - total = width * height - out = bytearray() - i = 0 - n = len(pixel_region) - while len(out) < total and i < n: - b = pixel_region[i]; i += 1 - if (b & 0xC0) == 0xC0: - count = b & 0x3F - if i >= n: - break - val = pixel_region[i]; i += 1 - remaining = total - len(out) - out.extend([val] * min(count, remaining)) - else: - out.append(b) - if len(out) < total: - out.extend([0] * (total - len(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)) +def decode_with_imagemagick(inp, outp): convert_bin = shutil.which('magick') or shutil.which('convert') - args = [convert_bin, ppm, out_png] if 'magick' not in (convert_bin or '') or convert_bin.endswith('convert') else [convert_bin, 'convert', ppm, out_png] - subprocess.run(args, check=True) - os.remove(ppm) + if not convert_bin: + raise RuntimeError("Weder 'magick' noch 'convert' (ImageMagick) gefunden.") + if convert_bin.endswith('magick'): + args = [convert_bin, f'pcx:{inp}', outp] + else: + args = [convert_bin, f'pcx:{inp}', outp] + subprocess.run(args, check=True, capture_output=True) 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) + hdr = read_header(data) + print(f"{inp}: {hdr['width']}x{hdr['height']} bpp={hdr['bpp']} " + f"marker_ok={hdr['marker_ok']}") + decode_with_imagemagick(inp, outp) print(f"-> {outp}") if __name__ == '__main__':