David Super JLI'ler
Alter: 39 Anmeldedatum: 13.10.2005 Beiträge: 315
Medaillen: Keine
|
Verfasst am: 25.12.2005, 15:39 Titel: Monolith ARCH00 Format |
|
|
Das Monolith Arch00 File Format
Da nun Infos über das Format in diversen Wikis stehen, hab ich meine alte Doku ausgepackt und will diese nun auch veröffentlichen. Hab das bislang nicht getan, da ich befürchtete das Monolith Stress machen könnt' aber es scheint wohl keine Probleme zu geben...
Das Arch00 Dateiformat dient zur Archivierung aller Spielrelevanten externen Dateien, welche von Spielen benötigt werden die die JupiterEX Engine verwenden.
Im Groben kann man Arch00 Archive in fünf Sektionen unterteilen:
Zitat: | Header
Namenstabelle
Datei Info Tabelle
Ordner Info Tabelle
Datenblock |
Header
Zitat: | 0x00 – 0x04 Ident
0x04 – 0x08 Version
0x08 – 0x0C Länge der Namenstabelle
0x0C – 0x10 Anzahl der Ordner
0x10 – 0x14 Anzahl der Dateien
0x14 – 0x30 Unbekannt |
Die ersten vier Bytes sollten immer den Wert „LTAR“ beinhalten, da dieser zur Identifizierung des Dateiformates verwendet wird.
Die nächsten vier Bytes beinhalten die Versionsnummer. Diese aktuelle Version ist 3, daher sollten die Bytes 0x04 – 0x08 den Wert 3 beinhalten.
Die darauf folgenden vier Bytes beinhalten die Länge der Namenstabelle. Mehr über die Namenstabelle im nächsten Block.
Die Bytes 0x0C – 0x14 beinhalten die Gesamtanzahl der Ordner und der Dateien. Die Ordnerzahl sollte immer mindestens 1 betragen, da immer ein „root“ Ordner vorhanden sein muss.
Namenstabelle
Die Namenstabelle ist ein n Byte großer Datenblock, in dem sämtliche Namen von archivierten Dateien und Ordnern eingetragen sind. Jeder Eintrag wird durch mindestens ein NULL Byte abgeschlossen. Es können allerdings noch weitere folgen, insgesamt werden immer so viele NULL Bytes angehängt, das die Länge des Eintrags plus die angehängten Bytes das nächste Vielfache von Vier ergeben.
Der Dateiname „river.fxo“ würde also zu „river.fxo\0\0\0“
Datei Info Tabelle
Zitat: | 0x00 – 0x04 Offset des Eintrags in der Namenstabelle
0x04 – 0x08 Offset zum Dateistart, im Datenblock
0x08 – 0x0C Unbekannt
0x0C – 0x10 Dateigröße
0x10 – 0x14 Unbekannt
0x14 – 0x18 Dateigröße (komprimiert)
0x18 – 0x20 Unbekannt |
Diese Struktur ist für jede einzelne Datei enthalten, sie kann also öfter als einmal auftreten. Die genaue Anzahl steht in den Kopfdaten des Archives.
Die ersten vier Bytes der Struktur beinhalten den Offset des Eintrags in der Namenstabelle, der den Dateinamen repräsentiert.
Die folgenden vier Bytes geben die Position der Daten, von Dateibeginn gesehen, an.
Die Bytes 0x0C – 0x10 geben die Größe der Datei an. Allerdings steht exakt der gleiche Wert auch in den Bytes 0x14 – 0x18. Eine Überlegung wäre, das einer der beiden Werte die Größe der komprimierten Daten beinhaltet, ob das der Fall ist, konnte ich allerdings noch nicht nachprüfen.
Ordner Info Tabelle
Zitat: | 0x00 – 0x04 Offset des Eintrags in der Namenstabelle
0x04 – 0x08 Index des ersten Unterordners
0x08 – 0x0C Index des nächsten Ordners
0x0C – 0x10 Anzahl der Dateien in diesem Ordner |
Wie auch bei der Datei Info Tabelle ist die Anzahl der gespeicherten Ordner Informationen in den Kopfdaten des Archives angegeben.
In den ersten vier Bytes steht wieder der Offset des Eintrags in der Namenstabelle, durch den der Ordnername herausgefunden werden kann.
Der „root“ Ordner hat hier keinen gültigen Wert, da der Name nicht variabel ist.
Die nächsten vier Bytes beinhalten den Index des ersten Unterordners, sollte kein Unterordner verfügbar sein sind die Bytes mit dem Wert 0xFFFF gefüllt.
Gleiches gilt für den Index des nächsten Ordners. Mit „nächster Ordner“ ist der Ordner gemeint, der in gleicher Höhe auf den aktuellen Ordner folgt.
Die letzten vier Bytes dieser Struktur geben die Anzahl der Dateien an, die in diesem Ordner enthalten sind.
Erstellen des Dateibaums
Um den Dateibaum zu erstellen ist es am sinnvollsten alle Ordner zu durchlaufen. Für jeden Ordner wird die angegebene Anzahl, der enthaltenen Dateien, aus der Datei Info Tabelle geholt. Dabei werden einfach untereinander liegende Einträge genommen. Der Startindex ist die Summe der Dateianzahlen der vorherigen Ordner.
Danach sollte geprüft werden ob ein Unterordner vorhanden ist, ist dies der Fall werden für diesen die Dateien aus der Datei Info Tabelle geholt usw.
Als letztes wird noch geprüft ob ein weiterer Ordner auf der gleichen Ebene vorhanden ist, sollte dies der Fall sein wird für diesen Ordner die gesamte Prozedur wiederholt.
CPP: | void DumpFolder( const FolderInfo &info, int &FileOffset )
{
int i;
// Prüfen ob der Ordner einen gültigen Offsetwert hat, falls
// dies nicht der Fall ist, handelt es sich um den root Ordner
if ( info.NameOffset == 0 )
Debug( "[root]\n" );
else
Debug( "[%s]\n", ( NameTable+info.NameOffset ));
// Durchlaufen und ausgeben aller Dateien
for ( i = 0; i < info.CountFiles; i++ )
{
Debug( "%s\n", (NameTable+FileInfoTable[ FileOffset+i ].NameOffset ) );
}
FileOffset += info.CountFiles;
// Prüfen ob ein Unterordner vorhanden ist, falls ja wird die
// Funktion (DumpFolder) mit dem Unterordner nochmals aufgerufen
if ( info.SubFolder > -1 )
DumpFolder( FolderInfoTable[ info.SubFolder ], FileOffset );
// Prüfen ob ein weiterer Ordner auf der gleichen Ebene vorhanden
// ist, sollte dies der Fall sein wird die Funktion (DumpFolder)
// mit dem nächsten Ordner nochmals aufgerufen
if ( info.NextFolder > -1 )
DumpFolder( FolderInfoTable[ info.NextFolder ], FileOffset );
} |
Aufgerufen wird diese Funktion mit einer Referenz auf den Root Order. Um den Root Ordner zu identifizieren muss nur geprüft werden, welcher Eintrag der Order Info Tabelle auf als Nametable Offset 0 hat:
CPP: | int FindRootFolderIndex( void )
{
int i;
for ( i = 0; i < header.CountFolders; i++ )
{
FolderInfo &info = FolderInfoTable[ i ];
// Wenn der Offset gleich 0 ist, handelt es sich um den root
// Ordner. In diesem Fall wird der aktuelle Wert von i
// zurückgegeben
if ( info.NameOffset == 0 )
return i;
}
return 0; // Hierher dürfte das Programm niemals kommen
}
// ...
int main(int argc, char *argv[])
{
int rootIndex, fileOffset;
fileOffset = 0;
rootIndex = FindRootFolderIndex();
DumpFolder( FolderInfoTable[ rootIndex ], fileOffset );
return 0;
} |
Finden von Dateien
Es ist natürlich Sinnvoll eine Methode zu implementieren, mit der man den Dateibaum nach bestimmten Dateien durchsuchen kann. Die Vorgehensweise ist ähnlich wie das erstellen des Dateibaums.
CPP: | FileInfo *FindFile( const FolderInfo &info, int &FileOffset, char *name )
{
int i;
FileInfo *res = NULL;
char *fileName;
char *path;
fileName = ExtractFileName( name );
path = ExtractPath( name );
// Zuerst wird geprüft ob der Ordnername mit dem aktuellen Ordner
// übereinstimmt...
char *tmp = ( char *)( NameTable+info.NameOffset );
if ( !strcmp( tmp, path ) )
{
// ...falls ja, werden alle Dateien in diesem Ordner
// durchlaufen und geprüft ob der jeweilige Dateiname
// übereinstimmt. Ist dies der Fall, wurde die Datei gefunden
// und ein Zeiger auf das betreffende Objekt kann zurückgegeben
// werden
for ( i = 0; i < info.CountFiles; i++ )
{
tmp = ( char *)( NameTable+FileInfoTable[ FileOffset+i ].NameOffset );
if ( !strcmp( tmp, fileName ) )
return &FileInfoTable[ FileOffset+i ];
}
}
FileOffset += info.CountFiles;
// Überprüfen ob ein Unterordner vorhanden ist, falls dies der Fall
// sein sollte, wird die Methode (FindFile) nochmals mit dem
// betreffenden Unterordner aufgerufen
if ( info.SubFolder > -1 )
res = FindFile( FolderInfoTable[ info.SubFolder ], FileOffset, name );
// Überprüfen ob ein weiterer Ordner auf der gleichen Ebene vorhanden
// ist, sollte dies der Fall sein, wird die Methode (FindFile)
// nochmals mit dem betreffenden Ordner aufgerufen. Hat `res` bereits
// einen Wert, wurde die Datei gefunden und es muss nicht mehr
// weitergesucht werden
if ( info.NextFolder > -1 && !res )
res = FindFile( FolderInfoTable[ info.NextFolder ], FileOffset, name );
return res;
} |
Extrahieren von Dateien
Um eine Datei aus dem Archiv zu entpacken, muss der Dateizeiger lediglich an die gegebene Position gesetzt und die gegebene Anzahl der Daten aus der Datei gelesen werden.
CPP: | bool Extract( char *Archive, const FileInfo &info )
{
int in, out;
char fileName[ 1024 ];
if ( ( in = _open( Archive, _O_RDONLY ) ) < 0 )
return false;
sprintf( fileName, "%s", ( NameTable+info.NameOffset ) );
if ( ( out = _open( fileName, _O_RDWR | _O_CREAT | _O_BINARY ) ) < 0 )
return false;
// Setzten des Dateizeigers auf den Anfang der gewünschten Daten.
// Wichtig ist hier die mögliche Größe der Archive zu berücksichtigen
// Ein 32 Bit signed Integer kann möglicherweise nicht genügend hohe
// Werte liefern. Deshalb wird hier die 64 Bit Variante verwendet
_lseeki64( in, info.Offset, SEEK_SET );
// Reservieren des Speichers und auslesen/seichern der Daten
BYTE *buff = new BYTE[ info.Length ];
_read( in, buff, info.Length );
_write( out, buff, info.Length );
delete [] buff;
buff = NULL;
_close( in );
_close( out );
return true;
} |
Der Vorgang um eine Datei zu extrahieren ist nun folgender: Zuerst wird anhand des Dateinamens der entsprechende Eintrag in der Datei Info Tabelle gesucht. Falls die Suche erfolgreich war, kann die Datei entpackt werden.
CPP: | // ...
rootIndex = FindRootFolderIndex();
fileOffset = 0;
char *name = "Worlds\\ReleaseMultiplayer\\Streets.World00p";
FileInfo *info = FindFile( FolderInfoTable[ rootIndex ], fileOffset, name);
if ( info )
Extract( ARCHIVE, *info );
else
Debug( "%s existiert nicht\n", name );
// ... |
grüße
P.S.: Die Beispiele sind in C *räusper*. Wen das stört der solls einfach für sich behalten!
P.P.S: Nochmal als PDF: hier |
|