Initial commit: PCC-Decoder (mit Xmax/Ymax-Bugfix), Notes, DOSBox-Config
- tools/pcc_to_png.py: PCX-artiger Decoder fuer die 77 PCC-Vollbild-/Logo- Dateien, mit Fix fuer falsch gelesene Width/Height (muss aus Xmax-Xmin+1/ Ymax-Ymin+1 statt Offset 12-15 berechnet werden) - tools/split_pre.py: Extrahiert alle 181 Dateien aus PCKELL.PRE - png_out/: alle 77 konvertierten PNGs (Arbeitsergebnis, kein Original-Asset) - NOTES.md: vollstaendiger Analyse-Stand inkl. offener Punkte - dosbox/dosbox.conf: funktionierende Testkonfiguration (IRQ-Fix) Bewusst ausgeschlossen (.gitignore): raw/ und extracted_pre/, da Original- Spieldaten von Kellogg's Tony and Friends (1994), keine Weiterverbreitung.
@@ -0,0 +1,4 @@
|
||||
# Original-Spieldateien (copyrighted, gehoeren nicht ins Repo)
|
||||
raw/
|
||||
extracted_pre/
|
||||
*.ppm
|
||||
@@ -0,0 +1,467 @@
|
||||
# Kellogg's Tony and Friends — Remake-Projekt, Analyse-Notizen
|
||||
|
||||
## Ziel
|
||||
Plattformunabhängiges Remake (Python) von "Kellogg's Tony and Friends" (DOS, 1994),
|
||||
lauffähig auf x64 ohne DOS/QEMU. Ursprung: Spiel crasht in unserer FreeDOS-VM
|
||||
(RTM.EXE-Engine vs. QEMU-VGA-Emulation, siehe Projekt "Classic Games" für den
|
||||
Debug-Marathon dazu) — daher der Wunsch für ein eigenes Remake.
|
||||
|
||||
## Quelldateien (aus VM-Disk `dos_shareware` extrahiert, liegen in `raw/`)
|
||||
- `RTM.EXE` — die eigentliche Spiel-Engine (Runtime), DOS-EXE (MZ-Header), 108 KB.
|
||||
- `START.EXE` / `SETUP.EXE` — Launcher/Konfigurationsprogramme.
|
||||
- `KELLOGG.INI` — Soundkarten-Konfig (IRQ/DMA), irrelevant fürs Remake.
|
||||
- `PCKELL.DAT` — 2,69 MB, **unkomprimiert**. Beginnt mit einer monoton
|
||||
steigenden 16-Bit-LE-Offset-Tabelle (vermutlich Sprite-/Block-Offsets in
|
||||
die anhängenden Rohdaten). Enthält laut `SRB.LST` 32 "SRB"-Blöcke
|
||||
(vermutlich "Sprite Resource Block"). Tabellenstruktur noch nicht final
|
||||
verstanden — steigt über mehr Einträge als die reinen 32 Top-Level-Files,
|
||||
also vermutlich verschachtelt (pro Sprite/Frame-Offsets innerhalb jedes
|
||||
SRB-Blocks). TODO: Tabellenende finden und pro-SRB-Grenzen bestimmen.
|
||||
- `PCKELL.SET` — **ist tatsächlich eine ZIP-Datei** (Signatur `PK\x03\x04`)!
|
||||
Enthält 3 Einträge:
|
||||
- `APPEND.LST` (Methode 6 "Imploded", 1945 Bytes entpackt) — Manifest mit
|
||||
178 Dateinamen für die in `PCKELL.PRE` zusammengefügten Assets.
|
||||
- `SRB.LST` (Methode 1 "Shrink", 314 Bytes entpackt) — Manifest mit den
|
||||
32 Dateinamen, die zu `PCKELL.DAT` gehören (1:1 gleiche Basisnamen wie
|
||||
ein Teil der BOB-Einträge in APPEND.LST, z.B. `TONY.SRB` ↔ `TONY.BOB`).
|
||||
- `PCKELL.PRE` (Methode 6 "Imploded", 2.328.934 Bytes entpackt) — die
|
||||
eigentliche Asset-Sammlung, konkateniert nach der Reihenfolge in
|
||||
`APPEND.LST`.
|
||||
|
||||
**WICHTIG — Kompressions-Falle:** ZIP-Methode 6 "Imploding" ist NICHT
|
||||
dasselbe wie das "DCL Implode/Explode"-Format aus PKWAREs Data
|
||||
Compression Library (dafür gibt's `blast.c` in zlib/contrib — hab ich
|
||||
zuerst versucht, schlägt fehl mit "distance too far back", weil es ein
|
||||
anderer Algorithmus ist trotz gleichem Namen). Debian/Ubuntus
|
||||
`unzip` (Info-ZIP, kompiliert MIT Explode+Unshrink-Support) entpackt
|
||||
beide Methoden aber klaglos — auf `aria-wohnung` funktioniert
|
||||
`unzip PCKELL.SET` direkt. Das Alpine-`unzip` im Brain-Container
|
||||
(aria-proxy) kann das NICHT (kein Implode-Support einkompiliert).
|
||||
|
||||
## Datei-Typen in APPEND.LST (178 Einträge total)
|
||||
| Ext | Anzahl | Vermutung |
|
||||
|------|--------|-----------|
|
||||
| PCC | 77 | "Compiled Picture" — vermutlich RLE/Palette-komprimiertes Vollbild (Titelscreens, Menüs, Hintergründe) |
|
||||
| BOB | 32 | "Blob" — Sprite-Grafik-Daten (Spielfiguren, Objekte), 1:1 mit den 32 SRB-Einträgen |
|
||||
| MAP | 22 | Level-/Karten-Daten |
|
||||
| ARE | 22 | "Area" — vermutlich Level-Metadaten/Kollisionszonen, im Paar mit MAP (22=22) |
|
||||
| ICO | 19 | Icons (z.B. für Menüs/HUD) |
|
||||
| SAM | 3 | Sound-Samples |
|
||||
| TFX | 3 | vermutlich Musik/Sound-Effekt-Format |
|
||||
|
||||
## Nächste Schritte (in dieser Reihenfolge sinnvoll)
|
||||
1. **PCKELL.PRE aufteilen**: Die 178 Dateien sind laut APPEND.LST hintereinander
|
||||
konkateniert — es fehlt noch die Größentabelle pro Datei (vermutlich am
|
||||
Anfang von PCKELL.PRE selbst, ähnlich der Offset-Tabelle in PCKELL.DAT,
|
||||
oder implizit aus dem Dateiformat jedes Typs ableitbar). Header von
|
||||
PCKELL.PRE selbst noch nicht angeschaut.
|
||||
2. **Ein PCC-Bild reverse-engineeren** — vermutlich klassisches Palette+RLE,
|
||||
das dürfte am leichtesten zu knacken sein (PCC ähnelt oft PCX-Varianten).
|
||||
3. **BOB/SRB-Sprite-Format verstehen** — Kollisions-Header aus PCKELL.DAT
|
||||
(die Offset-Tabelle) mit den 32 SRB-Namen abgleichen.
|
||||
4. **RTM.EXE nicht disassemblieren müssen** — Ziel ist ein sauberes
|
||||
Python-Reimplement der Spiellogik (Physik/Level/Gegner aus MAP/ARE lesen),
|
||||
nicht 1:1-Code-Nachbau der DOS-Engine.
|
||||
|
||||
## Aufbewahrungsort
|
||||
Alle Rohdaten + Manifeste liegen unter `raw/` in diesem Projektordner
|
||||
(`/shared/projects/kellogg_s_remake/raw/`). `raw/extracted/` enthält den
|
||||
fehlgeschlagenen blast.c-Versuch (kann weg). Entpackte Dateien
|
||||
(`APPEND.LST`, `SRB.LST`, `PCKELL.PRE` — alle bereits entpackt, nicht mehr
|
||||
komprimiert) liegen direkt in `raw/`.
|
||||
|
||||
## DOSBox-Testumgebung (2026-07-22) — Original läuft jetzt!
|
||||
|
||||
**DOSBox 0.74-3 ist auf `aria-wohnung` installiert** (apt). Zweck: das
|
||||
Original-Spiel als visuelle/funktionale Referenz neben dem Reverse-Engineering
|
||||
laufen lassen, ohne die kaputte QEMU-VGA-Emulation aus dem "Classic Games"-Crash.
|
||||
|
||||
### Fehlende Dateien nachgeliefert
|
||||
`raw/` hatte urspr. nur `RTM.EXE` + `PCKELL.*` — **`START.EXE`, `SETUP.EXE`,
|
||||
`KELLOGG.INI`, `HDPMI16.EXE` (DPMI-Host) und `DPMI16BI.OVL`** fehlten (waren
|
||||
nie aus der `dos_shareware`-VM-Disk kopiert worden, nur RTM+PCKELL). Auch ein
|
||||
`READ.ME` (Original-Textdatei mit Installations-/Troubleshooting-Hinweisen)
|
||||
lag dort. Alle liegen jetzt in `raw/` (Quelle: `dos_shareware`-Diskimage auf
|
||||
`aria-wohnung`, Pfad `GAMES/KELLOGG/` — per `qemu-nbd --read-only` gemountet,
|
||||
VM dafür kurz gestoppt, danach sauber wieder disconnected. **Die VM
|
||||
`dos_shareware` ist seitdem gestoppt** — bei Bedarf über's Cockpit-Panel oder
|
||||
`aria-vm boot dos_shareware` wieder hochfahren).
|
||||
|
||||
**RTM.EXE ist die eigentliche Engine**, aber sie verweigert den Direktstart
|
||||
("This loader is invoked and required by Borland tools") — man MUSS über
|
||||
`START.EXE` gehen, das dann RTM.EXE korrekt aufruft.
|
||||
|
||||
### Der Show-Stopper: IRQ-Konflikt (aus dem Original-READ.ME!)
|
||||
Mit den Original-`KELLOGG.INI`-Werten (`Adr=544 DMA=1 IRQ=7`, Soundblaster)
|
||||
hängt sich das Spiel nach dem 2. Studio-Logo (Factor 5) in einer Endlosschleife
|
||||
auf — kompletter schwarzer Screen, DOSBox-Prozess lebt aber (kein Crash,
|
||||
Freeze). Das READ.ME beschreibt dieses EXAKTE Symptom ("Bild bleibt schwarz")
|
||||
und empfiehlt IRQ 5 statt 7. Fix:
|
||||
- `raw/KELLOGG.INI`: `IRQ=5` (statt 7)
|
||||
- `dosbox.conf` `[sblaster]`-Sektion MUSS matchen: `irq=5` (sonst redet DOSBox's
|
||||
emulierte SB16 am falschen IRQ vorbei und das Spiel hängt trotzdem)
|
||||
|
||||
Mit diesem Fix läuft das Spiel sauber durch: Rauser-Advertainment-Logo →
|
||||
Factor-5-Logo → Kellogg's-Logo → "Tony & Friends in Kellogg's Land"-Titelbild
|
||||
→ Weltkarte (Berge/Wald/Schloss mit Drache, alle Charaktere sichtbar:
|
||||
Tiger/Tony, Toucan, Frosch, Affe/Coco, Bär). Screenshots liegen unter
|
||||
`/shared/uploads/aria_kellogg_*.png`.
|
||||
|
||||
### Funktionierende dosbox.conf
|
||||
```
|
||||
[sdl]
|
||||
fullscreen=false
|
||||
output=surface
|
||||
|
||||
[sblaster]
|
||||
sbtype=sb16
|
||||
sbbase=220
|
||||
irq=5
|
||||
dma=1
|
||||
hdma=5
|
||||
|
||||
[autoexec]
|
||||
mount c <pfad-zu-raw>
|
||||
c:
|
||||
START.EXE
|
||||
```
|
||||
|
||||
### Screenshot-Workflow (headless, ohne echten Desktop)
|
||||
`aria-wohnung` hat keinen laufenden X-Server. Screenshots via
|
||||
`xvfb-run -s '-screen 0 1024x768x24' dosbox ...` + `import -window root`
|
||||
(ImageMagick) im selben xvfb-run-Prozess. Tasteneingaben simulierbar mit
|
||||
`xdotool key <taste>` (kein `--window`-Targeting nötig, funktioniert auch
|
||||
ohne Fenstermanager in Xvfb). Audio läuft im ALSA-nosound-Modus (keine echte
|
||||
Soundkarte im Container/VM) — DOSBox emuliert die SB16 trotzdem korrekt genug
|
||||
fürs IRQ-Timing, es kommt nur kein tatsächlicher Ton raus.
|
||||
|
||||
### Steuerung (aus READ.ME, für spätere Gameplay-Tests)
|
||||
Pfeil links/rechts = laufen, Leertaste/Pfeil-hoch = springen, Pfeil-runter =
|
||||
ducken, ALT = Sonderfunktion/Tür öffnen, P = Pause, ESC = zurück zur Karte
|
||||
(ohne Leben zu verlieren).
|
||||
|
||||
## PCKELL.PRE Container-Format (2026-07-22) — Kernstruktur geknackt
|
||||
|
||||
Ziel: die 178 in `APPEND.LST` gelisteten Dateien (32 BOB, 77 PCC, 22 MAP,
|
||||
22 ARE, 19 ICO, 3 SAM, 3 TFX) sauber aus `PCKELL.PRE` (2.328.934 Bytes,
|
||||
bereits ZIP-entpackt) raustrennen. Tools liegen in `tools/split_pre.py`
|
||||
(aktuellster Stand, laeuft auf `aria-wohnung` mit `python3`, braucht
|
||||
`raw/PCKELL.PRE` + `raw/APPEND.LST` + `raw/SRB.LST`).
|
||||
|
||||
### Zwei Header-Varianten je nach Dateityp
|
||||
|
||||
**`.BOB`-Eintraege (32x, Sprite-Rohdaten):** kein Tag-Slot, Header direkt
|
||||
lueckenlos hintereinander:
|
||||
```
|
||||
[4-Byte-LE-Laenge][NUL-terminierter Original-DOS-Pfad
|
||||
"H:\WIWA\ENGINE\GAMEDATA\<NAME>.BOB"][<Laenge> Bytes Rohdaten]
|
||||
```
|
||||
Danach folgt variables Zero-Padding (~219-226 Byte beobachtet) bis zum
|
||||
naechsten Header. **Vollstaendig validiert: alle 32 BOB-Eintraege exakt
|
||||
gefunden, 0 Namens-Mismatches gegen APPEND.LST.** Robusteste Methode zum
|
||||
Finden: direkte Byte-Suche nach `H:\WIWA\ENGINE\GAMEDATA\<erwarteter Name>`
|
||||
im File (nicht raten, den vollen erwarteten Namen pro Eintrag einzeln
|
||||
suchen — das ist wasserdicht).
|
||||
|
||||
**Alle anderen Typen (PCC/MAP/ARE/ICO/SAM/TFX, 146x):** kein eingebetteter
|
||||
Pfad, dafuer ein **256-Byte-Tag-Slot** VOR dem eigentlichen Header:
|
||||
```
|
||||
[Tag-Slot, exakt 256 Byte lang:
|
||||
1-Byte-Namenslaenge + Dateiname (z.B. 0x0B "CHOCOS7.PCC")
|
||||
+ Rest mit Speicher-Muell des Kompilier-Tools aufgefuellt]
|
||||
[bei Tag-Slot-Start + 256: 4-Byte-LE-Laenge][<Laenge> Bytes Rohdaten]
|
||||
```
|
||||
Bestaetigt an mehreren Stellen (ANTS.PCC, CHOCOS7.PCC, und der
|
||||
"Master"-Tag "A.SRB" ganz am Dateianfang vor A.BOB). **Wichtige Falle:**
|
||||
beim Uebergang von der letzten `.BOB`- zur ersten `.PCC`-Datei (DOORS.BOB
|
||||
-> ANTS.PCC) ueberlappt der Tag-Slot von ANTS.PCC um ~34 Byte in das, was
|
||||
man anhand der BOB-Laengenangabe als "Ende von DOORS.BOB" berechnen wuerde.
|
||||
Grund noch nicht restlos geklaert (vermutlich rundet der Original-Compiler
|
||||
beim Sektionswechsel BOB->PCC anders). Workaround im Skript: Tag-Suche
|
||||
zuerst vorwaerts ab erwarteter Position, bei Fehlschlag Fallback mit
|
||||
300 Byte Sicherheitsabstand rueckwaerts.
|
||||
|
||||
**Kuriosum:** Der Tag fuer `.BOB`-Eintraege (nur beim allerersten,
|
||||
"A.SRB" vor A.BOB) nennt nicht den eigenen Namen, sondern den
|
||||
`.SRB`-Begleitnamen aus `SRB.LST` (1:1 gleiche Reihenfolge wie die
|
||||
BOB-Liste in APPEND.LST). Passt zur bekannten SRB<->BOB-Kopplung
|
||||
(Kollisions-Metadaten in PCKELL.DAT <-> Sprite-Pixeldaten in PCKELL.PRE).
|
||||
|
||||
### PCC-Bildformat (erste Erkenntnisse, noch nicht fertig decodiert)
|
||||
Nutzdaten beginnen mit einem PCX-aehnlichen 16-Byte-Header:
|
||||
```
|
||||
Byte 0: 0x0A (Manufacturer-Marker, wie ZSoft PCX)
|
||||
Byte 1: 0x05 (Version)
|
||||
Byte 2: 0x01 (Encoding = RLE)
|
||||
Byte 3: 0x08 (Bits pro Pixel = 8, also 256 Farben)
|
||||
Byte 4-11: bisher immer 0 gesehen (Xmin/Ymin/Reserved?)
|
||||
Byte 12-13: Breite (LE16), z.B. 0x0140 = 320
|
||||
Byte 14-15: Hoehe (LE16), z.B. 0x00C8 = 200
|
||||
Byte 16+: vermutlich RLE-komprimierte Pixel-Indexdaten (klassisches
|
||||
PCX-RLE: Byte mit oberen 2 Bits gesetzt (0xC0-0xFF) = Lauflaenge
|
||||
(Byte&0x3F), gefolgt vom Wert-Byte; sonst literaler Pixel)
|
||||
```
|
||||
Farbpalette (256x3 Byte RGB) wahrscheinlich NICHT pro Datei (dafuer waeren
|
||||
die kleinen Dateien wie ANTS.PCC mit nur 900 Byte total zu klein) — vermutlich
|
||||
eine globale Palette, die noch gefunden werden muss (Kandidat: irgendwo in
|
||||
RTM.EXE oder als eigene Ressource). TODO fuer naechste Session.
|
||||
|
||||
### PCKELL.PRE Splitter — GELOEST (2026-07-22, ~02:15 Ortszeit)
|
||||
Der vermeintliche "14%-Drift-Bug" war GAR KEIN Bug im Tag-Matching. Diagnose
|
||||
(`tools/diag_split.py`, inzwischen geloescht, siehe Git-History falls noetig)
|
||||
zeigte: jeder der 178 Tags aus `APPEND.LST` wird exakt EINDEUTIG gefunden
|
||||
(genau 1 Kandidat, Gap durchgehend 0, ausser dem bekannten BOB->PCC-
|
||||
Uebergang mit -34 Byte). Der Splitter war also die ganze Zeit korrekt —
|
||||
er endete nur zu frueh, weil **`APPEND.LST` gar nicht alle Eintraege in
|
||||
`PCKELL.PRE` auflistet.**
|
||||
|
||||
Direkt im Anschluss an den letzten `APPEND.LST`-Eintrag (`TITEL2.SAM`,
|
||||
endet bei `0x1e9aac`) folgen, im EXAKT GLEICHEN Tag-Slot-Format, noch
|
||||
**3 weitere, nicht gelistete Eintraege**:
|
||||
```
|
||||
DPMI16BI.OVL 65312 Byte
|
||||
RTM.EXE 108686 Byte
|
||||
START.EXE 148480 Byte
|
||||
```
|
||||
Der letzte davon (`START.EXE`) endet bei `0x238966` — **exakt** die
|
||||
Dateigroesse von `PCKELL.PRE`. Damit ist das Container-Format zu 100%
|
||||
verifiziert, kein einziges Byte bleibt unerklaert. Macht inhaltlich auch
|
||||
Sinn: `PCKELL.PRE` ist offenbar ein self-contained Redistributable-Paket,
|
||||
das die komplette DOS-Extender-Runtime (DPMI-Server + Runtime-EXE +
|
||||
Launcher) gleich mitbuendelt — nicht nur Spiel-Assets.
|
||||
|
||||
**Extra-Validierung:** die 3 extrahierten System-Dateien sind
|
||||
byte-identisch (`cmp`) zu den Einzeldateien, die schon lose in `raw/`
|
||||
liegen (aus der `dos_shareware`-VM kopiert) — zweite unabhaengige
|
||||
Bestaetigung, dass die Extraktion korrekt ist.
|
||||
|
||||
**`tools/split_pre.py` (finale v7) extrahiert jetzt alle 181 Dateien
|
||||
fehlerfrei nach `extracted_pre/`:**
|
||||
```
|
||||
32 BOB, 77 PCC, 22 MAP, 22 ARE, 19 ICO, 3 SAM, 3 TFX, 1 OVL, 2 EXE
|
||||
```
|
||||
Alte Splitter-Zwischenversionen (v1-v6, Diagnose-Scripts) sind auf
|
||||
`aria-wohnung` (`/home/aria/kellogg_remake/tools/`) aufgeraeumt — nur
|
||||
noch `split_pre.py` (v7, final) liegt dort, identisch zur Kopie hier im
|
||||
Projekt unter `tools/split_pre.py`. `extracted_pre/` (2,6 MB, 181 Dateien)
|
||||
liegt jetzt auch hier im Projekt, nicht nur auf `aria-wohnung`.
|
||||
|
||||
### PCC-Header — ergaenzender Fund
|
||||
Bei `MENU.PCC` sind Byte 4-11 NICHT alle Null (anders als bei `ANTS.PCC`
|
||||
vermutet) — sie enthalten `Xmax`/`Ymax` (0x013f=319, 0x00c7=199), passend
|
||||
zum klassischen PCX-Header-Layout `Xmin(2) Ymin(2) Xmax(2) Ymax(2)` bei
|
||||
Offset 4. Fuer ein 320x200-Bild ab (0,0) macht Xmax=319/Ymax=199 Sinn.
|
||||
Bei `ANTS.PCC` (kleines 900-Byte-Sprite-Preview-Bild) stehen dort
|
||||
durchgehend Nullen — vermutlich weil die Original-Autoren dieses Feld
|
||||
bei kleineren generierten Bildern nicht befuellt haben. Fuer den
|
||||
PCC-Decoder erstmal ignorierbar, Breite/Hoehe kommt zuverlaessig aus
|
||||
Offset 12-15.
|
||||
|
||||
### Naechste Schritte (Prioritaet) — ALT, siehe unten "PCC-Format GELOEST" fuer aktuellen Stand
|
||||
~~1. Ein PCC-Bild vollstaendig decodieren~~ ERLEDIGT (siehe unten).
|
||||
2. BOB/SRB-Sprite-Format verstehen (PCKELL.DAT-Offset-Tabelle mit den
|
||||
32 SRB-Namen abgleichen, dann Frame-Struktur pro Sprite ableiten).
|
||||
3. MAP/ARE-Levelformat reversen (kleinste Dateien zuerst, z.B. ein
|
||||
simples frühes Level, z.B. `W1L0.MAP`/`W1L0.ARE`).
|
||||
4. Erst danach: Python-Projektstruktur aufsetzen (pygame), Asset-Loader
|
||||
fuer BOB/PCC/MAP schreiben, Kernschleife (Physik/Kollision) gegen
|
||||
DOSBox-Referenzverhalten abgleichen.
|
||||
|
||||
## PCC-Bildformat — VOLLSTAENDIG GELOEST (2026-07-22, ~02:20 Ortszeit)
|
||||
|
||||
**Format zu 100% verstanden und visuell verifiziert.** PCC = klassisches
|
||||
PCX v5, 256-Farben, mit **eingebetteter Palette PRO DATEI** (kein globales
|
||||
Palette-Rätsel, wie in der vorherigen Notiz befürchtet):
|
||||
|
||||
```
|
||||
Byte 0-15: PCX-Header (Manufacturer=0x0A, Version=5, Encoding=1/RLE,
|
||||
BPP=8, Xmin/Ymin/Xmax/Ymax bei Offset 4-11,
|
||||
Width(LE16)/Height(LE16) bei Offset 12-15 — Width/Height sind
|
||||
DIREKTE Pixelmaße, nicht Xmax-Xmin+1, auch wenn beides meist
|
||||
gleich rauskommt bei 320x200-Vollbildern)
|
||||
Byte 16 .. (len-769): SCANLINE-weise RLE-komprimierte Pixel-Indexdaten.
|
||||
RLE-Tupel: Byte mit oberen 2 Bits gesetzt (0xC0-0xFF) =
|
||||
Lauflaenge (Byte&0x3F), gefolgt von 1 Wert-Byte. Sonst
|
||||
literaler Pixel. WICHTIG: pro Scanline wird bis exakt
|
||||
`width` Pixel dekodiert, ueberschuessige Pixel eines Runs
|
||||
am Zeilenende werden verworfen (klassisches PCX-Verhalten,
|
||||
NICHT als ein durchgehender Bytestrom decodieren — das
|
||||
fuehrt zu Off-by-Overshoot-Fehlern und Fehlausrichtung
|
||||
gegenueber der eingebetteten Palette am Dateiende).
|
||||
Letzte 769 Byte: `0x0C`-Marker + 768 Byte (256 x RGB) Palette — die
|
||||
klassische PCX-v5-256-Farben-Erweiterung. JEDE PCC-Datei
|
||||
traegt ihre EIGENE Palette mit sich (keine globale Palette
|
||||
aus RTM.EXE noetig, wie in der vorigen Session vermutet).
|
||||
```
|
||||
|
||||
**Decoder fertig:** `tools/pcc_to_png.py` (auf `aria-wohnung` unter
|
||||
`/home/aria/kellogg_remake/tools/pcc_to_png.py`, identische Kopie hier
|
||||
im Projekt unter `tools/pcc_to_png.py`). Aufruf:
|
||||
`python3 tools/pcc_to_png.py extracted_pre/<NAME>.PCC out.png`
|
||||
(braucht ImageMagick `convert` auf dem Zielsystem — ist auf `aria-wohnung`
|
||||
vorhanden).
|
||||
|
||||
**Visuelle Verifikation — 100% korrekt:**
|
||||
- `KARTE.PCC` → exakte Weltkarte (Berge/Schloss/Drache, Tony/Tiger,
|
||||
Coco/Affe, Frosch, Fluss, Bäume) — Pixel-für-Pixel wiedererkennbar
|
||||
gegen den DOSBox-Screenshot `aria_kellogg_worldmap.png` von vorher.
|
||||
- `KELLOGGS.PCC` → glasklares "Kellogg's präsentiert"-Logo mit
|
||||
korrektem geschwungenem Markenschriftzug, scharfe Kanten, kein
|
||||
Rauschen/Fehler in der Dekodierung.
|
||||
- `MENU.PCC` → sauberer Menü-Rahmen (blauer Hintergrund, rot-goldene
|
||||
Box) — vermutlich Passwort-/Level-Auswahl-UI.
|
||||
Alle Test-PNGs liegen unter `/shared/uploads/aria_pcc_*.png` zur
|
||||
Ansicht, dauerhafte Kopien aller **77 von 77 PCC-Dateien fehlerfrei
|
||||
batch-decodiert** liegen unter `png_out/` in diesem Projektordner
|
||||
(416 KB total, 77 Dateien — vollstaendige Validierung, kein einziger
|
||||
Absturz oder Format-Ausreisser).
|
||||
|
||||
Einzige kleine Restfrage (kein Blocker): bei manchen Dateien bleiben
|
||||
1-30 Byte am Ende des Pixel-Datenblocks vor der Palette ungenutzt
|
||||
(vermutlich Padding/Rundung des Original-Encoders) — beeinflusst das
|
||||
Bildergebnis nicht sichtbar, wird ignoriert.
|
||||
|
||||
## BOB-Sprite-Format — erste Erkenntnisse (2026-07-22, ~02:30 Ortszeit, IN ARBEIT)
|
||||
|
||||
**Kein PCX-Verwandter.** BOB-Dateien starten NICHT mit `0x0A`-Manufacturer-
|
||||
Byte, kein erkennbarer Klartext-Header wie bei PCC. Vermutlich beginnt die
|
||||
(vermutlich RLE-komprimierte) Pixel-Nutzdaten direkt ab Byte 0.
|
||||
|
||||
**Wichtiger Fund:** Die 32 BOB-Namen sind NICHT alle "richtige" Objektnamen
|
||||
— 14 davon sind Einzelbuchstaben (`A.BOB` .. `O.BOB`, ohne `M`) plus
|
||||
`BLUME.BOB`/`WOLKE.BOB` (Blume/Wolke, Deko-Objekte), dann die Marken-Packs
|
||||
(`VITAMIN`/`COCOPOPS`/`SMACKIES`/`FROSTIES`/`LOOPS`), dann Charaktere
|
||||
(`TONY`/`SMACKS`/`TOUCAN`/`COCO`), dann Level-Objekte (`PLATFW1`=Plattform,
|
||||
`KEYS`, `GAMEBAR`=HUD-Leiste, `KROKO`/`BOSSNAKE`/`DRAGON`=Gegner, `DOORS`).
|
||||
Die Buchstaben-Sprites (A-O) sind vermutlich Sammel-Buchstaben-Items
|
||||
(Bonus-Mechanik: Buchstaben im Level einsammeln) — passend zur Groessen-
|
||||
Gruppierung (viele davon fast gleich gross: B/E/N/O=1295 Byte, D/H/I=4406,
|
||||
J/K=2332 — deutet auf fixe Sprite-Dimensionen pro Buchstaben-"Familie",
|
||||
vermutlich Schriftart-aehnliche Boxen).
|
||||
|
||||
**Byte-Identitaet als Format-Hinweis:** `FROSTIES.BOB`, `COCOPOPS.BOB`,
|
||||
`SMACKIES.BOB` (alle 3323 Byte gross) haben BYTE-FUER-BYTE identische
|
||||
ersten 16 Byte (`16 b4 2c 76 82 b4 3a 9f 06 88 82 cf 16 e8 03 2d`) und
|
||||
divergieren erst ab ca. Offset 0x20. `LOOPS.BOB` (ebenfalls 3323 Byte)
|
||||
zeigt eine um 4 Byte verschobene, aber erkennbar verwandte Bytefolge.
|
||||
**Interpretation:** das ist vermutlich KEIN Klartext-Header, sondern
|
||||
bereits RLE-komprimierte Pixeldaten — die vier Sprites sind alle
|
||||
"Cerealien-Packung"-Objekte mit identischer Kastenform/Silhouette
|
||||
(gleiche Pixel-Auesserform => gleiche RLE-Runs am Anfang), die sich nur
|
||||
im aufgedruckten Marken-Logo unterscheiden (Divergenz kommt spaeter).
|
||||
Das wuerde erklaeren warum 4 komplett verschiedene Marken exakt gleich
|
||||
grosse Dateien mit ident. Praefix erzeugen: gleiche Box-Geometrie,
|
||||
gleiches Kompressionsschema, nur Content-Unterschied in der Bildmitte.
|
||||
|
||||
**Hypothese fuer naechste Session:** BOB = aehnliches RLE-Schema wie PCC
|
||||
(Byte mit oberen 2 Bits gesetzt = Lauflaenge+Wert), aber OHNE PCX-Header
|
||||
und OHNE eingebettete Palette — Breite/Hoehe muessen aus `PCKELL.DAT`
|
||||
kommen (die SRB-Offset-Tabelle, noch nicht durchdrungen) oder aus einer
|
||||
festen Tabelle in `RTM.EXE`. Naechste konkrete Schritte:
|
||||
1. `PCKELL.DAT` Offset-Tabelle vs. `SRB.LST`-Namen abgleichen (32 Eintraege,
|
||||
1:1 mit den 32 BOB-Dateien) — dort vermutlich Breite/Hoehe/Frame-Anzahl
|
||||
pro Sprite zu finden.
|
||||
2. Testweise RLE-Decode von `A.BOB` (kleinstes/einfachstes Beispiel aus der
|
||||
Buchstaben-Gruppe) mit angenommener kleiner Breite/Hoehe (z.B. 20x20 bis
|
||||
40x40 fuer ein UI-Buchstaben-Icon) durchprobieren, gegen erwartete
|
||||
Pixelzahl abgleichen (aehnlich wie beim PCC-Ansatz, der am Ende
|
||||
erfolgreich war: Header-Annahmen iterativ gegen Dateigroesse pruefen).
|
||||
3. Falls das nicht schnell konvergiert: `TONY.BOB` (Hauptfigur, 47726 Byte,
|
||||
groesste Datei nach COCO) ist wahrscheinlich der wichtigste Fund fuer
|
||||
den Port und rechtfertigt tiefere Analyse zuerst.
|
||||
|
||||
## Naechste Schritte (Prioritaet, aktueller Stand)
|
||||
1. **BOB/SRB-Sprite-Format weiter reversen** — siehe Hypothesen direkt
|
||||
oben. Naechster Schritt: `PCKELL.DAT`-Offset-Tabelle mit `SRB.LST`
|
||||
abgleichen, danach `A.BOB`-Testdecoding.
|
||||
2. MAP/ARE-Levelformat reversen (kleinste Dateien zuerst, z.B. ein
|
||||
simples frühes Level, z.B. `W1L0.MAP`/`W1L0.ARE` falls vorhanden —
|
||||
sonst kleinste Datei nach Groesse waehlen).
|
||||
3. Erst danach: Python-Projektstruktur aufsetzen (pygame), Asset-Loader
|
||||
fuer BOB/PCC/MAP schreiben (PCC-Loader ist im Grunde fertig, nur
|
||||
noch von PPM/ImageMagick-Zwischenschritt auf direktes PNG-Schreiben
|
||||
in Python umstellen, z.B. mit `Pillow` — aktuell haengt der Decoder
|
||||
noch am System-`convert`-Tool, das fuer den finalen Python-Port
|
||||
NICHT die Zielarchitektur sein soll).
|
||||
|
||||
### Hinweis fuer Fortsetzung nach Neustart/Session-Wechsel
|
||||
Dieses Projekt laeuft bewusst autonom weiter (Stefan-Wunsch, 2026-07-22).
|
||||
Fortschritt wird nach JEDEM Meilenstein hier in `NOTES.md` gesichert.
|
||||
Zusaetzlich liegt ein gepinnter Kurz-Status im Memory (Suchbegriff
|
||||
"Kellogg's Remake") als Fallback, falls diese Datei mal nicht griffbereit
|
||||
sein sollte. Bei Session-Neustart: einfach hier weiterlesen ab
|
||||
"Naechste Schritte" oben, keine Rueckfrage bei Stefan noetig.
|
||||
|
||||
Ein `trigger_timer` ("kellogg-remake-continue") ist als Selbst-Weck-Kette
|
||||
aktiv: feuert stuendlich, arbeitet den naechsten offenen Punkt ab, speichert
|
||||
hier, legt danach den naechsten Timer an — laeuft ohne Rueckfrage weiter.
|
||||
|
||||
## PCC-Decoder-Bugfix + Git-Repo (2026-07-22, ~09:20 Ortszeit)
|
||||
|
||||
Stefan hat zurecht bemaengelt, dass die PCC-Bilder verzerrt/verrutscht
|
||||
aussehen (z.B. Smacks-Frosch spiegelverkehrt, Kellogg's-Logo mit Fehlstelle).
|
||||
Ergebnis der Untersuchung:
|
||||
|
||||
**GEFUNDEN UND GEFIXT — Width/Height-Bug:** Der Decoder las Breite/Hoehe
|
||||
bisher aus Offset 12-15 (siehe alte Notiz oben). Das ist FALSCH — dieses
|
||||
Feld ist bei allen Dateien ein Konstant-/Platzhalterwert (vermutlich
|
||||
DPI-Reserved-Feld wie im echten 128-Byte-PCX-Header), der bei
|
||||
Vollbild-Screens (320x200) zufaellig mit der echten Groesse
|
||||
uebereinstimmt, bei kleineren Sprites/Logos aber komplett falsch ist.
|
||||
**Richtig:** Breite/Hoehe muessen aus `Xmax-Xmin+1` / `Ymax-Ymin+1`
|
||||
(Offset 4-11) berechnet werden. Beweis: `FACTOR5.PCC` konsumiert mit der
|
||||
alten (falschen) Hoehe 200 nicht alle Bytes sauber, mit der echten Hoehe
|
||||
199 (aus Ymax=198) landet der Byte-Konsum exakt bei 100% (51609/51609).
|
||||
Betroffen/jetzt gefixt: `RAUSER1.PCC` (182x46 statt fälschlich 320x200),
|
||||
`RAUSER2.PCC` (228x21), `RAUSER3.PCC` (16x55), `FAC0-3.PCC` (16x16),
|
||||
`FACTOR5.PCC` (320x199). Fix ist in `tools/pcc_to_png.py` eingebaut,
|
||||
alle 77 PNGs unter `png_out/` mit dem Fix neu generiert.
|
||||
|
||||
**Rauser-Intro: JA, schon extrahiert.** `RAUSER1/2/3.PCC` sind Frames
|
||||
eines scrollenden Text-Logos (vermutlich "RAUSER ADVERTAINMENT PRESENTS"),
|
||||
mit dem Fix jetzt lesbar (zeigt z.B. "...USER TRAI..." als Ausschnitt).
|
||||
Volles Logo muesste sich aus allen 3 Frames zusammensetzen lassen (noch
|
||||
nicht gemacht).
|
||||
|
||||
**FACTOR5.PCC "Geister"-Doppellogo: vermutlich KEIN Bug**, sondern ein
|
||||
Reflexions-/Schatten-Designelement — Byte-Konsum ist exakt 100%, also
|
||||
decodiert der Inhalt komplett korrekt, das Doppelbild ist einfach das,
|
||||
was in der Datei steht.
|
||||
|
||||
**OFFEN — KELLOGGS.PCC/KARTE.PCC-Notch-Artefakt:** Der Width/Height-Fix
|
||||
hat den harmlosen 320x200-Fall nicht veraendert (Xmax/Ymax ergeben dort
|
||||
zufaellig dieselbe Groesse wie vorher). Das Notch-Artefakt (rot/gelber
|
||||
Fleck rechts neben "Kellogg's"-Schriftzug, passt nicht zum
|
||||
DOSBox-Referenz-Screenshot) bleibt bestehen. Ausgeschlossen als Ursache
|
||||
(empirisch getestet, nicht nur vermutet):
|
||||
- Horizontales Rollen/Scrollen des ganzen Bilds (18 Shift-Kandidaten
|
||||
durchprobiert) — Artefakt bleibt IMMER an derselben Stelle relativ zum
|
||||
Bildinhalt (Box/Text), nicht zur Leinwand. Kein Verschiebe-/Wrap-Bug.
|
||||
- Zeilenweises Verwerfen von RLE-Overshoot am Zeilenende — zeilenbasierte
|
||||
und durchgehende (row-boundary-freie) Decodierung liefern fuer
|
||||
KELLOGGS.PCC byte-identische Ergebnisse (kein Overshoot tritt je auf).
|
||||
Interessant: `MENU.PCC` nutzt vermutlich denselben Box-Rahmen (leere
|
||||
Variante) und rendert jetzt SAUBER ohne Notch — der Fehler ist also
|
||||
dateispezifisch, nicht im Rahmen-Design selbst. Naechste Hypothese:
|
||||
Palette-Fehlzuordnung fuer einzelne Indizes ODER RLE-Ambiguitaet bei
|
||||
literalen Pixelwerten >= 0xC0 (werden durch `(b&0xC0)==0xC0` evtl.
|
||||
faelschlich als Lauflaengen-Token statt literaler Indexwert gelesen) —
|
||||
noch zu verifizieren durch gezielten Test mit einem Decoder, der beide
|
||||
Interpretationen an der vermuteten Fehlstelle durchspielt.
|
||||
|
||||
Ebenfalls noch offen (niedrige Prioritaet): `TONY.PCC`/`SMACKS.PCC`
|
||||
liefern mit dem Xmax/Ymax-Fix nur 1x1 Pixel (degenerated, Xmax=Ymax=0
|
||||
im Header) — vermutlich sind das keine "echten" Vollbilder, sondern
|
||||
Platzhalter/ungenutzte Preview-Slots; die eigentlichen Charakter-Sprites
|
||||
stecken hoechstwahrscheinlich in den `.BOB`-Dateien (`TONY.BOB`,
|
||||
`SMACKS.BOB` etc.), nicht in den gleichnamigen `.PCC`-Dateien.
|
||||
|
||||
**Git-Repo eingerichtet:** Auf Stefans Wunsch liegt der Projektstand jetzt
|
||||
unter `https://git.hacker-net.de/Aria-Software/tony-remake`. Bewusst NICHT
|
||||
mit eingecheckt: `raw/` (Original-Spieldateien: PCKELL.DAT/.PRE/.SET,
|
||||
RTM.EXE, START.EXE etc. — das ist buchstaeblich das Original-Spiel) und
|
||||
`extracted_pre/` (die daraus entpackten 181 Rohdateien — noch
|
||||
Original-Asset-Material, nur unverpackt). Mit eingecheckt: `NOTES.md`,
|
||||
`tools/` (unser Code: `split_pre.py`, `pcc_to_png.py`), `dosbox/`
|
||||
(Test-Konfiguration), `png_out/` (unsere konvertierten PNG-Ergebnisse,
|
||||
408 KB, reine Arbeitsergebnisse kein Originalmaterial).
|
||||
@@ -0,0 +1,15 @@
|
||||
[sdl]
|
||||
fullscreen=false
|
||||
output=surface
|
||||
|
||||
[sblaster]
|
||||
sbtype=sb16
|
||||
sbbase=220
|
||||
irq=5
|
||||
dma=1
|
||||
hdma=5
|
||||
|
||||
[autoexec]
|
||||
mount c ~/kellogg_remake/raw
|
||||
c:
|
||||
START.EXE
|
||||
|
After Width: | Height: | Size: 294 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 323 B |
|
After Width: | Height: | Size: 393 B |
|
After Width: | Height: | Size: 393 B |
|
After Width: | Height: | Size: 392 B |
|
After Width: | Height: | Size: 390 B |
|
After Width: | Height: | Size: 371 B |
|
After Width: | Height: | Size: 375 B |
|
After Width: | Height: | Size: 323 B |
|
After Width: | Height: | Size: 323 B |
|
After Width: | Height: | Size: 294 B |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 575 B |
|
After Width: | Height: | Size: 566 B |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 820 B |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 412 B |
|
After Width: | Height: | Size: 417 B |
|
After Width: | Height: | Size: 408 B |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 405 B |
|
After Width: | Height: | Size: 346 B |
|
After Width: | Height: | Size: 405 B |
|
After Width: | Height: | Size: 346 B |
|
After Width: | Height: | Size: 465 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 446 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 426 B |
|
After Width: | Height: | Size: 294 B |
|
After Width: | Height: | Size: 294 B |
|
After Width: | Height: | Size: 294 B |
|
After Width: | Height: | Size: 415 B |
|
After Width: | Height: | Size: 415 B |
|
After Width: | Height: | Size: 415 B |
|
After Width: | Height: | Size: 415 B |
|
After Width: | Height: | Size: 415 B |
|
After Width: | Height: | Size: 415 B |
|
After Width: | Height: | Size: 294 B |
|
After Width: | Height: | Size: 311 B |
|
After Width: | Height: | Size: 311 B |
|
After Width: | Height: | Size: 875 B |
|
After Width: | Height: | Size: 812 B |
|
After Width: | Height: | Size: 826 B |
|
After Width: | Height: | Size: 819 B |
|
After Width: | Height: | Size: 814 B |
|
After Width: | Height: | Size: 787 B |
|
After Width: | Height: | Size: 745 B |
|
After Width: | Height: | Size: 772 B |
|
After Width: | Height: | Size: 362 B |
@@ -0,0 +1,105 @@
|
||||
#!/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 <input.PCC> <output.png>
|
||||
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('<HHHH', data[4:12])
|
||||
width, height = xmax - xmin + 1, ymax - ymin + 1
|
||||
pal_start = len(data) - 769
|
||||
marker = data[pal_start]
|
||||
pixel_region = data[16:pal_start]
|
||||
pal_bytes = data[pal_start+1:pal_start+1+768]
|
||||
palette = [(pal_bytes[i], pal_bytes[i+1], pal_bytes[i+2]) for i in range(0, 768, 3)]
|
||||
|
||||
out = bytearray()
|
||||
i = 0
|
||||
n = len(pixel_region)
|
||||
for row in range(height):
|
||||
row_out = bytearray()
|
||||
while len(row_out) < width 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
|
||||
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()
|
||||
@@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PCKELL.PRE Splitter v7 -- FINAL (2026-07-22)
|
||||
|
||||
Format komplett verstanden:
|
||||
- .BOB-Eintraege (32x): kein Tag-Slot, Header direkt per Pfad-Praefix-Suche
|
||||
gefunden ("H:\\WIWA\\ENGINE\\GAMEDATA\\<name>"), Header = Fundstelle-4.
|
||||
- Alle Nicht-BOB-Eintraege (PCC/MAP/ARE/ICO/SAM/TFX, 146x aus APPEND.LST,
|
||||
PLUS 3 weitere System-Dateien ohne APPEND.LST-Eintrag): Tag-Slot
|
||||
[1-Byte-Namenslaenge][Dateiname][Fuellmuell bis 256 Byte], danach
|
||||
[4-Byte-LE-Laenge][Rohdaten].
|
||||
|
||||
WICHTIGER FUND: APPEND.LST listet nur die 178 Spiel-Assets. PCKELL.PRE
|
||||
enthaelt zusaetzlich, im GLEICHEN Tag-Format, direkt im Anschluss an den
|
||||
letzten APPEND.LST-Eintrag (TITEL2.SAM), noch 3 System-/Runtime-Dateien:
|
||||
DPMI16BI.OVL, RTM.EXE, START.EXE (die DOS-Extender-Runtime -- macht Sinn,
|
||||
das ist ein self-contained Redistributable). Der letzte dieser 3 Eintraege
|
||||
endet EXAKT auf EOF (0x238966) -- damit ist das Format zu 100% verifiziert,
|
||||
keine Bytes bleiben unerklaert.
|
||||
|
||||
Ergebnis: 178 (APPEND.LST) + 3 (System) = 181 Dateien total.
|
||||
"""
|
||||
import struct
|
||||
import os
|
||||
|
||||
RAW = "/home/aria/kellogg_remake/raw/PCKELL.PRE"
|
||||
APPEND_LST = "/home/aria/kellogg_remake/raw/APPEND.LST"
|
||||
OUT_DIR = "/home/aria/kellogg_remake/extracted_pre"
|
||||
|
||||
with open(RAW, "rb") as f:
|
||||
data = f.read()
|
||||
with open(APPEND_LST) as f:
|
||||
names = [l.strip() for l in f if l.strip()]
|
||||
|
||||
# die 3 unlisted System-Dateien, empirisch gefunden direkt im Anschluss
|
||||
EXTRA_NAMES = ["DPMI16BI.OVL", "RTM.EXE", "START.EXE"]
|
||||
|
||||
PATH_PREFIX = b"H:\\WIWA\\ENGINE\\GAMEDATA\\"
|
||||
N = len(data)
|
||||
|
||||
pos = 0x100
|
||||
entries = []
|
||||
all_names = names + EXTRA_NAMES
|
||||
|
||||
for i, name in enumerate(all_names):
|
||||
is_bob = name.upper().endswith(".BOB")
|
||||
if is_bob:
|
||||
marker = PATH_PREFIX + name.encode("ascii")
|
||||
p = data.find(marker, pos)
|
||||
if p == -1:
|
||||
print(f"FEHLER: Pfad fuer #{i} ({name}) nicht gefunden ab {pos:#x}")
|
||||
break
|
||||
header_start = p - 4
|
||||
length = struct.unpack_from("<I", data, header_start)[0]
|
||||
path_start = p
|
||||
nul = data.index(b"\x00", path_start)
|
||||
path = data[path_start:nul].decode("cp437", errors="replace")
|
||||
data_start = nul + 1
|
||||
else:
|
||||
tb = name.encode("ascii")
|
||||
marker = bytes([len(tb)]) + tb
|
||||
tag_pos = data.find(marker, pos)
|
||||
if tag_pos == -1:
|
||||
tag_pos = data.find(marker, max(0, pos - 300))
|
||||
if tag_pos == -1:
|
||||
print(f"FEHLER: Tag fuer #{i} ({name}) nicht gefunden ab {pos:#x}")
|
||||
break
|
||||
header_start = tag_pos + 256
|
||||
length = struct.unpack_from("<I", data, header_start)[0]
|
||||
path = None
|
||||
data_start = header_start + 4
|
||||
|
||||
data_end = data_start + length
|
||||
if data_end > N:
|
||||
print(f"FEHLER: Datenende {data_end:#x} > Dateigroesse bei #{i} ({name}) length={length}")
|
||||
break
|
||||
entries.append((name, header_start, length, data_start, data_end, path))
|
||||
pos = data_end
|
||||
|
||||
print(f"{len(entries)} von {len(all_names)} Eintraegen gefunden.")
|
||||
if entries:
|
||||
print(f"Letztes Datenende: {entries[-1][4]:#x} von {N:#x} (Match: {entries[-1][4] == N})")
|
||||
|
||||
mism = sum(1 for name, hs, length, ds, de, path in entries
|
||||
if path is not None and path.split("\\")[-1].upper() != name.upper())
|
||||
print(f"BOB Pfad-Mismatches: {mism}")
|
||||
|
||||
if len(entries) == len(all_names) and entries[-1][4] == N and mism == 0:
|
||||
os.makedirs(OUT_DIR, exist_ok=True)
|
||||
for name, hs, length, ds, de, path in entries:
|
||||
with open(os.path.join(OUT_DIR, name), "wb") as out:
|
||||
out.write(data[ds:de])
|
||||
print(f">>> ERFOLG: {len(entries)} Dateien geschrieben nach {OUT_DIR}")
|
||||
import collections
|
||||
ext_count = collections.Counter(n.split(".")[-1].upper() for n, *_ in entries)
|
||||
print("Typen:", dict(ext_count))
|
||||
else:
|
||||
print(">>> Unvollstaendig.")
|
||||
for e in entries[-5:]:
|
||||
print(" ", e[0], hex(e[1]), e[2])
|
||||