Reverse Engineering Workspace für die Wasserzeichen in World of Warcraft Screenshots.
  • C 98.9%
  • Python 0.5%
  • Assembly 0.4%
  • C++ 0.2%
Find a file
2026-06-05 19:53:15 +02:00
archive Weitere Ergänzungen an Gitignore, Altdatei verschoben. 2026-06-03 14:48:20 +02:00
cpp Versionhinweis bzgl. 3.3.5a aufgenommen. 2026-06-05 19:53:15 +02:00
decode_maps Aktualisierung von Skript, Readme, Doku. 2026-06-03 19:12:47 +02:00
doc Weitere Dateien aufgenommen. 2026-06-03 14:46:10 +02:00
encoder Weitere Dateien aufgenommen. 2026-06-03 14:46:10 +02:00
ghidra Initialer commit der ersten Dateien. 2026-06-03 14:35:08 +02:00
images Weitere Dateien aufgenommen. 2026-06-03 14:46:10 +02:00
ldpc LDPC-Ordner aufgenommen, wird vom Skript benötigt. 2026-06-03 19:12:21 +02:00
.gitignore Gitignore aktualisiert. 2026-06-05 19:47:49 +02:00
batch_decode.py Initialer commit der ersten Dateien. 2026-06-03 14:35:08 +02:00
dct_explore.py Initialer commit der ersten Dateien. 2026-06-03 14:35:08 +02:00
decode_cplt.py Initialer commit der ersten Dateien. 2026-06-03 14:35:08 +02:00
decode_maps.py Initialer commit der ersten Dateien. 2026-06-03 14:35:08 +02:00
decoder.py Initialer commit der ersten Dateien. 2026-06-03 14:35:08 +02:00
ldpc_extract.py Initialer commit der ersten Dateien. 2026-06-03 14:35:08 +02:00
readme.md Readme aktualisiert. 2026-06-05 19:22:40 +02:00
Startpunkt.md Initialer commit der ersten Dateien. 2026-06-03 14:35:08 +02:00
TODO.md Weiterer fixer Pilot: 3-Byte Suffix am Ende der Payload (ff3f0f). 2026-06-03 19:49:44 +02:00
wow_icon.png Icon aufgenommen. 2026-06-03 15:28:46 +02:00
wwmdec.py Python-Decoder: Decoding von 800x600 Bildern repariert. 2026-06-05 19:02:17 +02:00

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 713 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 .venv bereits installiert).
  • Die LDPC-Matrix ldpc/big_H.npy muss vorhanden sein (liegt im Repo; falls nicht, mit ldpc_extract.py aus Wow.exe neu 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 (CW0CW7): G = echte Daten dekodiert, . = leer/trivial (legitim für die Null-Padding-Codewörter CW1CW3), 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) oder fmt=host (Hostname). gameTime erscheint 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 dynamic aufrufen — 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 (CW0CW3):
# 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 1148 und die hartcodierte Realm-Adresse 0x0F3FFF00 in Byte 8587), 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
010 Account-Name (Großbuchstaben/Ziffern, ggf. #Version)
1148 Null-Padding (accountName-Buffer-Schwanz)
4963 Realm-Hostname (links­bündig) bzw. Null bei numerischer IP
6467 gameTime (gepackter Zeitstempel, WowEncodeTime) — bei Hostname-Realms vom Hostnamen überschrieben
6879 IP-Oktette (12 ASCII-Ziffern, rechtsbündig) bzw. Hostname-Fortsetzung
8084 Port (5 ASCII-Ziffern, zero-padded)
8587 Realm-Adresse 0x0F3FFF00 (ff 3f 0f)

8. Typische Probleme

  • „no watermark groups found" — Bild kleiner als 800×600 (trägt kein Wasserzeichen) oder --layout dynamic vergessen. 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 --aggregate poolen.
  • gameTime fehlt — der Realm ist ein Hostname; dann ist das gameTime-Feld konstruktionsbedingt überschrieben und nicht rekonstruierbar.

9. Wichtige Dateien