Startseite » Peering Inside Restricted Area - .PAK-File Dumper

Restricted Area - .PAK-File Dumper

Analyse der .PAK-Files, Entwicklung eines Dumpers.

Version 2, mit Datetype-Erkennung

Bisherige Artikel zum Thema

Einleitung

Ich hoffe, die Informationen in diesem Artikel bringen uns ein Stück näher an eine Erweiterung für das schöne Spiel. Deshalb veröffentliche ich auch den Sourcecode des Programms, sodass sich Leute finden, die auf den Informationen aufbauen können.
Die .PAK-Dateien von Restricted Area sind grosse Dateien, die andere, kleinere Dateien, in komprimierter Form enthalten. Die .PAK-Dateien sind sogenannte Bigfiles, wie Blizzards MPQ, ID's WAD u.Ä. Restricted Area benutz den Kompressionsalgorithmus JCALG v5.32 von Jeremy Collake.

Einzelschritte

  1. Besorg euch einen Hexeditor. Zum Beispiel y0das 16Edit FX oder Frhed, beide sind kostenlos und funktionieren wunderbar.
  2. Öffnet \Restricted Area\GFX\Animation\City_Fassburn.pak mit dem Hexeditor eurer Wahl.
  3. Ladet euch die JCALG Bibliothek von Jeremy Collake herunter!

Aufbau der .PAK-Dateien

Die .PAK-Dateien sind im weiteren wie folgt aufgebaut:
--- BLOCK 1 --- 0x00000000 DWORD Grösse der Datei ungepackt 0x00000004 DWORD Grösse der Datei gepackt 0x00000008 JCALC Header und die komprimierten Daten. --- BLOCK 2 --- 0x00000000 DWORD Grösse der Datei ungepackt 0x00000004 DWORD Grösse der Datei gepackt 0x00000008 JCALC Header und die komprimierten Daten. --- BLOCK N --- Von diesen Blöcken sind beliebig viele hintereinander in einer .PAK-Datei enthalten. Die .PAK-Datei selbst hat keinen übergeordneten Header, der z.B. die Anzahl der Dateien oder deren Indizes enthält. Den zusätzlichen Header hat Master Creations sicherlich eingebaut, da JCALG Archive in ihrem eigenen Header keine Informationen über die grösse der gepackten Datei oder der Originaldatei enthalten und um beim Entpacken den Aufruf von JCALG1_GetUncompressedSizeOfCompressedBlock zu sparen, da dieser sehr kostenintensiv ist.

Diesen zusätzlichen Headr habe ich wie folgt nachgebildet: typedef struct __restricted_area_pak { DWORD dwRealSize; DWORD dwPackedSize; } __restricted_area_pak_type;

Extrahierung einer Datei

Wollen wir eine einzelne Datei aus einer .PAK-Datei extrahieren, so müssen wir zuerst die ersten beiden DWORD's wie oben bezeichnet einlesen. Wichtig ist für und aber nur der Wert an Offset 0x00000004, da er die Grösse der Datei in der .PAK Datei bezeichnet. Nehmen wir "City_Fassburn.pak" und öffnen die Datei in einem Hexeditor, so bekommen wir folgende Werte für unsere ersten beiden DWORD's: 0x00000000 36 c0 00 00 | ca 3e 00 00 ca 3e 00 00 stellen wir wegen der reversen Byteorder um nach 3e ca was dezimal 16.074 ergibt. Nun haben wir die Grösse der ersten einzelnen Datei in der .PAK-Datei. Demnach müssen wir nur noch den Speicherblock von 0x00000008 bis 0x00000008 + 0x00003eca = 0x00003ed2 auslesen und haben die erste Datei extrahiert. Da sich dies auch automatisieren lässt, habe ich ein kleines Programm geschrieben, was die gesamte Prozedur erledigt. Das Programm erwartet als Parameter den Name einer .PAK-Datei und entpackt alle enthaltenen Dateien nach 0.dump, 1.dump ... Da in einer .PAK-Datei beliebige Daten sein können, werden die entpackten Dateien nicht umbenannt. Ein kurzer Blick in den Header identifiziert den Type meisst. So bezeichnet "BM....." eine Bitmap .BMP-Datei, "RIFF....WAVEfmt" eine .WAV-Datei. Aus einem anderen Projekt habe ich bereits eine gute Routine um einen Header zu identifizieren, eventuell, werde ich die hier noch integrieren.

Code

