18.04.2011, 17:39; Update: 07.12.2020, 04:16

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.

Bild einer Planetenoberfläche mit 16x16 Feldern, verkleinert um Faktor 2 Titelbildschirm mit Buttons und Mauszeiger

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 String setup.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

FunktionAdresseGröße (in Bytes)TypWert
Magic0x006char []"xtRLE\0"
unbekannt0x064uint32_t0x00000201
Größe der extrahierten Daten0x0a4uint32_tvariierend
xtRLE-kodierte Daten0x0evariierendsiehe folgendvariierend

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$numArgumentFunktion
0b 0000 0000unverändertkeinesFühre einen Seek aus zu Position (0x0e+$num) in der Datei; schreibe keine Ausgabedaten
0b 0xxx xxxx$command & ~0x801 ByteSchreibe 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

HeaderTable HeaderTable

Details

FunktionAdresseGröße (in Bytes)TypWert
Pointer to Table Header0x004uint32_t0x00000024
Table size (in Bytes) including the Table Header0x044uint32_tvariierend, meist 0x00000400 oder 0x00000404
unbekannt, [1] kann der Table-Header-Pointer dupliziert sein0x0812uint32_t [3]0x00000000 0x00000024 0x00000000
Maximale Breite (kopiert aus zugehöriger .sbs-Datei?)0x142uint16_tvariierend, meist 0x0280 = 640
Maximale Höhe (kopiert aus zugehöriger .sbs-Datei?)0x162uint16_tvariierend, meist 0x01e0 = 480
unbekannt, [0] können die Bits per Pixel sein, [6] können die Anzahl Farbplanes sein0x1814uint16_t [7]0x0008 0x0000 0x0000 0x0000 0x0000 0x0000 0x0003
Anzahl Einträge in Table (entspricht offenbar immer (Table size / 4 - 1))0x262uint16_tvariierend, meist 0x00ff oder 0x0100
Table [RGBx]; x: Padding, immer 0x000x28variierenduint8_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:

HeaderDescription TableBitmap Data

Header

FunktionAdresseGröße (in Bytes)TypWert
Start-Adresse der Description-Table0x004uint32_t0x00000014 üblicherweise
unbekannt0x044uint32_t0x00010000
Anzahl enthaltener Bilder0x084uint32_tvariierend
Maximale Breite0x0c2uint16_tvariierend, meist 0x0280 = 640
Maximale Höhe0x0e2uint16_tvariierend, meist 0x01e0 = 480
unbekannt0x104uint32_t0x00000000

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:

FunktionGröße (in Bytes)TypAnmerkung
Identifikation8char [8]bei 8-Byte Länge nicht mehr '\0'-terminiert
Breite2uint16_t
Höhe2uint16_t
unbekannt4uint32_t(0x00000008, evtl. bpp)
Offset zu Bitmap-Daten4uint32_t
unbekannt4uint32_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.

FunktionAdresseGröße (in Bytes)TypWert
Start-Offset der Glyph-Bitmaps0x002uint16_t0x2c
unbekannt0x024uint16_t [2]0x0100 0x0003
Breite0x062uint16_tvariierend
Höhe0x082uint16_tvariierend
unbekannt0x0a14uint16_t [7]0x0000 0x0000 0x0008 0x0000 0x0100 0x0020 0x0020
ASCII end (e)0x182uint16_tvariierend
unbekannt0x1a2uint16_t0x0000 üblicherweise
Glyph-Tabellengröße0x1c4uint32_tvariierend
Palettengröße0x204uint32_t0x0400 üblicherweise
Anzahl der Zeichen (n)0x244uint32_tvariierend
unbekannt0x284uint32_t0x00000000

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:

Index0xRRGGBB
10xffffff
20x3058a7
30x588fdf
40x8fc7ff