Outpost 2 Dateiformate · bei.pm
Die auf dieser Seite beschriebenen Dateiformate basieren auf der technischen Analyse geistigem Eigentums von Dynamix, Inc. und Sierra Entertainment.
Das geistige Eigentum ist heute Teil der Activision Publishing, Inc.- / Activision Blizzard, Inc.-Masse und derzeit im Besitz von Microsoft Corp..
Die Informationen wurden durch Reverse Engineering und Datenanalyse zum Zwecke der Archivierung und Interoperabilität mit historischen Daten zusammengetragen.
Es wurden keine proprietären oder vertraulichen Spezifikationen verwendet.
Das Spiel kann derzeit bei gog.com käuflich als Download erworben werden.
Die folgende Artikel-Reihe dokumentiert meine Erkenntnisse zu den Datenformaten im Echtzeit-Strategiespiel "Outpost 2: Geteilte Bestimmung", welches 1997 durch Sierra veröffentlicht und von Dynamix entwickelt wurde.
Ich habe mich etwa vom 01. November 2015 bis zum 14. November 2015 vorranging mit der Analyse der Daten des Spieles - und was es damit anfängt - beschäftigt.
Den Informationen, die ich bislang gewinnen konnte, zufolge hat Dynamix - wie so viele kommerzielle Unternehmen - einige Datenformate nicht speziell für Outpost 2 entwickelt, sondern auch in anderen Entwicklungen wie zum Beispiel der Mechwarrior-Serie (abgewandelt) verwendet.
Unabhängig davon lässt sich zudem feststellen, dass sich die Innovationskraft der Dateiformate praktisch in Grenzen hält und häufig auf länger bestehenden Konzepte aus üblichen Formaten wie JFIF und RIFF aufgebaut wird.
Zur Interpretation der Tabellen und Datenformate stehen weitere Informationen unter Was ist was? bereit.
Die hier angegebenen Daten sind im Allgemeinen als Little Endian zu verstehen.
Abschließend lässt sich sagen, dass das Reverse Engineering sehr viel Spaß bereitete, auch wenn es nicht vollständig ist.
Natürlich kann ich auch nur empfehlen, das Spiel selbst zu spielen, da es interessante Spielmechaniken bietet.
Einführung
Die von Outpost 2 verwendeten Datenformate haben einen an JFIF / PNG erinnernden Aufbau - die einzelnen Datenblöcke verfügen dabei immer über einen 8 Byte Header. Daher spare ich es mir, die einzelnen Header an den entsprechenden spezifischen Stellen zu dokumentieren und dokumentiere dort nur Abweichungen.
Das Format ist immer das folgende; die eigentlichen Nutzdaten sind dann darin eingebettet:
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | Enthält die Information darüber, was im nächsten Datenblock zu erwarten ist. Bekannte Werte:
|
0x0004 | uint(24) | Block-Länge | Enthält die Information darüber, wie groß (in Byte) der folgende Datenblock ist. Dabei sind die reinen Nutzdaten gemeint - die 8 Header-Byte sind darin nicht enthalten. |
0x0007 | uint(8) | Flags? | Es ist unbekannt, wozu dieser Block genau dient. In den Volumes ist dieser Wert häufig 0x80, in anderen Dateien häufig 0x00. Das legt nahe, dass es sich um ein Flag-Set handelt. |
Volumes
Bei den Volumes handelt es sich um einen Datencontainer für das Spiel, ähnlich zu einem Archivformat wie z.B. Tarball. Zumindest in Outpost 2 kennt das Format dabei lediglich Dateien - keine Ordner. Wahrscheinlich ließen sich diese allerdings über entsprechende Dateinamen simulieren.
Ein Volume besteht dabei aus dem Volume-Header sowie aus mehreren Volume Blöcken, die den konkreten Dateien entsprechen.
"Volumes" sind die Dateien mit der Endung 'vol'
im Spielverzeichnis.
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 56 | 4f | 4c | 20 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | V | O | L | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Block-Länge | |
0x0007 | uint(8) | Flags |
Volume Header
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 76 | 6f | 6c | 68 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | v | o | l | h | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Block-Länge | |
0x0007 | uint(8) | Flags |
Der Volume Header enthält seinerseits keinerlei Nutzdaten.
Er dient lediglich als Container.
Als erstes Datum im Volume Header sollten sich die Volume Strings finden; darauf folgen dann die Volume-Informationen.
Volume Strings
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 76 | 6f | 6c | 69 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | v | o | l | i | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Block-Länge | |
0x0007 | uint(8) | Flags |
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 76 | 6f | 6c | 73 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | v | o | l | s | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Block-Länge | |
0x0007 | uint(8) | Flags | |
0x0008 | uint(32) | Payload-Länge | Gibt an, wieviele Bytes der folgenden Daten tatsächlich Nutzdaten sind. Die restlichen, verbleibenden Daten der Volume-Strings-Liste sind offenbar als garbage zu werten. In Dateien mit späterem Datum sind diese 'verbleibenden Daten' 0x00, was auf Unzulänglichkeiten mit der Toolchain während der Entwicklung des Spieles deuten könnte, sprich, dass sich erst sehr spät ein Entwickler um die korrekte Initialisierung der Puffer gekümmert hat, da es keinen Einfluss auf das Spiel hat, ob die Daten initialisiert sind oder nicht. |
0x000c | uint(8)[] | Dateinamen-Liste | Hierbei handelt es sich um eine 0-Byte-Terminierte Liste von Dateinamen, die - zumindest im vorliegenden Datenbestandteil - lediglich ASCII-Zeichen erwarten lässt. Es ist nicht nötig, beim Parsen der Daten diesen Datenblock genauer auszuwerten, da in den Volume-Informationen ohnehin direkt die Offsets der Dateinamen referenziert werden. |
Bei den Volume Strings handelt es sich um eine Liste von Dateinamen, die innerhalb des Volumes enthalten sind.
Volume Informationen
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 76 | 6f | 6c | 69 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | v | o | l | i | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Block-Länge | |
0x0007 | uint(8) | Flags |
Die Volume-Informationen nehmen detailliertere Informationen zu den Dateien auf. Dabei handelt es sich in gewisser Weise um eine Art FAT-Verzeichniseintrag (FAT = File Allocation Table)
Die Anzahl an Dateien ergibt sich aus der Blockgröße geteilt durch die Länge der Verzeichniseinträge - 14 Byte.
Die einzelnen Verzeichniseinträge haben dabei jeweils folgenden Aufbau:
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Dateinamen-Offset | Gibt an, an welchem Offset (!) innerhalb der Dateinamensliste (Volume-Strings) der Dateiname der Datei findet. Bezieht sich dabei auf den Anfang des Nutzdatenblocks. |
0x0004 | uint(32) | Datei-Offset | Gibt an, an welchem Offset innerhalb der gesamten Volume-Datei sich die Datei befindet. |
0x0008 | uint(32) | Datei-Größe | Gibt an, wie groß die Datei in Byte ist. |
0x000c | uint(16) | Flags? | Gibt offenbar zusätzliche Informationen über die Dateikodierung an.
|
Volume Block
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 56 | 42 | 4c | 48 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | V | B | L | H | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Block-Länge | |
0x0007 | uint(8) | Flags |
Bei einem Volume-Block handelt es sich um einen Container, der Dateien aufnimmt. Er enthält lediglich noch einmal - aufgrund des Blockformates - redundant die Dateigröße und anschließend folgen direkt die Nutzdaten.
Tiles
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 50 | 42 | 4d | 50 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | P | B | M | P | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Block-Länge | |
0x0007 | uint(8) | Flags |
Bei den Tiles handelt es sich um ein Outpost-2-Spezifisches
Bitmap-Grafikformat. Sie erstrecken sich über 13 Tilesets,
"wells" genannt (well0000.bmp
bis well0012.bmp
),
die sich innerhalb des Volumes maps.vol befinden.
Dabei enthalten die Tilesets / Wells folgendes:
Dateiname | Inhalt |
---|---|
well0000.bmp | Eine 32x32px große, blaue Grafik - ideal als Test, ob der eigene Image-Loader funktioniert |
well0001.bmp | Enthält helles Gestein, Gebirgszüge auf hellem Gestein und unzählige Varianten von Einschlagskratern in helles Gestein |
well0002.bmp | Enthält helles-Gestein-'Doodads' - also Elemente, die zur Auflockerung (oder bewusst als Struktur, wie zB Mauern) auf hellem Gestein platziert werden können, darunter auch Vegetation |
well0003.bmp | Enthält eine krustenartige Struktur auf hellem Gestein |
well0004.bmp | Enthält dunkles Gestein, Gebirgszüge auf dunklem Gestein und unzählige Varianten von Einschlagskratern in dunkles Gestein |
well0005.bmp | Enthält dunkles-Gestein-'Doodads' - also Elemente, die zur Auflockerung (oder bewusst als Struktur, wie zB Mauern) auf dunklem Gestein platziert werden können |
well0006.bmp | Enthält eine Krustenartige Struktur auf dunklem Gestein, sowie Übergänge zwischen hellem und dunklem Gestein |
well0007.bmp | Enthält Lava inklusive jeweils 4-5 Frames Animation der selbigen |
well0008.bmp | Enthält Sand und unzählige Varianten von Einschlagskratern in Sand |
well0009.bmp | Enthält Sand-'Doodads' - also Elemente, die zur Auflockerung (oder bewusst als Struktur, wie zB Mauern) auf Sand platziert werden können |
well0010.bmp | Enthält je 48 Übergänge von Sand zu hellem und dunklem Gestein |
well0011.bmp | Enthält die Polarkappen der Map, mit dunklem Gestein als Untergrund |
well0012.bmp | Enthält die Polarkappen der Map, mit hellem Gestein als Untergrund |
Es erschreit ratsam für eine akkurate Umsetzung, die Tiles nicht im Vorraus zu rendern um diese zu cachen, da die Daten für den Tag/Nacht-Zyklus noch bearbeitet werden müssen - und sehr sehr viele Daten anfallen würden.
Die Tiles sind 8bpp-Grafiken mit indexierter Palette zu je 32x32 Pixel Auflösung, welche untereinander angeordnet sind. In einem so entstandenen Tileset können allerdings weitaus mehr
Der Haupt-Container besteht aus 2 Sektionen: head
und data
.
Tiles Header
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 68 | 65 | 61 | 64 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | h | e | a | d | . | . | . | . | . | . | . | . | . | . | . | . |
0x0010 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Block-Länge | |
0x0007 | uint(8) | Flags | |
0x0008 | uint(32) | Version / Flags? | Hierbei könnte es sich um eine Versionsangabe des Dateiformates handeln; in allen mir vorliegenden Dateien stand hier der Wert |
0x000c | uint(32) | Breite (Horizontale Auflösung) | Gibt an, wie breit die Bilddatei ist (in Pixel). Bei allen Wells von Outpost 2 wird hier der Wert |
0x0010 | uint(32) | Höhe (Vertikale Auflösung) | Gibt an, wie hoch die Bilddatei ist (in Pixel). Bei allen Wells von Outpost 2 wird hier der Wert |
0x0014 | uint(32) | Farbtiefe? | Die Bedeutung dieses Wertes ist unbekannt. Da er in allen geprüften Dateien den Wert |
0x0018 | uint(32) | Farbtiefe 2? | Die Bedeutung dieses Wertes ist unbekannt. Möglicherweise handelt es sich um eine 'Ziel'-Farbtiefe. |
Nach diesen Angaben erfolgt noch eine im standardisierten RIFF-Format vorliegende Palettendatei. Die genaue Spezifikation findet sich - da die Paletten auch anderswo auftauchen - unter Paletten.
Tiles Daten
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 64 | 61 | 74 | 61 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | d | a | t | a | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Block-Länge | |
0x0007 | uint(8) | Flags |
Schließlich folgen bereits die blanken Pixeldaten, von links-oben zeilenweise nach rechtsunten.
Der Datenwert bei den in aller Regel als 8bpp-Bitmaps vorliegenden Grafiken entspricht dabei dem Index der Farbe in der Farbpalette.
Die Spiel-Engine zeichnet die Tiles dabei *wahrscheinlich* on-demand.
Dies scheint unter anderem dem Tag-Nacht-Zyklus geschuldet zu sein, der 32 Abstufungen einzelner Tiles kennt. Dabei wird offenbar vom Helligkeitswert jeweils 'ein wenig' abgezogen. Genaue Werte ließen sich noch nicht ermitteln, ich arbeite auf der Berechnungsgrundlage
v *= (daylight / 48) + 0.25;
mit den HSV-Daten der Pixel, wobei daylight ein Wert von 0-31 ist und v ein Wert zwischen 0-1. Darüber hinaus ist zu berücksichtigen, dass auf der Map jeweils noch ein Rand von 16 Tiles nach links und rechts (der dient dem unsichtbaren Spawnen von Einheiten) existiert.
Zusätzlich scheint der Tag-Nacht-Zyklus pro Gamecycle jeweils nur eine Spalte der Map zu aktualisieren.
Ein beschleunigter Tag-Nacht-Zyklus sieht daher wie folgt aus:
PRT
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 43 | 50 | 41 | 4c | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | C | P | A | L | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Paletten-Länge | Gibt, entgegen zum normalen Blockformat, die Anzahl an in dieser Datei zu findenden Paletten an - nicht die Länge des Blocks in Byte. |
0x0007 | uint(8) | Flags | Wahrscheinlich, wie üblich, Flags. Mir sind allerdings keine Flags bekannt; da alle mir bekannten Werte |
Wofür PRT
genau steht ist mir nicht bekannt; denkbar wäre beispielsweise 'Palette and Ressource Table' - da es sich bei dieser Datei - zu finden als op2_art.prt in der maps.vol - um eine solche handelt, bzw dies die Funktion ganz gut beschreiben würde.
Diese Datei enthält dabei eine Liste von Paletten, eine Tabelle über alle verwendeten Bitmaps, alle Animationsdefinitionen und noch eine Reihe von unbekannten Daten. Sie folgt dem bisherigen Containerformat lose, da nicht alle Datensätze diesem Schema folgen.
Die CPAL
-Sektion (steht wahrscheinlich für Paletten-Container) umschließt dabei lediglich die Palettendaten, indem es angibt, wieviele der jeweils üblicherweise 1052 Byte großen 8-Bit-Paletten vorhanden sind.
Die 1052-Byte-Angabe gilt dabei nicht als verbindlich, da das Palettenformat potentiell unterschiedliche Palettengrößen vorsähe. Sie gilt lediglich für den Datenbestand, mit dem Outpost 2 ausgeliefert wird.
Nach den Paletten-Listen folgt unmittelbar und ohne einleitenden Header, bereits die Liste der Bitmaps; genauso unmittelbar folgen darauf die Animationslisten.
Beide werden dabei jeweils mit einem uint(32) (oder wieder uint24+uint8 flags?) eingeleitet, der die Anzahl an Datensätzen enthält.
Paletten
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 50 | 50 | 41 | 4c | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | P | P | A | L | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Paletten-Länge | Gibt, entgegen zum normalen Blockformat, die Anzahl an in dieser Datei zu findenden Paletten an - nicht die Länge des Blocks in Byte. |
0x0007 | uint(8) | Flags | Wahrscheinlich, wie üblich, Flags. Mir sind allerdings keine Flags bekannt; da alle mir bekannten Werte |
Die Paletten-Informationen sind sehr einfach einzulesen.
Sie bestehen jeweils aus einem Header und einem Data-Segment.
Paletten-Header
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 68 | 65 | 61 | 64 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | h | e | a | d | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Paletten-Länge | Gibt, entgegen zum normalen Blockformat, die Anzahl an in dieser Datei zu findenden Paletten an - nicht die Länge des Blocks in Byte. |
0x0007 | uint(8) | Flags | Wahrscheinlich, wie üblich, Flags. Mir sind allerdings keine Flags bekannt; da alle mir bekannten Werte |
0x0008 | uint(32) | Palettenformat-Version? | Definiert wahrscheinlich, welcher Palettenformats-Version die Palette folgt. Alle Outpost2-Paletten scheinen dabei Version |
Paletten-Daten
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | 64 | 61 | 74 | 61 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | d | a | t | a | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Magic Bytes | |
0x0004 | uint(24) | Block-Länge | |
0x0007 | uint(8) | Flags |
Die Datensektion nimmt die einzelnen Paletten-Einträge auf. Die Anzahl an Paletten-Einträge ergibt sich aus der Block-Länge / 4.
Die einzelnen Einträge haben dabei folgenden, schlichten Aufbau;
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | -- | -- | -- | 04 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(8) | Rot-Komponente | Gibt den Rot-Anteil der Farbe an |
0x0001 | uint(8) | Grün-Komponente | Gibt den Grün-Anteil der Farbe an |
0x0002 | uint(8) | Blau-Komponente | Gibt den Blau-Anteil der Farbe an |
0x0003 | uint(8) | Unbekannt - Flags? | Es ist unklar, was dieser Wert bedeutet, da er scheinbar grundsätzlich |
Zu den Paletten ist ansonsten nur noch zu sagen, dass bei für Animationen zu verwendende Paletten folgende Regeln gelten:
- Die erste Farbe ist IMMER transparent, egal was für ein Wert dort angegeben ist.
-
Die Paletten-Einträge 1-24 sind in den Paletten 1-8 als Spielerfarbe zu werten.
Wo die Farben abseits von Spieler 1 genau herkommen, ist mir unklar.
ich vermute, dass die restlichen Farben hardcoded sind
Bitmaps
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
0x0010 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Ausgerichtete Breite | Gibt die Breite der Pixeldatenzeilen in Byte an - da diese an die 4-Byte-Grenzen ausgerichtet sind. So ist es schnell möglich, eine bestimmte Bildzeile anzuspringen. Warum dieser Wert separat gespeichert wird, obwohl er berechnet werden könnte, ist unklar. |
0x0004 | uint(32) | Offset | Gibt den Offset der ersten Zeile in der Bitmap an |
0x0008 | uint(32) | Höhe | Gibt die Höhe des Bildes in Pixel an |
0x000c | uint(32) | Breite | Gibt die Breite des Bildes in Pixel an |
0x0010 | uint(16) | Typ | Gibt die Art des Bildes an. Hierbei scheint es sich um eine Bitmaske zu handeln:
|
0x0012 | uint(16) | Palette | Definiert, welche Palette aus der PRT-Datei verwendet werden soll |
Diese Datenstruktur der PRT-Datei gibt an, wie die für die Sprites verwendeten Bitmaps aufgebaut sind. Diese Bitmaps dienen dabei als einzelner Bestandteil, von dem mehrere zu einem Animationsframe eines Sprites zusammengebaut werden.
Die konkreten Bilddaten verbergen sich dagegen in der
op2_art.BMP im Spielverzeichnis.
Warum diese Bitmapdatei über einen (überwiegend korrekten) RIFF-Bitmapheader verfügt,
ist unklar. Wahrscheinlich verwendet Outpost 2 System-APIs zum Laden der Grafiken,
indem dieser Header temporär übernommen wird und die entsprechenden, variierenden Felder überschrieben werden.
Die Pixeldaten sind dabei in der BMP-Datei an der Position Offset + dem uint32-Offset, das in der BMP-Datei an Adresse 0x000A zu finden ist (RIFF-Bitmap-Datenoffset), zu finden - und entsprechen wieder der zeilenweisen Anordnung von oben links nach rechts unten.
Monochrome 1bpp-Grafiken können dabei so gezeichnet werden, dass Farbe 0 vollständiger Transparenz, sowie Farbe 1 ein halbtransparentes schwarz/grau ist, da die Monochrom-Grafiken üblicherweise für Fahrzeug- und Gebäudeschatten in den Animationen verwendet werden.
Damit kann man bereits viele Grafiken zusammensetzen.
Animationen
Nun kommen wir zur Königsklasse der Disziplinen innerhalb der Outpost 2 Datenformate:
Den Animationen.
Die Animationslisten werden mit einem globalen Header, der primär der Datenverifikation dient, eingeleitet. Daraufhin folgen die konkreten Animationsedefinitionen, die sich in 3 Stufen gliedern:
-
Animation
Eine Animation ist die oberste Instanz; sie stellt eine Animation einer Einheit, eines Gebäudes oder einer 'Partikel-Animation' (Kometenschlag, Wetter, Explosion) in einem bestimmten Ausgangslage dar. -
Frame
Ein Frame ist ein einzelnes Bild innerhalb einer Animation. Eine Animation kann ein oder mehrere Frames beinhalten. -
Subframe
Ein Subframe ist die Information darüber, dass eine bestimmte Bitmap unter bestimmten Kriterien an eine bestimmte Position eines Frames gezeichnet werden soll. Ein Frame kann ein oder mehrere Subframes beinhalten.
Anschließend folgen schon direkt die einzelnen Animationsdefinitionen.
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Anzahl Animationen | Wieviele Animationsdatensätze vorhanden sind |
0x0004 | uint(32) | Anzahl Frames | Wieviele Frames insgesamt vorhanden sein sollten |
0x0008 | uint(32) | Anzahl Subframes | Wieviele Subframes insgesamt vorhanden sein sollten |
0x000c | uint(32) | Anzahl optionaler Einträge | Wieviele "optionale Einträge" vorhanden sind. |
Animation
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
0x0010 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
0x0020 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(32) | Unbekannt 1 | Unbekannte Informationen |
0x0004 | uint(32) | Bounding Box: Links | Gibt den linken Anfang (in Pixel) der Bounding Box an. |
0x0008 | uint(32) | Bounding Box: Oben | Gibt den oberen Anfang (in Pixel) der Bounding Box an. |
0x000c | uint(32) | Bounding Box: Breite | Gibt die Breite (in Pixel) der Bounding Box an. |
0x0010 | uint(32) | Bounding Box: Höhe | Gibt die Höhe (in Pixel) der Bounding Box an. |
0x0014 | uint(32) | Offset: X | Gibt den horizontalen Mittelpunkt der Animation an |
0x0018 | uint(32) | Offset: Y | Gibt den vertikalen Mittelpunkt der Animation an |
0x001c | uint(32) | Unbekannt 2 | Unbekannte Information |
0x0020 | uint(32) | Anzahl Frames | Gibt an, wieviele Animationsframes in dieser Animation enthalten sind |
0x0024 | uint(32) | Anzahl Windows | Gibt an, wieviele Fenster beim Zeichnen anzuwenden sind |
Die Daten der obersten Schicht, der Animation, sind dabei vorrangig Verwaltungsdaten - die Boundingbox bezeichnet dabei die Koordinaten der Markierung um das Fahrzeug/Gebäude, wenn das selbige ausgewählt ist und gibt auch gleichzeitig an, welcher Bereich anklickbar sein soll.
Das Offset bestimmt vorrangig den "Nullpunkt"; den Punkt, der auf spielinterne Koordinaten aufzurechnen beziehungsweise abzuziehen ist. Man könnte auch mathematischer sagen: das Offset bezeichnet hier den Koordinatenursprung.
Bei den Windows handelt es sich, genauso wie beim Offset, um jeweils (pro Window) 4 uint(32)-Werte, die einen Bereich angeben, der für einzelne Subframes als verwendbar gilt. Ausserhalb der Windows darf, sofern es für die Bitmap entsprechend vorgesehene ist, nicht gezeichnet werden.
Frame
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(8) | Subframe-Anzahl und Toggle für Optional 1, 2 | Dieser Wert enthält:
|
0x0001 | uint(8) | Unbekannt 1 und Toggle für Optional 3, 4 | Dieser Wert enthält:
|
0x0002 | uint(8) | Optional 1 | Unbekannt |
0x0003 | uint(8) | Optional 2 | Unbekannt |
0x0004 | uint(8) | Optional 3 | Unbekannt |
0x0005 | uint(8) | Optional 4 | Unbekannt |
Subframe
Adr | x0 | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | xA | xB | xC | xD | xE | xF | char | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0x0000 | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . | . |
Offset | Datentyp | Bezeichnung | Erklärung |
---|---|---|---|
0x0000 | uint(16) | Bitmap-Id | Gibt an, welche Bitmap für diesen Subframe verwendet werden soll |
0x0002 | uint(8) | Unbekannt 1 | Ist unbekannt - ich vermute allerdings stark, das es sich hierbei um eine Render-Priorität (Z-Layer) handelt. |
0x0003 | uint(8) | Subframe-Id | Gibt an, in welchem Subframe wir uns befinden |
0x0004 | sint(16) | Offset - Horizontal | Gibt an, wo innerhalb des Frames das Subframe platziert werden soll, bzw. um wieviele Pixel die Bitmap horizontal verschoben werden soll |
0x0006 | sint(16) | Offset - Vertikal | Gibt an, wo innerhalb des Frames das Subframe platziert werden soll, bzw. um wieviele Pixel die Bitmap vertikal verschoben werden soll |
Damit können wir nun einzelne Frames, als auch komplette Animationen entsprechend zusammensetzen, hier einmal exemplarisch an einer komplexeren Animation, der Animation mit dem Index 500, demonstriert
Animation 500
Animation 500 zeigt, wie ein Plymouth-Transporter, der mit gewöhnlichem Erz beladen ist, entladen wird. Dabei handelt es sich um eine der wenigen Animationen, die die Windowing-Funktionalität nutzt.
Und so lässt sich die komplette Animation zusammenfügen.
Leider gibt es noch ein Problem mit der oberen Ladeluke, da hier das entsprechende Bit in der Grafiktyp-Information nicht gesetzt ist.
Hier noch ein paar weitere, wunderschön animierte Sprites aus dem Spiel:
User-Interface
Nun fehlt noch das User-Interface des Spieles, welches in einem gebürstetem Metall-Look gehalten ist.
Aber auch hier ist erkennbar, dass Dynamix das Rad nicht neu erfinden musste; hier werden nicht nur schlicht die durch die von Windows bereitgestellten User32- und GDI32-APIs genutzt - insbesondere wird auch das Ressourcen-Management von User32 verwendet.
Diese lassen sich beispielsweise durch Programme wie das von Angus Johnson als Freeware entwickelte Resource Hacker, oder - falls man unter Linux / Mac OS den Einsatz von Wine scheut - mithilfe des in icoutils enthaltenen wrestool extrahieren.
Dateiname | Inhalt |
---|---|
Outpost2.exe | Enthält lediglich das Icon des Spieles, welches die Weltraumstation vor Neu Terra zeigt |
op2shres.dll | Enthält neben Grafiken für Bedienelemente wie Umrandungen, Buttons, Radio-Buttons und Checkboxen auch Dialog-Hintergründe, die Begleit-Bilder für die Story-Missionstexte und die Hauptmenü-Hintergrundgrafik |
out2res.dll | Enthält die Ingame-Fensterdekoration, Icons für gewöhnliches und spezielles Metall, den Lade-Bildschirm, Grafiken für Dialoge sowie weitere Cursor-Grafiken, zusätzlich zu den animierten im Spielverzeichnis |