Der Code für den Entpacker funktioniert tadellos. Da es sich aber um ein kleines Tool als Denkanstoss handelt, behandele ich nicht alle möglichen Fehlerfälle. Ihr müsst selbst darauf achten, dass die .PAK-Datei auch wirklich eine .PAK-Datei ist! /// /// Dateiname: restricted-area-pak-dumper.cpp /// /// Diese Datei gehört zu dem Artikel: /// - http://www.naden.de/public/articles/restricted-area-pak-file-dumper.php /// /// Dieser Code ist (c) 2004 naden.de - Der Code darf frei gelesen, empfohlen, verlinkt, compiliert und teilweise in anderen /// Programmen - nichtkommerzieller Art - verwendet werden, solange die Quelle angegeben wird. /// Eine darüber hinausgehender Nutzung welcher Art und Weise auch immer bedarf der schriftlichen Genehmigung des Autors. /// Bei Zuwiderhandlungen ist der Schuldige damit einverstanden, Schadenersatzforderungen nachzukommen. /// #include <stdio.h> #include <windows.h> #include "restricted_area_pak_types.h" #include "jcalg1.h" #include "file_signatures.h" void GetFileTypeByHeader( LPSTR lpData, LPSTR lpType ) { if( RIFF_SIGNATURE == (DWORD)( ( (DWORD)lpData[3]<<24 ) | ( (DWORD)lpData[2]<<16 ) | ( (DWORD)lpData[1]<<8 ) | ( (DWORD)lpData[0] ) ) ) { lstrcpy( lpType, "wav" ); return; } if( SIMPLE_BITMAP_SIGNATURE == (WORD)( ( (WORD)lpData[ 0 ]<<8 ) | (WORD)lpData[ 1 ] ) ) { lstrcpy( lpType, "bmp" ); return; } /// wenn Type nicht bekannt, dann .dump lstrcpy( lpType, "dump" ); } UINT ExtractPackedBlockToFile( HANDLE hFileIn, DWORD dwBlockSize, DWORD dwFilesize, DWORD dwIndex ) { LPSTR lpData; LPSTR lpBuffer; HANDLE hFileOut; try { /// reserviere Speicher lpData = new char[ dwBlockSize ]; if( !lpData ) { throw 2; } DWORD dwBytesRead; /// Lese Block ReadFile( hFileIn, (LPVOID)lpData, dwBlockSize, &dwBytesRead, 0 ); if( dwBytesRead != dwBlockSize ) { throw 3; } lpBuffer = new char[ dwFilesize /*dwUncompressedSize*/ ]; if( !lpBuffer ) { throw 4; } /// entpacke Block DWORD dwNewFilesize = JCALG1_Decompress_Fast( (LPVOID)lpData, lpBuffer ); if( dwNewFilesize == 0 ) { throw 5; } char szExtension[ 5 ] = { 0 }; GetFileTypeByHeader( lpBuffer, szExtension ); DWORD dwFlags = FILE_ATTRIBUTE_NORMAL; /// erstelle Ausgabedateiname char szFilename[ MAX_PATH ] = { 0 }; wsprintf( szFilename, OUTFILE_MASK, dwIndex, szExtension ); /// Erstelle Ausgabedatei if( ( hFileOut = CreateFile( szFilename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, dwFlags, 0 ) ) == INVALID_HANDLE_VALUE ) { throw 6; } DWORD dwBytesWritten; /// speichere Daten WriteFile( hFileOut, lpBuffer, dwNewFilesize, &dwBytesWritten, 0 ); CloseHandle( hFileOut ); if( dwBytesWritten != dwNewFilesize ) { throw 7; } } catch( int uException ) { if( lpBuffer ) { delete[] lpBuffer; } if( lpData ) { delete[] lpData; } if( hFileOut != INVALID_HANDLE_VALUE ) { CloseHandle( hFileOut ); } return( uException ); } delete[] lpBuffer; delete[] lpData; return( 1 ); } int main( int argc, char** argv ) { printf( "\nResticted Area - .PAK-Dumper (c)2004 www.naden.de - v0.2\n" ); if( argc != 2 ) { printf( "\n Syntax: %s pfad\\dateiname.pak\n", argv[ 0 ] ); } HANDLE hFileIn; /// Datei zum lesen öffnen if( ( hFileIn = CreateFile( argv[ 1 ], GENERIC_READ, 0, 0, OPEN_EXISTING, 0, 0 ) ) == INVALID_HANDLE_VALUE ) { printf( "\n Fehler: Die Eingabedatei konnte nicht geöffnet werden!", argv[ 0 ] ); return( 1 ); } printf( "\n Lese aus Datei: %s\n", argv[ 1 ] ); printf( "\n block# gepackt entpackt ratio%%" ); printf( "\n ---------------------------------------" ); DWORD dwBytesRead; DWORD dwIndex = 0U; bool bRead; /// lese PAK-Header while( ( bRead = ReadFile( hFileIn, &ra_pak, 8, &dwBytesRead, 0 ) ) ) { if( bRead && dwBytesRead == 0 ) { /// Ende der Datei? break; } /// Check ob die angeforderten 8 Bytes gelesen wurden if( dwBytesRead != 8 ) { printf( "\n Fehler: Kann nicht aus Eingabedatei lesen! ( %u )", dwIndex ); CloseHandle( hFileIn ); return( 1 ); } printf( "\n %.8u %.8u %.8u %.2f ... ", dwIndex, ra_pak.dwPackedSize, ra_pak.dwRealSize, GET_PERCENT( ra_pak ) ); UINT uResult; if( ( uResult = ExtractPackedBlockToFile( hFileIn, ra_pak.dwPackedSize, ra_pak.dwRealSize, dwIndex ) ) != 1 ) { printf( " Fehler (%s)", GET_ERRORMESSAGE_BY_INDEX( uResult ) ); } else { printf( "OK" ); } dwIndex ++; } /// while CloseHandle( hFileIn ); printf( "\n\n Fertig, es wurden %d Dateien extrahiert!\n", dwIndex ); return( 0 ); } Die Header-Datei definiert einen benötigten Struct, einige Hilfsmacros und die Fehlermeldungen. /// /// Dateiname: restricted_area_pak_types.h /// /// Diese Datei gehört zu dem Artikel: /// - http://www.naden.de/public/articles/restricted-area-pak-file-dumper.php /// /// Dieser Code ist (c) 2004 naden.de - Der Code darf frei gelesen, empfohlen, verlinkt, compiliert und teilweise in anderen /// Programmen - nichtkommerzieller Art - verwendet werden, solange die Quelle angegeben wird. /// Eine darüber hinausgehender Nutzung welcher Art und Weise auch immer bedarf der schriftlichen Genehmigung des Autors. /// Bei Zuwiderhandlungen ist der Schuldige damit einverstanden, Schadenersatzforderungen nachzukommen. /// typedef struct __restricted_area_pak { DWORD dwRealSize; DWORD dwPackedSize; } __restricted_area_pak_type; __restricted_area_pak_type ra_pak; #define GET_ERRORMESSAGE_BY_INDEX( i ) __restricted_area_pak_errors[ i-2 ] #define OUTFILE_MASK "%.8u.%s" #define GET_PERCENT( p ) ( (double)p.dwPackedSize / ( (double)p.dwRealSize / 100.0 ) ) char __restricted_area_pak_errors[ 7 ][ 70 ] = { /// 2 { "Hauptspeicher konnte nicht reserviert werden!\0" }, /// 3 { "Die angeforderte Menge an Bytes konnte nicht gelesen werden!\0" }, /// 4 { "Speicher für den Buffer konnte nicht reserviert werden!\0" }, /// 5 { "Block konnte nicht entpackt werden!\0" }, /// 6 { "Ausgabedatei konnte nicht erstellt werden!\0" }, /// 7 { "Es konnten nicht alle Daten geschrieben werden!\0" } };
/// /// Dateiname: file_signatures.h /// /// Diese Datei gehört zu dem Artikel: /// - http://www.naden.de/public/articles/restricted-area-pak-file-dumper.php /// /// Dieser Code ist (c) 2004 naden.de - Der Code darf frei gelesen, empfohlen, verlinkt, compiliert und teilweise in anderen /// Programmen - nichtkommerzieller Art - verwendet werden, solange die Quelle angegeben wird. /// Eine darüber hinausgehender Nutzung welcher Art und Weise auch immer bedarf der schriftlichen Genehmigung des Autors. /// Bei Zuwiderhandlungen ist der Schuldige damit einverstanden, Schadenersatzforderungen nachzukommen. /// /// WAV #define RIFF_SIGNATURE 0x46464952 #define WAVE_SIGNATURE 0x57415645 #define FMT_SIGNATURE 0x20746d66 #define DATA_SIGNATURE 0x61746164 /// Unter dieser URL sind nähere Informationen zum Dateiformat zu finden. /// http://ccrma-www.stanford.edu/CCRMA/Courses/422/projects/WaveFormat/ /// BMP #define SIMPLE_BITMAP_SIGNATURE 0x424d Beim kompilieren des Codes muss die Datei "jcalg1_static.lib" mit in das Programm gelinkt werden.

