Ich bin ein Fan des Spiels Perry Rhodan: Operation Eastside. Wegen häufigen Abstürzen und weil Windows bei mir nur in einer VM läuft, dachte ich mir, man könnte das Spiel einmal nachbauen.
Da ich keine Lust habe, eigene Bildchen dafür zu malen und eigene Texte zu schreiben, möchte ich auf die Daten, die das Spiel bereits mitbringt, zugreifen. Deshalb hier eine Übersicht über die Dateiformate, die das Spiel verwendet und meinen aktuellen Fortschritt bzgl. deren Entschlüsselung. Ich möchte betonen, dass sämtliches Reverse-Engineering nur für private Zwecke und ohne externe Informationen erfolgt ist.
Spiel
Sobald etwas Funktionsfähiges entstanden ist, werde ich es hier verlinken.
Hier ein erstes erzeugtes Bild einer zufallsgenerierten Planetenoberfläche mit 16x16 Feldern, jedoch noch ohne dekorative Elemente wie Bäume, Sträucher, Steine, etc.. Unter Benutzung der für diese Zwecke wunderbaren SDL-Bibliothek funktionieren nun schon Anzeige statischer Grafiken (und damit auch Menüs, Texte, Buttons, etc.), der Spiel-eigene Mauszeiger sowie Sounds und Musik.
Dateitypen
Größtenteils bekannt
- Daten/Tabellen: xtRLE (DATA/*.dat, GAMES/*.dat, GAMES/game??/*.plt)
- Audio: PCM (DATA/{Fx,Voc}/*.sfx, SOUND/Track?.sfx), Midi (SOUND/*.mid)
- Video: Smacker Video (DATA/V?0.seq, DATA/Anim/*.seq, VIDEO/*.seq)
- Paletten (DATA/*.pal)
- Bildsammlungen: Palettized Bitmaps (*.sbs)
- Fonts: Palettized Bitmap Fonts (DATA/*.mcf)
- Die Namen der Sonnensysteme werden vom Executable automatisch generiert aus einer Liste von Präfixen und einem oder mehreren Einträgen aus einer Liste an Folgesilben. Beide Listen sind hardcoded in DATA/Rhodan.exe und befinden sich direkt hintereinander an Offsets 0x1092ac und 0x109380.
Bisher unbekannt
- Savegames (GAMES/*.dat, GAMES/game??/*.{dat,msg})
- stars/starinfo.dat auf der CD-ROM
(XOR der Daten mit 0xaa liefert an Position 0x2a den Stringsetup.exe
; weiter habe ich leider noch gar nichts über diese - um von der Byte-Häufigkeit her zu sprechen wahrscheinlich komprimierte - Datei herausfinden können. Ich vermute daher, dass es sich um eine Art Kopierschutz der CD-ROM handelt.
Weiterhin scheint die Datei aus 8 Teilstücken zusammengesetzt zu sein, die alle einen identischen Header der Länge 0x2a aufweisen. Die Größen der ersten 7 betragen je etwas mehr als 216 Bytes, genauer: 0x1000017, 0x1000025, 0x1000010, 0x1000020, 0x1000010, 0x100001e, 0x100000c und 0x2fd912. Tatsächlich sind die ersten 0x3a1234 Bytes aller Teilstücke außer des ersten identisch.)
Formate
Soweit nicht anders gekennzeichnet sind die Bytes aller Datentypen nach Little Endian geordnet.
xtRLE
Der Name xtRLE, den ich aus den ersten 6 Byte der .dat-Dateien habe, referiert höchstwahrscheinlich auf so etwas wie "Extended Run-Length-Encoding" und meint damit eine Erweiterung des "normalen" RLEs, auf die gleich eingegangen wird. Das Spiel Airline Tycoon (ebenfalls von Spellbound) speichert CSV-Tabellen auch im xtRLE-Format. Ich konnte verifizieren, dass diese Dateien mit demselben Schlüssel (0xa5 0x00) zu entpacken sind.
Header
Funktion | Adresse | Größe (in Bytes) | Typ | Wert |
Magic | 0x00 | 6 | char [] | "xtRLE\0" |
unbekannt | 0x06 | 4 | uint32_t | 0x00000201 |
Größe der extrahierten Daten | 0x0a | 4 | uint32_t | variierend |
xtRLE-kodierte Daten | 0x0e | variierend | siehe folgend | variierend |
Nach diesem 14 Byte großen Header folgen ohne weitere Umschweife direkt die Run-Length-kodierten Daten. Generell sind die Daten strukturiert in ein Kommando-Byte ($command) und ein eventuell folgendes Argument (variierender Größe). Es wird eine die Schleifenausführung überdauernde Variable $num verwandt, die sich in zweien der drei Fälle durch $command errechnet, sonst bleibt sie unverändert aus dem letzten Schleifendurchlauf. Es sind folgende drei Varianten sind zu unterscheiden:
Body
$command | $num | Argument | Funktion |
0b 0000 0000 | unverändert | keines | Führe einen Seek aus zu Position (0x0e+$num) in der Datei; schreibe keine Ausgabedaten |
0b 0xxx xxxx | $command & ~0x80 | 1 Byte | Schreibe das Argument-Byte so oft, wie das Kommando-Byte angibt |
0b 1xxx xxxx | $command & ~0x80 | $num Bytes | Übernehme die $num Argument-Bytes unverändert in die Ausgabe |
Offensichtlich gibt es - falls dieses RLE tatsächlich zu einer Komprimierung führen soll - keine Verwendung für die Kommandos 0b 0000 0001 bzw. 0b 1000 0000, weshalb sie in den Dateien auch nicht auftauchen. Statistiken über die Byte-Werte von kodiertem CP-1252-Text zeigen, dass vor allem kurze Sequenzen benutzt werden, die Komprimierung somit also recht ineffizient arbeitet.
Entschlüsselung
Durch diese Dekomprimierung erhält man mittels eines phantasievollen XOR-Schemas verschlüsselte Daten, die sich, wie der folgende Code demonstriert, entschlüsseln lassen. Der Schlüssel hat dabei zwei Bytes, eines für einfach vorkommende Zeichen (chars) und eines für mehrfach hintereinander erscheinende Zeichen. Dabei wird ein einzelnes Zeichen jeweils mit dem passenden Schlüssel-Byte XOR'ed, je nachdem, ob es zu einer Sequenz von mehreren (verschlüsselten) Zeichen desselben Werts gehört.
Hierbei enthalten die Variablen one und two jeweils den Schlüssel für einzeln bzw. mehrfach vorkommende Zeichen.
Die Schlüssel sind bei den .dat-Dateien im Ordner DATA/:
- one = 0xa5 (geraten, bzw. durch Häufigkeitsanalyse erhalten; ich nehme an, der Wert versteckt sich in RHODAN.EXE)
- two = 0x00 (also keine Änderung doppelter Daten-Bytes)
Dieses Dekodierprogramm für xtRLE, geschrieben in C, enthält alle notwendigen Schritte, um eine solche Datei zu dekodieren. Beachtenswert ist jedoch, dass die Daten selbst - zumeist Text - wieder kodiert sind; für die beiden hier genannten Spiele ist das Text-Encoding CodePage 1252. Der Schlüssel ist für beide Spiele derselbe, sodass angenommen werden kann, dass er auch für Daten dieses Formats aus anderer Quelle funktionieren kann.
PCM
Die PCM-Audio-Daten sind einfache, unkomprimierte, signed 16-Bit Samples in Little Endian mit 22050 Hz Rate. Die Dateien in DATA/ enthalten alle nur einen Channel, wohingegen die Hintergrundmusik-Dateien SOUND/Track?.sfx Stereo-Sound bei ebenfalls 22050 Hz gleichen Formats beinhalten.
Smacker Video
Siehe hier.
Alle Videos sind Smacker Videos mit 256 Farben, also einer Palette, sodass sie sich im selben Darstellungsmodus anzeigen lassen wie alle anderen Graphiken des Spiels. Es gibt sowohl Videos ohne Ton, als auch mit Mono- sowie mit Stereo-Sound. In letzteren beiden Fällen ebenfalls wieder 22050 Hz, sodass das Audioausgabeformat mit dem der anderen Sound-Daten des Spiels ebenfalls übereinstimmt, was das Handling einfacher macht.
Paletten
Perry Rhodan: Operation Eastside nutzt eigene Paletten für jeden dargestellten Screen, wobei teils einige Bereiche nicht benutzt werden, die vom Hauptprogramm zur Laufzeit mit passenden Farben für die Fonts aufgefüllt werden.
Aufbau
Header | Table Header | Table |
Details
Funktion | Adresse | Größe (in Bytes) | Typ | Wert |
Pointer to Table Header | 0x00 | 4 | uint32_t | 0x00000024 |
Table size (in Bytes) including the Table Header | 0x04 | 4 | uint32_t | variierend, meist 0x00000400 oder 0x00000404 |
unbekannt, [1] kann der Table-Header-Pointer dupliziert sein | 0x08 | 12 | uint32_t [3] | 0x00000000 0x00000024 0x00000000 |
Maximale Breite (kopiert aus zugehöriger .sbs-Datei?) | 0x14 | 2 | uint16_t | variierend, meist 0x0280 = 640 |
Maximale Höhe (kopiert aus zugehöriger .sbs-Datei?) | 0x16 | 2 | uint16_t | variierend, meist 0x01e0 = 480 |
unbekannt, [0] können die Bits per Pixel sein, [6] können die Anzahl Farbplanes sein | 0x18 | 14 | uint16_t [7] | 0x0008 0x0000 0x0000 0x0000 0x0000 0x0000 0x0003 |
Anzahl Einträge in Table (entspricht offenbar immer (Table size / 4 - 1)) | 0x26 | 2 | uint16_t | variierend, meist 0x00ff oder 0x0100 |
Table [RGBx]; x: Padding, immer 0x00 | 0x28 | variierend | uint8_t [variierend][4] | variierend |
SBS-Bildsammlungen
Perry Rhodan: Operation Eastside arbeitet mit indizierten 8-Bit Bitmaps, d.h. jeder 8-Bit-Wert eines Pixels im Bild ist ein Index in eine zugehörige Farbpalette, die die eigentlichen Farbwerte (RGB, 8+8+8 Bit) enthält. Damit ist nicht der gesamte True-Color-Farbraum von 24 Bit möglich, sondern lediglich 256 Farben können gleichzeitig in einem Bild dargestellt werden. Dies ist dem Alter des Spiels geschuldet, es benutzt schließlich auch den 640x480 Pixel Bildmodus bei 256 Farben.
Unter diesen Voraussetzungen und weil sich das Spiel in einzelne Bildschirmansichten (im Folgenden: Screens) unterteilen lässt, die je völlig unabhängig voneinander sind, wird jeder Screen von einer eigenen Farbpalette und einem Satz (oder mehreren Sätzen) von 8-Bit Bitmaps begleitet, die das Programm an mehr oder weniger feste Positionen auf dem Screen malt.
Die Bildsammlungen sind in den .sbs-Dateien enthalten, die wie folgt aufgebaut sind:
Header | Description Table | Bitmap Data |
Header
Funktion | Adresse | Größe (in Bytes) | Typ | Wert |
Start-Adresse der Description-Table | 0x00 | 4 | uint32_t | 0x00000014 üblicherweise |
unbekannt | 0x04 | 4 | uint32_t | 0x00010000 |
Anzahl enthaltener Bilder | 0x08 | 4 | uint32_t | variierend |
Maximale Breite | 0x0c | 2 | uint16_t | variierend, meist 0x0280 = 640 |
Maximale Höhe | 0x0e | 2 | uint16_t | variierend, meist 0x01e0 = 480 |
unbekannt | 0x10 | 4 | uint32_t | 0x00000000 |
Description Table
Es folgen so viele Enträge in der Description-Table, wie es Bilder in der Datei gibt. Die Einträge haben folgende 24-Byte lange Form und sind nach ihrem Identifikationsstring lexikographisch geordnet:
Funktion | Größe (in Bytes) | Typ | Anmerkung |
Identifikation | 8 | char [8] | bei 8-Byte Länge nicht mehr '\0'-terminiert |
Breite | 2 | uint16_t | |
Höhe | 2 | uint16_t | |
unbekannt | 4 | uint32_t | (0x00000008, evtl. bpp) |
Offset zu Bitmap-Daten | 4 | uint32_t | |
unbekannt | 4 | uint32_t | (0x00000000, evtl. ist das Offset-Feld auch 8 Byte breit) |
Bitmap Daten
Die Bitmap-Daten beginnen an dem im zugehörigen Eintrag in der Description Table angegebenen Offset in der Datei und erstrecken sich über genau (Breite x Höhe) Bytes (in dieser Reihenfolge).
Sie sind mit Hilfe der zur .sbs-Datei gehörigen Palette zu interpretieren und in Farben umzusetzen. Typischerweise ist der Farbindex 0x00 voller Transparenz zuzuordnen.
Notierenswert ist ein in jedem der DATA/*.sbs-Sets speziell benanntes Bild, das entweder
SCREEN
oder (nur im Fall von Title.sbs) TITLE
heißt, die Maximalgröße
im Header der .sbs-Datei durch seine Größe von 640x480 Pixel bestimmt und im zugehörigen Screen
als Hintergrundbild genommen wird. Hier besteht die Ausnahme, dass 0x00 der Palette
folgend als Schwarz (0x000000) benutzt wird, bzw. das Resultat später auf ein schwarzes Bild
projiziert wird.
Fonts
Header | Glyph-Tabelle | Palette | X-advances | cp1252-mapping |
Folgend der Header.
Funktion | Adresse | Größe (in Bytes) | Typ | Wert |
Start-Offset der Glyph-Bitmaps | 0x00 | 2 | uint16_t | 0x2c |
unbekannt | 0x02 | 4 | uint16_t [2] | 0x0100 0x0003 |
Breite | 0x06 | 2 | uint16_t | variierend |
Höhe | 0x08 | 2 | uint16_t | variierend |
unbekannt | 0x0a | 14 | uint16_t [7] | 0x0000 0x0000 0x0008 0x0000 0x0100 0x0020 0x0020 |
ASCII end (e ) | 0x18 | 2 | uint16_t | variierend |
unbekannt | 0x1a | 2 | uint16_t | 0x0000 üblicherweise |
Glyph-Tabellengröße | 0x1c | 4 | uint32_t | variierend |
Palettengröße | 0x20 | 4 | uint32_t | 0x0400 üblicherweise |
Anzahl der Zeichen (n ) | 0x24 | 4 | uint32_t | variierend |
unbekannt | 0x28 | 4 | uint32_t | 0x00000000 |
Das Mapping von cp1252-kodierten Zeichen c
zu Glyph-Index
i
wird wie folgt berechnet. Zunächst wird c
im cp1252-Mapping zu k
übersetzt, dann:
i = k - (e - n + 1).
Das Rendering aller Fonts erfolgt mit einer globalen Modifikation der zugehörigen Paletten. Ersetzt werden folgende Werte:
Index | 0xRRGGBB |
1 | 0xffffff |
2 | 0x3058a7 |
3 | 0x588fdf |
4 | 0x8fc7ff |