- C 98.9%
- Python 0.5%
- Assembly 0.4%
- C++ 0.2%
| archive | ||
| cpp | ||
| decode_maps | ||
| doc | ||
| encoder | ||
| ghidra | ||
| images | ||
| ldpc | ||
| .gitignore | ||
| batch_decode.py | ||
| dct_explore.py | ||
| decode_cplt.py | ||
| decode_maps.py | ||
| decoder.py | ||
| ldpc_extract.py | ||
| readme.md | ||
| Startpunkt.md | ||
| TODO.md | ||
| wow_icon.png | ||
| wwmdec.py | ||
wwmdec — Decoder für das WoW-Screenshot-Wasserzeichen
wwmdec liest das unsichtbare Wasserzeichen aus World-of-Warcraft-Screenshots
aus und rekonstruiert den eingebetteten ClientStamp: Account-Name, Realm
(Hostname oder IP + Port) und — bei numerischen Realms — den Zeitstempel
(gameTime).
Kurzfassung der Technik: Der Embedder quantisiert den Mittelwert jedes 4×5-Pixel-Blocks auf ein 2,5-Luma-Gitter (bit-0 →
Y mod 2.5 ≈ 0, bit-1 →≈ 1.25) und verteilt die Verschiebung per Bayer-Dither unsichtbar über die Pixel. Die Nutzdaten sind LDPC-fehlerkorrigiert und 3×-redundant pro Makroblock abgelegt, über 7–13 Makroblöcke verteilt. Hintergrund und Reverse-Engineering: siehe Startpunkt.md und TODO.md.
1. Voraussetzungen
- Python 3.13 (im Projekt liegt ein virtuelles Environment unter
.venv). - Pakete:
numpy,Pillow,scipy(im.venvbereits installiert). - Die LDPC-Matrix
ldpc/big_H.npymuss vorhanden sein (liegt im Repo; falls nicht, mitldpc_extract.pyausWow.exeneu erzeugen).
Aufruf-Konvention in dieser Anleitung (Windows):
.venv\Scripts\python.exe wwmdec.py <BILD> [OPTIONEN]
(Unter der Bash kann man stattdessen .venv/Scripts/python.exe schreiben.)
2. Schnellstart — ein einzelnes Bild dekodieren
Für echte JPG-Screenshots ist --layout dynamic --pilot die richtige Wahl:
.venv\Scripts\python.exe wwmdec.py images\scrots\WoWScrnShot_022710_223808.jpg --layout dynamic --pilot
Beispielausgabe:
pilot-constrained LDPC decode (per-codeword pinned source bits + format-adaptive 2nd pass):
codewords: G....JJG (account=ok, realm=ok)
ClientStamp: account='SILENT2' realm=212.72.182.75:8050 (ip, addr=ok) gameTime=2010-02-27 22:37 Sat
So liest man die Ausgabe:
codewords: G....JJG— Status der 8 Codewörter (CW0–CW7):G= echte Daten dekodiert,.= leer/trivial (legitim für die Null-Padding-Codewörter CW1–CW3),J= syndrom-gültig aber Feld-Alphabet verletzt,R= Decodierung fehlgeschlagen.account=ok, realm=ok— das eigentliche Erfolgsmaß: Account (CW0) und Realm (Adresse + Port + konstante Realm-Addr) wurden plausibel rekonstruiert.ClientStamp:— die ausgewerteten Felder.fmt=ip(numerische IP) oderfmt=host(Hostname).gameTimeerscheint nur bei numerischen Realms (bei Hostname-Realms überschreibt der Hostname das gameTime-Feld).
Für das synthetische Test-BMP (
images/WoWScrnShot_091012_114416.bmp, einheitlich blau) ohne--layout dynamicaufrufen — dort funktioniert die Varianz-Autodetektion (--layout auto, der Default).
3. Wenn ein Bild allein nicht reicht — Multi-Image (last resort)
Manche Bilder sind einzeln zu verrauscht (z.B. große, texturarme Nebel-/Wasser-
flächen, in denen das Wasserzeichen im Content untergeht). Wenn man mehrere
Screenshots derselben Session hat (gleicher Account, gleicher Realm), poolt
--aggregate deren Evidenz:
.venv\Scripts\python.exe wwmdec.py shot1.jpg --aggregate shot2.jpg shot3.jpg
Beispiel — die drei einzeln scheiternden Bilder zusammen:
.venv\Scripts\python.exe wwmdec.py images\scrots\WoWScrnShot_012912_152727.jpg ^
--aggregate images\scrots\WoWScrnShot_012912_152732.jpg ^
images\scrots\WoWScrnShot_012912_152737.jpg
multi-image decode: pooled 3 screenshot(s)
codewords: G...GGGG (account=ok, realm=ok)
ClientStamp: account='SILENT2' realm=fairhaven.last-anchor:8085 (host, addr=ok)
Warum es hilft: Das Wasserzeichen ist über die Frames konstant, das Bild-/JPG-Rauschen (z.B. animiertes Wasser) ist pro Frame anders — beim Mitteln verstärkt sich das Wasserzeichen, das Rauschen mittelt sich weg.
Hinweise:
- Account und Realm sind pro Session konstant und werden zuverlässig rekonstruiert.
- Die
gameTime(Minuten-Auflösung) unterscheidet sich nur, wenn die Shots in verschiedenen Minuten aufgenommen wurden — das betrifft dann nur wenige Bits und nicht Account/numerischen Realm/Port. Für maximale Realm-Treue Bilder derselben Minute zusammenfassen.
4. Diagnose-Visualisierungen
4.1 Decode-Map (Codewort-Status)
Färbt jeden Block nach dem Decode-Status seines Codeworts und umrahmt die Hierarchie (Makroblock → Sub-Block/Chunk → Codewort → Block):
.venv\Scripts\python.exe wwmdec.py <BILD> --layout dynamic --pilot --dump-decode-map
Ohne Pfad landet die PNG als <bildname>_decodemap.png neben dem Eingabebild.
4.2 Block-Heatmap (per-Bit-Zuverlässigkeit / Fehlerkarte)
Zeigt auf Bit-Ebene (ein 4×5-Block = 1 Bit), wo das Wasserzeichen lesbar ist:
# Blinder Modus: grün = Block rastet sauber aufs 2.5-Gitter, rot = mehrdeutig
.venv\Scripts\python.exe wwmdec.py <BILD> --layout dynamic --pilot --dump-heatmap
# Wahrheitsgetreue Fehlerkarte, wenn der Account bekannt ist (CW0–CW3):
# grün = Bit korrekt, rot = falsch, Helligkeit = Konfidenz
.venv\Scripts\python.exe wwmdec.py <BILD> --layout dynamic --pilot --dump-heatmap --known-account SILENT2
Ohne Pfad landet die PNG als <bildname>_heatmap.png neben dem Eingabebild.
Wichtiger Caveat zum blinden Modus: Auf Bildern, die scheitern, ist die interne Referenz (der Konsens über alle Makroblöcke) selbst fehlerhaft. Genuin korrekte Regionen können dann fälschlich rot erscheinen. Für eine belastbare Aussage auf solchen Bildern den
--known-account-Modus benutzen.
5. Stapelverarbeitung (ganze Verzeichnisse)
5.1 Tabellen-Report über alle Screenshots
.venv\Scripts\python.exe batch_decode.py --dir images\scrots
Optionen: --limit N (nur die ersten N), --workers K (Parallelität),
--save-payloads DIR (rohe Payloads ablegen), --maps [DIR] (zusätzlich
Decode-Maps schreiben und nach „am meisten rot" ranken).
5.2 Decode-Maps für ein ganzes Verzeichnis
.venv\Scripts\python.exe decode_maps.py --dir images\scrots --out decode_maps
Schreibt pro Bild eine *_decodemap.png und einen Ranking-Report (von „am
schwersten" bis „voll dekodiert").
6. Decoder-Methoden (Übersicht der wichtigsten Flags)
| Flag | Zweck |
|---|---|
--layout dynamic |
Makroblock-Geometrie aus den Bildmaßen rekonstruieren (für echte Screenshots immer nötig; passt sich an 1280×1024, 1920×1080 etc. an). |
--layout auto |
Varianz-basierte Autodetektion (nur für das synthetische Test-BMP). Default. |
--pilot |
Empfohlener Decoder für echte JPGs: pinnt die bekannten Konstanten als Pilot-Bits (Null-Padding-Bytes 11–48 und die hartcodierte Realm-Adresse 0x0F3FFF00 in Byte 85–87), macht einen format-adaptiven 2. Pass für den Realm, nutzt bei 1080p die partielle 3. Wiederholung (Best-of-Both) und fällt für den Account auf eine Group-Subset-Suche zurück. Gibt direkt den ausgewerteten ClientStamp aus. |
--aggregate IMG … |
Multi-Image-Last-Resort (siehe Abschnitt 3). |
--grid |
Reiner 2.5-Luma-Gitter-Decoder ohne Pilots (perfekt auf dem BMP). |
--account |
ML-Beam-Suche nur über den Account (CW0), mit Konfidenz-Maß. |
--constraints |
Zusätzliche Feld-Alphabet-Bits pinnen (opt-in). Nur als Validator sinnvoll, als Decode-Pins bringen sie auf schweren Bildern nichts und bergen Falsch-Positiv-Risiko — daher standardmäßig aus. |
--out PATH |
Rohe dekodierte Payload-Bytes in eine Datei schreiben. |
Die ältere Methoden --ldpc, --mf, --var, --collapse-payload,
--highpass existieren weiterhin für Experimente; für den Produktivgebrauch ist
--layout dynamic --pilot (Einzelbild) bzw. --aggregate (Multi-Image) die
richtige Wahl.
Vollständige Liste aller Optionen:
.venv\Scripts\python.exe wwmdec.py --help
7. Das 88-Byte-ClientStamp-Layout (verifiziert)
| Offset | Inhalt |
|---|---|
0–10 |
Account-Name (Großbuchstaben/Ziffern, ggf. #Version) |
11–48 |
Null-Padding (accountName-Buffer-Schwanz) |
49–63 |
Realm-Hostname (linksbündig) bzw. Null bei numerischer IP |
64–67 |
gameTime (gepackter Zeitstempel, WowEncodeTime) — bei Hostname-Realms vom Hostnamen überschrieben |
68–79 |
IP-Oktette (12 ASCII-Ziffern, rechtsbündig) bzw. Hostname-Fortsetzung |
80–84 |
Port (5 ASCII-Ziffern, zero-padded) |
85–87 |
Realm-Adresse 0x0F3FFF00 (ff 3f 0f) |
8. Typische Probleme
- „no watermark groups found" — Bild kleiner als 800×600 (trägt kein
Wasserzeichen) oder
--layout dynamicvergessen. 800×600 selbst wird über die hartcodierte 4-Regionen-Tabelle (FUN_004a8fe0) unterstützt. - Account/Realm = Müll auf einem realen JPG — texturreiche/dunkle Szene,
Einzelbild reicht nicht → mehrere Session-Bilder per
--aggregatepoolen. gameTimefehlt — der Realm ist ein Hostname; dann ist das gameTime-Feld konstruktionsbedingt überschrieben und nicht rekonstruierbar.
9. Wichtige Dateien
- wwmdec.py — Hauptdecoder (CLI + alle Decode-Methoden).
- batch_decode.py / decode_maps.py — Stapelverarbeitung.
- ldpc_extract.py — LDPC-Matrizen aus
Wow.exeextrahieren. - encoder/readable.c — lesbare C-Rekonstruktion des Embedders.
- TODO.md / Startpunkt.md — Reverse-Engineering-Doku und Erkenntnisse.