PCC-Decoder-Bugfix: RLE-Runs duerfen Zeilengrenzen ueberschreiten (Row-Reset war falsch)
Stefan hat zurecht bemaengelt dass KARTE.PCC/KELLOGGS.PCC verzerrt/verrutscht
aussehen (Smacks-Frosch an falscher Position, Notch-Fehlstelle im Logo-Rahmen).
Root Cause: Der Decoder hat RLE-Runs am Ende jeder Bildzeile hart abgeschnitten
und den Rest verworfen (klassisches PCX-Verhalten angenommen). Dieses Format
haelt sich aber NICHT an die PCX-Konvention 'ein Run ueberschreitet nie eine
Zeile' -- Runs laufen frei ueber Zeilengrenzen. Verifiziert per Pixel-fuer-Pixel-
Vergleich gegen echten DOSBox-Screenshot (world map): mit durchgehender
Dekodierung (kein Row-Reset) ist das Ergebnis jetzt pixel-identisch zur Referenz.
- tools/pcc_to_png.py: Row-Reset entfernt, decodiert jetzt width*height Pixel
am Stueck aus dem RLE-Strom
- png_out/: alle 77 PNGs mit dem Fix neu generiert
- tools/wip_bob_sprite_research/: Backup der laufenden (noch ungeloesten)
BOB-Sprite-Format-Exploration von aria-wohnung, damit nichts bei einem
VM-Neustart verloren geht
- NOTES.md: Root-Cause-Analyse dokumentiert, alte Fehldiagnose ('byte-identisch
zwischen Row-Reset und kontinuierlich') korrigiert, Rauser-Intro-Frage
beantwortet (3 Frames vorhanden, Text-Logo, keine Video-Datei)
@@ -465,3 +465,85 @@ Original-Asset-Material, nur unverpackt). Mit eingecheckt: `NOTES.md`,
|
|||||||
`tools/` (unser Code: `split_pre.py`, `pcc_to_png.py`), `dosbox/`
|
`tools/` (unser Code: `split_pre.py`, `pcc_to_png.py`), `dosbox/`
|
||||||
(Test-Konfiguration), `png_out/` (unsere konvertierten PNG-Ergebnisse,
|
(Test-Konfiguration), `png_out/` (unsere konvertierten PNG-Ergebnisse,
|
||||||
408 KB, reine Arbeitsergebnisse kein Originalmaterial).
|
408 KB, reine Arbeitsergebnisse kein Originalmaterial).
|
||||||
|
|
||||||
|
## KELLOGGS.PCC/KARTE.PCC-Notch-Artefakt — GELOEST (2026-07-22, ~09:30 Ortszeit)
|
||||||
|
|
||||||
|
**Die Notiz oben ("Ausgeschlossen als Ursache ... Zeilenweises Verwerfen von
|
||||||
|
RLE-Overshoot ... liefern byte-identische Ergebnisse") war FALSCH.** Das war
|
||||||
|
ein Messfehler einer frueheren Session: der "kontinuierliche" Testdecoder
|
||||||
|
hat versehentlich weiterhin die width/height aus Offset 12-15 gelesen (dem
|
||||||
|
VERWORFENEN Bugfix-1-Feld) statt aus Xmax/Ymax — dadurch kam bei den beiden
|
||||||
|
320x200-Vollbildern zufaellig das gleiche (korrekte) Ergebnis raus wie beim
|
||||||
|
zeilenbasierten Decoder, und der Test schien zu bestaetigen, dass beide
|
||||||
|
Methoden aequivalent sind. Sind sie nicht.
|
||||||
|
|
||||||
|
**Neu verifiziert mit einem sauberen Vergleichstest** (`Buffer.compare` in
|
||||||
|
Node.js, unabhaengige Re-Implementierung): zeilenbasiertes Decoding
|
||||||
|
(Row-Reset, discard Overflow am Zeilenende) und durchgehendes Decoding
|
||||||
|
(RLE-Runs duerfen ueber Zeilengrenzen hinweglaufen) liefern **NICHT**
|
||||||
|
identische Ergebnisse — 216 von 64000 Pixeln unterscheiden sich bei
|
||||||
|
KARTE.PCC, 293 von 64000 bei KELLOGGS.PCC. Klingt nach wenig (<0.5%), reicht
|
||||||
|
aber, um ganze Bildbereiche sichtbar zu verschieben, weil ab dem ersten
|
||||||
|
"verlorenen" Ueberlauf-Pixel alle nachfolgenden Zeilen einen Pixel weiter
|
||||||
|
"nachruecken".
|
||||||
|
|
||||||
|
**Beweis per Ground-Truth-Vergleich:** DOSBox-Screenshot
|
||||||
|
`aria_kellogg_worldmap.png` (1024x768 mit schwarzen Balken) exakt auf den
|
||||||
|
reinen Spielbereich zugeschnitten (`convert -fuzz 5% -trim` → 640x400,
|
||||||
|
= 2x 320x200) und auf 320x200 runterskaliert
|
||||||
|
(`/shared/uploads/aria_karte_reference_320.png`). Nebeneinander-Vergleich
|
||||||
|
mit dem durchgehend (ohne Row-Reset) decodierten `KARTE.PCC`
|
||||||
|
(`/shared/uploads/aria_karte_compare2.png`): **pixel-identisch** — Berge,
|
||||||
|
Schloss, Drache, Tiger, Affe/Coco, Frosch, Teich, Haus, Voegel-Ecke, alles
|
||||||
|
exakt an der richtigen Stelle. Das Row-Reset-Verhalten war der einzige
|
||||||
|
Bug; es gibt kein horizontales Rollen, keine Palette-Fehlzuordnung, keine
|
||||||
|
RLE-Ambiguitaet bei Werten >=0xC0.
|
||||||
|
|
||||||
|
**Fix:** `tools/pcc_to_png.py` decodiert jetzt global `width*height` Pixel
|
||||||
|
aus dem RLE-Strom OHNE pro Zeile abzuschneiden (kein Row-Reset mehr). Alle
|
||||||
|
77 PNGs in `png_out/` wurden mit diesem finalen Decoder neu erzeugt
|
||||||
|
(diesmal wirklich verifiziert, nicht nur "sieht plausibel aus"). Kleine
|
||||||
|
Sprites/Logos waren von diesem Bug kaum betroffen (selten ein Run, der eine
|
||||||
|
Zeilengrenze ueberschreitet) — deshalb fiel es dort nie auf, nur bei den
|
||||||
|
zwei grossen 320x200-Vollbildern.
|
||||||
|
|
||||||
|
**Rauser-Intro bestaetigt (Frage von Stefan):** `RAUSER1/2/3.PCC` sind schon
|
||||||
|
laenger extrahiert und liegen jetzt mit dem finalen Decoder sauber decodiert
|
||||||
|
in `png_out/`. Es ist ein Text-Logo "RAUSER" + "ADVERTAINMENT" (Studio-Card),
|
||||||
|
als 3 Bild-Frames — vermutlich fuer einen Scroll-/Fade-Effekt, den die
|
||||||
|
Original-Engine (RTM.EXE) zur Laufzeit zusammensetzt, KEIN Video/keine
|
||||||
|
Animation-Datei. Fuers Remake heisst das: die 3 Frames sind da, die
|
||||||
|
Overlay-/Scroll-Logik muss im Python-Port nachgebaut werden (vermutlich
|
||||||
|
simpel: Frame croppen + ueber Zeit einblenden, sobald wir wissen wie die
|
||||||
|
Original-Sequenz zeitlich ablaeuft — dafuer ggf. DOSBox-Referenzvideo/
|
||||||
|
Screenshots in der Sequenz aufnehmen, spaeterer Schritt).
|
||||||
|
|
||||||
|
## BOB-Sprite-Format — Exploration laeuft, noch nicht geloest (Stand 09:30)
|
||||||
|
|
||||||
|
Auf `aria-wohnung` (`/home/aria/kellogg_remake/tools/`) lief parallel schon
|
||||||
|
einiges an Exploration fuer das naechste Format (BOB-Sprites): Brute-Force-
|
||||||
|
Breitensuche (`bob_width_search.py`), Header-Diff zwischen aehnlich grossen
|
||||||
|
Dateien (`bob_header_diff.py`, `bob_header_search.py`), Histogramm-Analyse
|
||||||
|
(`bob_histogram.py`), Sichtgitter-Rendering mit mehreren Breiten-Kandidaten
|
||||||
|
(`bob_render_grid.py`/`bob_render_grid2.py`), sowie erste Versuche die
|
||||||
|
`PCKELL.DAT`-Offset-Tabelle zu parsen (`dat_analyze.py`/`dat_analyze2.py`,
|
||||||
|
`dat_table2.py`, `dat_u16.py`). **Kein Ergebnis/Durchbruch bisher** — alles
|
||||||
|
noch offene Exploration, keiner der Scripts hat schon eine verifizierte
|
||||||
|
Loesung. Zur Sicherheit (Stefans Wunsch: nichts soll bei einem VM-Neustart
|
||||||
|
verloren gehen) sind alle diese Scripts jetzt zusaetzlich hier im
|
||||||
|
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
|
||||||
|
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).
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 323 B After Width: | Height: | Size: 316 B |
|
Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 360 B |
|
Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 360 B |
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 360 B |
|
Before Width: | Height: | Size: 390 B After Width: | Height: | Size: 360 B |
|
Before Width: | Height: | Size: 371 B After Width: | Height: | Size: 350 B |
|
Before Width: | Height: | Size: 375 B After Width: | Height: | Size: 350 B |
|
Before Width: | Height: | Size: 323 B After Width: | Height: | Size: 316 B |
|
Before Width: | Height: | Size: 323 B After Width: | Height: | Size: 316 B |
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
|
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 575 B After Width: | Height: | Size: 577 B |
|
Before Width: | Height: | Size: 566 B After Width: | Height: | Size: 565 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 325 B |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 325 B |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 325 B |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 325 B |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 325 B |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 325 B |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 325 B |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 325 B |
|
Before Width: | Height: | Size: 322 B After Width: | Height: | Size: 325 B |
|
Before Width: | Height: | Size: 820 B After Width: | Height: | Size: 761 B |
|
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 412 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 417 B After Width: | Height: | Size: 358 B |
|
Before Width: | Height: | Size: 408 B After Width: | Height: | Size: 353 B |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 322 B |
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 319 B |
|
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 322 B |
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 319 B |
|
Before Width: | Height: | Size: 465 B After Width: | Height: | Size: 379 B |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 379 B |
|
Before Width: | Height: | Size: 446 B After Width: | Height: | Size: 381 B |
|
Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 376 B |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 882 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 426 B After Width: | Height: | Size: 420 B |
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
|
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 415 B After Width: | Height: | Size: 355 B |
|
Before Width: | Height: | Size: 294 B After Width: | Height: | Size: 294 B |
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 311 B After Width: | Height: | Size: 311 B |
|
Before Width: | Height: | Size: 875 B After Width: | Height: | Size: 835 B |
|
Before Width: | Height: | Size: 812 B After Width: | Height: | Size: 793 B |
|
Before Width: | Height: | Size: 826 B After Width: | Height: | Size: 804 B |
|
Before Width: | Height: | Size: 819 B After Width: | Height: | Size: 804 B |
|
Before Width: | Height: | Size: 814 B After Width: | Height: | Size: 775 B |
|
Before Width: | Height: | Size: 787 B After Width: | Height: | Size: 777 B |
|
Before Width: | Height: | Size: 745 B After Width: | Height: | Size: 734 B |
|
Before Width: | Height: | Size: 772 B After Width: | Height: | Size: 747 B |
|
Before Width: | Height: | Size: 362 B After Width: | Height: | Size: 361 B |
@@ -1,49 +1,54 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Finaler PCC-Decoder fuer Kellogg's Tony and Friends (2026-07-22, ARIA).
|
"""Finaler PCC-Decoder fuer Kellogg's Tony and Friends (2026-07-22, ARIA).
|
||||||
|
|
||||||
Format (verifiziert gegen MENU.PCC/RAUSER1-3/FACTOR5, visuell + byte-exakt):
|
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):
|
||||||
|
|
||||||
- Byte 0-15: echter PCX-Header-Anfang (Manufacturer=0x0A, Version=5,
|
- 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).
|
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 /
|
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
|
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
|
Vollbild-Screens (320x200) zufaellig identisch aus und wurden erst dafuer
|
||||||
gehalten -- sind aber tatsaechlich NICHT die Bilddimensionen (vermutlich
|
gehalten -- sind aber tatsaechlich NICHT die Bilddimensionen (vermutlich
|
||||||
ein DPI/Reserved-Feld wie im echten 128-Byte-PCX-Header), sondern ein
|
ein DPI/Reserved-Feld wie im echten 128-Byte-PCX-Header), sondern ein
|
||||||
Konstantwert der bei kleinen Sprites/Logos (z.B. RAUSER1.PCC: 182x46,
|
Konstantwert der bei kleinen Sprites/Logos (z.B. RAUSER1.PCC: 182x46,
|
||||||
FAC0.PCC: 16x16) komplett falsch war und zu kaputten/leeren Bildern fuehrte.
|
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
|
- Byte 16 .. (len-769): RLE-komprimierte Pixel-Indexdaten.
|
||||||
statt echten 199) zu ueberlaufen.
|
WICHTIG (Bugfix 2, 2026-07-22): Die RLE-Runs werden NICHT pro Scanline
|
||||||
- Byte 16 .. (len-769): RLE-komprimierte Pixel-Indexdaten, SCANLINE-weise
|
zurueckgesetzt/abgeschnitten. Frueher wurde nach `width` Pixeln pro Zeile
|
||||||
decodiert (pro Zeile wird bis width Pixel gefuellt, ueberschuessige Pixel
|
hart getrimmt und der Rest eines laufenden RLE-Runs verworfen ("row reset").
|
||||||
eines Runs am Zeilenende werden verworfen -- klassisches PCX-Verhalten).
|
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),
|
RLE-Tupel: Byte mit oberen 2 Bits gesetzt (0xC0-0xFF) = Lauflaenge (&0x3F),
|
||||||
gefolgt von einem Wert-Byte. Sonst literaler Pixel.
|
gefolgt von einem Wert-Byte. Sonst literaler Pixel.
|
||||||
|
|
||||||
- Letzte 769 Bytes: 0x0C-Marker + 768 Byte (256 x RGB) eingebettete Palette
|
- Letzte 769 Bytes: 0x0C-Marker + 768 Byte (256 x RGB) eingebettete Palette
|
||||||
(klassische PCX-v5-256-Farben-Erweiterung). PRO DATEI eigene Palette,
|
(klassische PCX-v5-256-Farben-Erweiterung). PRO DATEI eigene Palette,
|
||||||
keine globale Palette noetig.
|
keine globale Palette noetig.
|
||||||
|
|
||||||
BEKANNTER OFFENER BUG (Stand 2026-07-22, noch nicht geloest):
|
GELOEST (frueher "bekannter offener Bug", Stand vor 2026-07-22 Nachmittag):
|
||||||
KELLOGGS.PCC und KARTE.PCC (volle 320x200-Screens) zeigen weiterhin einen
|
Das "Kerbe"/Notch-Artefakt neben dem Kellogg's-Schriftzug und die verrutschten
|
||||||
lokalen Deko-Fehler (ein "Kerbe"/Notch-Artefakt rechts neben dem Kellogg's-
|
Charaktere auf KARTE.PCC waren beide der gleiche Bug (Row-Reset, s.o.), NICHT
|
||||||
Schriftzug, roter/gelber Fleck der nicht ins Referenz-Screenshot passt).
|
ein horizontales Rollen und NICHT eine Palette-Fehlzuordnung. Mit der
|
||||||
Ausgeschlossen als Ursache: (a) horizontales Rollen/Verschieben des ganzen
|
kontinuierlichen Dekodierung ist das Artefakt komplett weg.
|
||||||
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>
|
Nutzung: python3 pcc_to_png.py <input.PCC> <output.png>
|
||||||
Schreibt ein PPM und konvertiert via ImageMagick `convert` zu PNG.
|
Schreibt ein PPM und konvertiert via ImageMagick `convert`/`magick` zu PNG.
|
||||||
"""
|
"""
|
||||||
import sys, struct, subprocess, os
|
import sys, struct, subprocess, os, shutil
|
||||||
|
|
||||||
def decode_pcc(data):
|
def decode_pcc(data):
|
||||||
manuf, version, encoding, bpp = data[0], data[1], data[2], data[3]
|
manuf, version, encoding, bpp = data[0], data[1], data[2], data[3]
|
||||||
@@ -55,25 +60,23 @@ def decode_pcc(data):
|
|||||||
pal_bytes = data[pal_start+1:pal_start+1+768]
|
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)]
|
palette = [(pal_bytes[i], pal_bytes[i+1], pal_bytes[i+2]) for i in range(0, 768, 3)]
|
||||||
|
|
||||||
|
total = width * height
|
||||||
out = bytearray()
|
out = bytearray()
|
||||||
i = 0
|
i = 0
|
||||||
n = len(pixel_region)
|
n = len(pixel_region)
|
||||||
for row in range(height):
|
while len(out) < total and i < n:
|
||||||
row_out = bytearray()
|
|
||||||
while len(row_out) < width and i < n:
|
|
||||||
b = pixel_region[i]; i += 1
|
b = pixel_region[i]; i += 1
|
||||||
if (b & 0xC0) == 0xC0:
|
if (b & 0xC0) == 0xC0:
|
||||||
count = b & 0x3F
|
count = b & 0x3F
|
||||||
if i >= n:
|
if i >= n:
|
||||||
break
|
break
|
||||||
val = pixel_region[i]; i += 1
|
val = pixel_region[i]; i += 1
|
||||||
row_out.extend([val] * count)
|
remaining = total - len(out)
|
||||||
|
out.extend([val] * min(count, remaining))
|
||||||
else:
|
else:
|
||||||
row_out.append(b)
|
out.append(b)
|
||||||
row_out = row_out[:width]
|
if len(out) < total:
|
||||||
if len(row_out) < width:
|
out.extend([0] * (total - len(out)))
|
||||||
row_out.extend([0] * (width - len(row_out)))
|
|
||||||
out.extend(row_out)
|
|
||||||
|
|
||||||
return dict(width=width, height=height, marker_ok=(marker == 0x0C),
|
return dict(width=width, height=height, marker_ok=(marker == 0x0C),
|
||||||
pixels=bytes(out), palette=palette,
|
pixels=bytes(out), palette=palette,
|
||||||
@@ -88,7 +91,9 @@ def write_png(pixels, palette, width, height, out_png):
|
|||||||
r, g, b = palette[px]
|
r, g, b = palette[px]
|
||||||
buf.extend([r, g, b])
|
buf.extend([r, g, b])
|
||||||
f.write(bytes(buf))
|
f.write(bytes(buf))
|
||||||
subprocess.run(['convert', ppm, out_png], check=True)
|
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)
|
os.remove(ppm)
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
base = "/home/aria/kellogg_remake/extracted_pre/"
|
||||||
|
names = ["A","B","C","D","E","F","G","H","I","J","K","L","N","O","BLUME","WOLKE",
|
||||||
|
"VITAMIN","COCOPOPS","SMACKIES","FROSTIES","LOOPS","TONY","SMACKS","TOUCAN",
|
||||||
|
"COCO","PLATFW1","KEYS","GAMEBAR","KROKO","BOSSNAKE","DRAGON","DOORS"]
|
||||||
|
|
||||||
|
N = 128
|
||||||
|
headers = {}
|
||||||
|
for nm in names:
|
||||||
|
path = base+nm+".BOB"
|
||||||
|
data = open(path,"rb").read()
|
||||||
|
headers[nm] = data[:N]
|
||||||
|
print(f"{nm:10s} size={len(data):6d} header={data[:N].hex()}")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("Per-byte-position constant analysis (position: set of distinct values, only show non-constant or show const marker):")
|
||||||
|
for pos in range(N):
|
||||||
|
vals = set(h[pos] for h in headers.values() if len(h) > pos)
|
||||||
|
if len(vals) == 1:
|
||||||
|
print(f"pos {pos:3d}: CONST 0x{next(iter(vals)):02x}")
|
||||||
|
else:
|
||||||
|
print(f"pos {pos:3d}: VARIES ({len(vals)} distinct)")
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import struct
|
||||||
|
|
||||||
|
def decode_scanline(data, width, max_rows=100000):
|
||||||
|
i = 0
|
||||||
|
n = len(data)
|
||||||
|
rows = 0
|
||||||
|
row_len = 0
|
||||||
|
while i < n:
|
||||||
|
if row_len >= width:
|
||||||
|
rows += 1
|
||||||
|
row_len = 0
|
||||||
|
if rows > max_rows:
|
||||||
|
return rows, i, False
|
||||||
|
continue
|
||||||
|
b = data[i]
|
||||||
|
if (b & 0xC0) == 0xC0:
|
||||||
|
count = b & 0x3F
|
||||||
|
i += 1
|
||||||
|
if i >= n:
|
||||||
|
return rows, i, False
|
||||||
|
i += 1
|
||||||
|
need = width - row_len
|
||||||
|
take = min(count, need)
|
||||||
|
row_len += take
|
||||||
|
else:
|
||||||
|
row_len += 1
|
||||||
|
i += 1
|
||||||
|
if row_len == 0:
|
||||||
|
return rows, i, True
|
||||||
|
else:
|
||||||
|
rows += 1 # partial
|
||||||
|
return rows, i, False
|
||||||
|
|
||||||
|
base = "/home/aria/kellogg_remake/extracted_pre/"
|
||||||
|
files = ["B.BOB","E.BOB","N.BOB","O.BOB","A.BOB","J.BOB","K.BOB","D.BOB"]
|
||||||
|
|
||||||
|
for fn in files:
|
||||||
|
data = open(base+fn, "rb").read()
|
||||||
|
n = len(data)
|
||||||
|
print(f"=== {fn} size={n} ===")
|
||||||
|
found_any = False
|
||||||
|
for headerlen in range(0, 17, 1):
|
||||||
|
if headerlen % 2 != 0:
|
||||||
|
continue
|
||||||
|
header = data[:headerlen]
|
||||||
|
body = data[headerlen:]
|
||||||
|
num_u16 = headerlen // 2
|
||||||
|
u16s = struct.unpack(f"<{num_u16}H", header) if headerlen else ()
|
||||||
|
# try every pair (w_idx, h_idx) among header u16 fields as (width,height) candidates
|
||||||
|
for wi in range(num_u16):
|
||||||
|
w = u16s[wi]
|
||||||
|
if w < 2 or w > 400:
|
||||||
|
continue
|
||||||
|
rows, consumed, ok = decode_scanline(body, w)
|
||||||
|
if ok:
|
||||||
|
# check if any header u16 equals rows (=height)
|
||||||
|
match_h = [hi for hi,val in enumerate(u16s) if val == rows]
|
||||||
|
print(f" headerlen={headerlen} width_idx={wi} width={w} -> rows={rows} clean=True header_u16={u16s} height_field_match_idx={match_h}")
|
||||||
|
found_any = True
|
||||||
|
if not found_any:
|
||||||
|
print(" (no clean header+width combo found in range)")
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
def rle_decode_pcx_style(data):
|
||||||
|
out = bytearray()
|
||||||
|
i = 0
|
||||||
|
n = len(data)
|
||||||
|
while i < n:
|
||||||
|
b = data[i]
|
||||||
|
if (b & 0xC0) == 0xC0:
|
||||||
|
count = b & 0x3F
|
||||||
|
i += 1
|
||||||
|
if i >= n:
|
||||||
|
break
|
||||||
|
val = data[i]
|
||||||
|
out.extend([val]*count)
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
out.append(b)
|
||||||
|
i += 1
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
base = "/home/aria/kellogg_remake/extracted_pre/"
|
||||||
|
for fn in ["TONY.BOB","O.BOB","A.BOB"]:
|
||||||
|
data = open(base+fn,"rb").read()
|
||||||
|
dec = rle_decode_pcx_style(data)
|
||||||
|
from collections import Counter
|
||||||
|
c = Counter(dec)
|
||||||
|
print(fn, "distinct values:", len(c), "min", min(dec), "max", max(dec))
|
||||||
|
top = c.most_common(10)
|
||||||
|
print(" top10:", top)
|
||||||
|
below16 = sum(v for k,v in c.items() if k<16)
|
||||||
|
print(f" fraction of pixels <16: {below16/len(dec):.3f} total_pixels={len(dec)}")
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
base = "/home/aria/kellogg_remake/extracted_pre/"
|
||||||
|
names = ["A","B","C","D","E","F","G","H","I","J","K","L","N","O","BLUME","WOLKE",
|
||||||
|
"VITAMIN","COCOPOPS","SMACKIES","FROSTIES","LOOPS","TONY","SMACKS","TOUCAN",
|
||||||
|
"COCO","PLATFW1","KEYS","GAMEBAR","KROKO","BOSSNAKE","DRAGON","DOORS"]
|
||||||
|
|
||||||
|
landmark = bytes.fromhex("7682b43a9f068882")
|
||||||
|
|
||||||
|
for nm in names:
|
||||||
|
path = base+nm+".BOB"
|
||||||
|
data = open(path,"rb").read()
|
||||||
|
idx = data.find(landmark, 0, 64)
|
||||||
|
pre = data[:idx] if idx>=0 else None
|
||||||
|
print(f"{nm:10s} size={len(data):6d} landmark_at={idx:3d} preamble={pre.hex() if pre else 'NOTFOUND'}")
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
from PIL import Image
|
||||||
|
import math
|
||||||
|
|
||||||
|
def rle_decode_pcx_style(data):
|
||||||
|
out = bytearray()
|
||||||
|
i = 0
|
||||||
|
n = len(data)
|
||||||
|
while i < n:
|
||||||
|
b = data[i]
|
||||||
|
if (b & 0xC0) == 0xC0:
|
||||||
|
count = b & 0x3F
|
||||||
|
i += 1
|
||||||
|
if i >= n:
|
||||||
|
break
|
||||||
|
val = data[i]
|
||||||
|
out.extend([val]*count)
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
out.append(b)
|
||||||
|
i += 1
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
base = "/home/aria/kellogg_remake/extracted_pre/"
|
||||||
|
fn = "O.BOB"
|
||||||
|
data = open(base+fn, "rb").read()
|
||||||
|
dec = rle_decode_pcx_style(data)
|
||||||
|
print("decoded len:", len(dec))
|
||||||
|
|
||||||
|
widths = list(range(8, 60, 2))
|
||||||
|
cell_h = 80
|
||||||
|
cell_w = 60
|
||||||
|
cols = 8
|
||||||
|
rows = math.ceil(len(widths)/cols)
|
||||||
|
sheet = Image.new("L", (cols*cell_w, rows*cell_h), 40)
|
||||||
|
|
||||||
|
for idx, w in enumerate(widths):
|
||||||
|
h = (len(dec) + w - 1)//w
|
||||||
|
padded = dec + bytes(w*h - len(dec))
|
||||||
|
img = Image.frombytes("L", (w, h), padded)
|
||||||
|
# scale to fit cell, keep aspect
|
||||||
|
scale = min(cell_w/w, cell_h/h)
|
||||||
|
nw, nh = max(1,int(w*scale)), max(1,int(h*scale))
|
||||||
|
img_r = img.resize((nw, nh), Image.NEAREST)
|
||||||
|
cx = (idx % cols) * cell_w
|
||||||
|
cy = (idx // cols) * cell_h
|
||||||
|
sheet.paste(img_r, (cx, cy))
|
||||||
|
|
||||||
|
sheet = sheet.resize((sheet.width*3, sheet.height*3), Image.NEAREST)
|
||||||
|
outpath = "/home/aria/kellogg_remake/tools/o_bob_grid.png"
|
||||||
|
sheet.save(outpath)
|
||||||
|
print("saved", outpath, "widths:", widths)
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
from PIL import Image
|
||||||
|
import math, sys
|
||||||
|
|
||||||
|
def rle_decode_scanline_clipped(data, width, max_rows=400):
|
||||||
|
"""Decode with PCX-style per-scanline clipping: exactly `width` px per row,
|
||||||
|
overshoot from a run is discarded, continues to next row."""
|
||||||
|
rows = []
|
||||||
|
row = []
|
||||||
|
i = 0
|
||||||
|
n = len(data)
|
||||||
|
while i < n and len(rows) < max_rows:
|
||||||
|
if len(row) >= width:
|
||||||
|
rows.append(row)
|
||||||
|
row = []
|
||||||
|
continue
|
||||||
|
b = data[i]
|
||||||
|
if (b & 0xC0) == 0xC0:
|
||||||
|
count = b & 0x3F
|
||||||
|
i += 1
|
||||||
|
if i >= n:
|
||||||
|
break
|
||||||
|
val = data[i]
|
||||||
|
i += 1
|
||||||
|
need = width - len(row)
|
||||||
|
take = min(count, need)
|
||||||
|
row.extend([val]*take)
|
||||||
|
else:
|
||||||
|
row.append(b)
|
||||||
|
i += 1
|
||||||
|
if row:
|
||||||
|
row.extend([0]*(width-len(row)))
|
||||||
|
rows.append(row)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
base = "/home/aria/kellogg_remake/extracted_pre/"
|
||||||
|
fn = sys.argv[1] if len(sys.argv) > 1 else "O.BOB"
|
||||||
|
skip = int(sys.argv[2]) if len(sys.argv) > 2 else 0
|
||||||
|
data = open(base+fn, "rb").read()[skip:]
|
||||||
|
|
||||||
|
widths = list(range(8, 60, 2))
|
||||||
|
cell_h = 90
|
||||||
|
cell_w = 60
|
||||||
|
cols = 8
|
||||||
|
rows_n = math.ceil(len(widths)/cols)
|
||||||
|
sheet = Image.new("L", (cols*cell_w, rows_n*cell_h), 40)
|
||||||
|
|
||||||
|
for idx, w in enumerate(widths):
|
||||||
|
rows = rle_decode_scanline_clipped(data, w, max_rows=cell_h)
|
||||||
|
h = len(rows)
|
||||||
|
if h == 0:
|
||||||
|
continue
|
||||||
|
flat = bytearray()
|
||||||
|
for r in rows:
|
||||||
|
flat.extend(r)
|
||||||
|
img = Image.frombytes("L", (w, h), bytes(flat))
|
||||||
|
scale = min(cell_w/w, cell_h/h)
|
||||||
|
nw, nh = max(1,int(w*scale)), max(1,int(h*scale))
|
||||||
|
img_r = img.resize((nw, nh), Image.NEAREST)
|
||||||
|
cx = (idx % cols) * cell_w
|
||||||
|
cy = (idx // cols) * cell_h
|
||||||
|
sheet.paste(img_r, (cx, cy))
|
||||||
|
|
||||||
|
sheet = sheet.resize((sheet.width*3, sheet.height*3), Image.NEAREST)
|
||||||
|
outpath = f"/home/aria/kellogg_remake/tools/{fn}_grid_clip_skip{skip}.png"
|
||||||
|
sheet.save(outpath)
|
||||||
|
print("saved", outpath, "widths:", widths)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import sys, struct
|
||||||
|
|
||||||
|
def rle_decode_pcx_style(data):
|
||||||
|
"""Decode assuming classic PCX RLE over the whole byte stream (no scanline boundary)."""
|
||||||
|
out = bytearray()
|
||||||
|
i = 0
|
||||||
|
n = len(data)
|
||||||
|
while i < n:
|
||||||
|
b = data[i]
|
||||||
|
if (b & 0xC0) == 0xC0:
|
||||||
|
count = b & 0x3F
|
||||||
|
i += 1
|
||||||
|
if i >= n:
|
||||||
|
break
|
||||||
|
val = data[i]
|
||||||
|
out.extend([val]*count)
|
||||||
|
i += 1
|
||||||
|
else:
|
||||||
|
out.append(b)
|
||||||
|
i += 1
|
||||||
|
return bytes(out)
|
||||||
|
|
||||||
|
def factor_pairs_near_sqrt(total, tol=0):
|
||||||
|
import math
|
||||||
|
res = []
|
||||||
|
for w in range(1, int(math.isqrt(total))+50):
|
||||||
|
if w == 0: continue
|
||||||
|
if total % w == 0:
|
||||||
|
h = total // w
|
||||||
|
res.append((w,h))
|
||||||
|
return res
|
||||||
|
|
||||||
|
files = ["A.BOB","B.BOB","E.BOB","N.BOB","O.BOB","D.BOB","H.BOB","I.BOB","J.BOB","K.BOB","TONY.BOB"]
|
||||||
|
base = "/home/aria/kellogg_remake/extracted_pre/"
|
||||||
|
|
||||||
|
for fn in files:
|
||||||
|
path = base+fn
|
||||||
|
data = open(path,"rb").read()
|
||||||
|
dec = rle_decode_pcx_style(data)
|
||||||
|
print(fn, "raw_len=",len(data), "decoded_len=",len(dec))
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
def try_decode_scanline(data, width, max_height=2000):
|
||||||
|
"""PCX-style per-scanline RLE decode: for each row, decode exactly `width`
|
||||||
|
pixels (excess from a run discarded), continue until input consumed.
|
||||||
|
Returns (rows_pixels_list, bytes_consumed, ok, error_msg)."""
|
||||||
|
i = 0
|
||||||
|
n = len(data)
|
||||||
|
rows = []
|
||||||
|
row = []
|
||||||
|
while i < n:
|
||||||
|
if len(row) >= width:
|
||||||
|
rows.append(row)
|
||||||
|
row = []
|
||||||
|
if len(rows) > max_height:
|
||||||
|
return rows, i, False, "too many rows"
|
||||||
|
continue
|
||||||
|
b = data[i]
|
||||||
|
if (b & 0xC0) == 0xC0:
|
||||||
|
count = b & 0x3F
|
||||||
|
i += 1
|
||||||
|
if i >= n:
|
||||||
|
return rows, i, False, "truncated run (missing value byte)"
|
||||||
|
val = data[i]
|
||||||
|
i += 1
|
||||||
|
need = width - len(row)
|
||||||
|
take = min(count, need)
|
||||||
|
row.extend([val]*take)
|
||||||
|
# NOTE: if count > need, the overshoot pixels for the *next* row
|
||||||
|
# are DISCARDED per classic PCX semantics (not carried over)
|
||||||
|
else:
|
||||||
|
row.append(b)
|
||||||
|
i += 1
|
||||||
|
if row:
|
||||||
|
# leftover partial row at EOF
|
||||||
|
rows.append(row)
|
||||||
|
return rows, i, False, f"EOF with partial row of {len(row)} px"
|
||||||
|
return rows, i, True, "clean"
|
||||||
|
|
||||||
|
def scan_widths(data, wmin=4, wmax=250):
|
||||||
|
results = []
|
||||||
|
for w in range(wmin, wmax+1):
|
||||||
|
rows, consumed, ok, msg = try_decode_scanline(data, w)
|
||||||
|
# figure out leftover partial-row size from msg if present
|
||||||
|
leftover = None
|
||||||
|
if "partial row of" in msg:
|
||||||
|
leftover = int(msg.split("partial row of")[1].split("px")[0].strip())
|
||||||
|
elif msg == "clean":
|
||||||
|
leftover = 0
|
||||||
|
results.append((w, len(rows), leftover, ok, msg))
|
||||||
|
return results
|
||||||
|
|
||||||
|
base = "/home/aria/kellogg_remake/extracted_pre/"
|
||||||
|
files = ["A.BOB","B.BOB","E.BOB","N.BOB","O.BOB","D.BOB","J.BOB"]
|
||||||
|
for fn in files:
|
||||||
|
data = open(base+fn,"rb").read()
|
||||||
|
print(f"=== {fn} (size={len(data)}) ===")
|
||||||
|
results = scan_widths(data)
|
||||||
|
clean = [r for r in results if r[2]==0]
|
||||||
|
if clean:
|
||||||
|
for r in clean:
|
||||||
|
print(" CLEAN (leftover=0):", r)
|
||||||
|
else:
|
||||||
|
print(" no width in range gives leftover==0")
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import struct, sys
|
||||||
|
|
||||||
|
path = "/home/aria/kellogg_remake/raw/PCKELL.DAT"
|
||||||
|
data = open(path, "rb").read()
|
||||||
|
print("total size:", len(data))
|
||||||
|
|
||||||
|
# Read uint16 LE values until they stop being monotonically non-decreasing
|
||||||
|
vals = []
|
||||||
|
off = 0
|
||||||
|
prev = -1
|
||||||
|
while off + 2 <= len(data):
|
||||||
|
v = struct.unpack_from("<H", data, off)[0]
|
||||||
|
vals.append(v)
|
||||||
|
off += 2
|
||||||
|
if len(vals) > 5000:
|
||||||
|
break
|
||||||
|
|
||||||
|
# find longest monotonic non-decreasing prefix
|
||||||
|
mono_len = 1
|
||||||
|
for i in range(1, len(vals)):
|
||||||
|
if vals[i] >= vals[i-1]:
|
||||||
|
mono_len = i+1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
print("monotonic non-decreasing prefix length (uint16 entries):", mono_len)
|
||||||
|
print("that's byte offset:", mono_len*2)
|
||||||
|
print("first 40 vals:", vals[:40])
|
||||||
|
print("vals around break point:", vals[max(0,mono_len-10):mono_len+10])
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import struct
|
||||||
|
|
||||||
|
path = "/home/aria/kellogg_remake/raw/PCKELL.DAT"
|
||||||
|
data = open(path, "rb").read()
|
||||||
|
|
||||||
|
vals = []
|
||||||
|
off = 0
|
||||||
|
while off + 2 <= len(data):
|
||||||
|
v = struct.unpack_from("<H", data, off)[0]
|
||||||
|
vals.append(v)
|
||||||
|
off += 2
|
||||||
|
|
||||||
|
start = 6 # skip first 6 uint16 (12 bytes) which look like a header
|
||||||
|
mono_len = start+1
|
||||||
|
for i in range(start+1, len(vals)):
|
||||||
|
if vals[i] >= vals[i-1]:
|
||||||
|
mono_len = i+1
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
print("monotonic non-decreasing run starting at idx", start, "-> length", mono_len-start, "entries")
|
||||||
|
print("breaks at idx", mono_len, "byte offset", mono_len*2)
|
||||||
|
print("values around break:", vals[mono_len-5:mono_len+15])
|
||||||
|
print("last mono value:", vals[mono_len-1])
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import struct
|
||||||
|
|
||||||
|
path = "/home/aria/kellogg_remake/raw/PCKELL.DAT"
|
||||||
|
data = open(path, "rb").read()
|
||||||
|
|
||||||
|
# look at region from 580 to 900, print as hex with offsets
|
||||||
|
start = 580
|
||||||
|
end = 900
|
||||||
|
for i in range(start, end, 16):
|
||||||
|
chunk = data[i:i+16]
|
||||||
|
hexs = ' '.join(f'{b:02x}' for b in chunk)
|
||||||
|
print(f'{i:06x}: {hexs}')
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import struct
|
||||||
|
|
||||||
|
path = "/home/aria/kellogg_remake/raw/PCKELL.DAT"
|
||||||
|
data = open(path, "rb").read()
|
||||||
|
|
||||||
|
start = 588
|
||||||
|
end = 900
|
||||||
|
vals = []
|
||||||
|
off = start
|
||||||
|
while off+2 <= end:
|
||||||
|
v = struct.unpack_from('<H', data, off)[0]
|
||||||
|
vals.append((off, v))
|
||||||
|
off += 2
|
||||||
|
|
||||||
|
for off, v in vals:
|
||||||
|
print(off, hex(v), v)
|
||||||