Begriffserkärungen

Weiterführende Links


Danksagungen

Danke allen Lesern, von denen ich so viel positives Feadback bekommen habe.

Mit den obigen Informationen sollte jeder in der Lage sein, den Entpacker zu erweitern und auf dem Code aufzubauen. Feadback ist wie immer willkommen. Ich hoffe, das dieses Tutorial Leute dazu animiert, weiter in die Richtung zu forschen, um schnellstmöglich schöne Erweiterungen für Restricted Area zu erstellen. Anregungen, konstruktive Verbesserungsvorschläge und allgemeines Feadback sind wie immer willkommen.

Der direkte Link zu diesem artikel lautet: http://www.naden.de/public/articles/restricted-area-pak-file-dumper.php

Alle Angaben ohne Anspruch auf Richtigkeit oder Vollständigkeit. Die Manipulation der Dateien erfolgt auf eigene Verantwortung. Es ist ausgeschlossen, dass der Autor für etwaige Schäden am System oder den Programmen zur Rechenschaft gezogen werden kann.
Alle Markenrechte liegen bei den jeweiligen Eigentümern. Der Autor ist in keiner Weise mit den Herstellern oder dem Distributor des Spiels verbunden.

Artikeldatum: 22.10.2004
© 2004 naden.de - Der Artikel darf frei gelesen, empfohlen und verlinkt werden. Eine darüber hinausgehender Nutzung welcher Art und Weise auch immer bedarf der schriftlichen Genehmigung des Autors. Bei Zuwiderhandlungen ist der Schuldige damit einverstanden, Schadenersatzforderungen nachzukommen.