В данной статье будут рассматриваться архивы от игр, т. н. "без сжатия", где куча файлов просто сливается в один (в играх Doom эти архивы остроумно назвали WAD - т.е. "комок").
Общие положения
Если у Вас есть архив, то распаковать его обычно не сложно. Почти все архивы устроены одинаково:
1) Сначала идёт сигнатура архива: "BIGF", "IWAD", "PACK"... (иногда, конечно, может и не быть).
2) Количество файлов в архиве (в Doom для этих целей используется файл "F_END" - последний файл, дальше не распаковывать).
3) Размер заголовка (редко).
4) Описание файлов. Обычно состоит из имени файла (нередко с путём), размера и смещения (либо от начала файла-архива, либо от конца заголовка-описания). Есть правда "долбанутые" архивы (например из игры Syberia II), где хранится только размер и имя файла - тогда приходится высчитывать смещение вручную. Имя файла обычно заканчивается символом $00 или оно фиксировано, скажем, 8 символов как в .WAD (что не используется - забивается тем же нулём). Смещение и размер бычно 4 (dword) байта.
Важно: "FAT архива" или, проще говоря, заголовки файлов, могут шифровать (в играх Max Payne .RAS архивы), чтобы кто-нибудь умный не вытащил. Но если не шифровали сами файлы, то их нетрудно достать. Например, .WAV, .BMP, .AVI - содержат в начале описания свой размер.
В архивах Quake-подобных игр (.PAK, .WAD) раздел-описание файлов находится не в начале, а в конце архива. В начале стоит ссылка (4 байта) на место в файле, с которого начинается раздел-описание файлов.
Пример
Рассмотрим формат архива от игры C&C: Generals. Возьмём файл MUSIC.BIG и будем тащить оттуда музыку (заодно и послушаем в удовольствие). Раскройте файл каким-нибудь HEX редактором. Смотрим:
1) Первые 4 байта сигнатура - "BIGF".
2) Вторые 4 байта - размер файла-архива.
3) Опять 4 байта - количество файлов в архиве (ВНИМАНИЕ! Эти и ВСЕ последующие 4 байта перед использованием нужно РАЗВЕРНУТЬ ЗАДОМ НАПЕРЁД!!!).
4) 4 байта - размер всего заголовка-описания в файле-архиве (развернуть!).
5) Смещение файла от начала файла-архива - 4 байта (развернуть!).
6) Размер файла - 4 байта (развернуть!).
7) Имя файла (вместе с путём). Заканчивается символом $00.
Пункты 5-7 повторить пока не пройдёте все файлы в архиве (generals.asc, который распакуется, можете переименовать в .TGA и полюбоваться на него).
--- Листинг программы для вытаскивания музыки из MUSIC.BIG файла ---
(можете скачать эту программу из раздела "Файловый архив")
{ Распаковщик BIGF файлов из игр от Electronic Arts }
{ Написана на Delphi }
Program EAUnpack;
{$APPTYPE CONSOLE} { консольное приложение }
Uses SysUtils;
Var
TF, FPos, Sz, FSz, FOffs, I: LongInt;
S, St, Sd: String;
P: Pointer;
Fl, F: File;
B: Byte;
SChar: Char; { Slash char - for NFSU2 }
{ прочедура для "разворота" типа LongInt }
Function ReadLong(Var F: File; VIV: Boolean): LongInt;
Var K, Bl: Byte;
L: LongInt;
Begin
L:=0;
If VIV=False Then BlockRead(F, L, 4)
Else
For K:=3 DownTo 0 Do
Begin
BlockRead(F, Bl, 1);
L:=L+(Bl Shl (8*K));
End;
ReadLong:=L;
End;
Begin
WriteLn('EA Unpacker (for BIGF)');
WriteLn('For .BIG (C&C: Generals) / .VIV (Need For Speed (includes Underground 2!))');
WriteLn('T#i$ PR0GR@M bY -=CHE@TER=-');
WriteLn('http://CTPAX-CHEATER.losthost.org');
WriteLn;
If ParamCount<>1 Then
Begin
WriteLn('Usage: eaunpack filename.ext');
Exit;
End;
If FileExists(ParamStr(1))=False Then
Begin
WriteLn('Input file not found!');
Exit;
End;
AssignFile(Fl, ParamStr(1));
Reset(Fl, 1);
SetLength(S, 4);
BlockRead(Fl, S[1], 4);
If (S<>'BIGF') And (S<>'BIG4') Then { 'BIG4' for NFSU2 } { читаем сигнатуру }
Begin
CloseFile(Fl);
WriteLn('This is not BIG/VIV archive!'); { Упс! Это - не BIGF! }
Exit;
End;
Sz:=ReadLong(Fl, False); { Читаем нормальные 4 байта }
If Sz<>FileSize(Fl) Then { Если не равны размеру файла, то пытаемся }
Begin { прочитать как "развёрнутые" }
Seek(Fl, 4);
Sz:=ReadLong(Fl, True);
End;
If Sz<>FileSize(Fl) Then { если и после этого не совпало - то это не BIGF }
Begin
CloseFile(Fl);
WriteLn('This is not BIG/VIV archive!');
Exit;
End;
TF:=ReadLong(Fl, True); { Total Files - количество файлов в архиве }
Seek(Fl, FilePos(Fl)+4);{ Пропускаем развёрнутые 4 байта - размер заголовка }
For I:=1 To TF Do { от 1 до количества_файлов_в_архиве делать: }
Begin
FOffs:=ReadLong(Fl, True); { читаем смещение файла }
FSz:=ReadLong(Fl, True); { читаем размер файла }
S:=''; { чистим строчку }
Repeat
BlockRead(Fl, B, 1); { читаем байты из файла, пока не встретится символ 0 }
If B<>0 Then S:=S+Chr(B); { и добавляем их в строку - формируем имя }
Until B=0;
FPos:=FilePos(Fl); { запоминаем текущую позицию в заголовке, чтобы вернуться позже }
St:=S;
If Pos('/', St)<>0 Then SChar:='/' { <- данная примочка, для файлов из NFSU2 - }
Else SChar:='\'; { определяет символ-разделитель для каталогов }
While (Length(St)>0) And (St[Length(St)]<>SChar) Do
Delete(St, Length(St), 1); { вычленяем путь, без имени файла }
If Length(St)>0 Then
Begin
Sd:='.';
While Length(St)<>0 Do
Begin
{ по очереди удлинняем имя пути ... }
Sd:=Sd+'\'+Copy(St, 1, Pos(SChar,St));
Delete(Sd, Length(Sd), 1);
{ ... и создаём его, если его нет }
If DirectoryExists(Sd)=False Then CreateDir(Sd);
St:=Copy(St, Pos(SChar, St)+1, Length(St));
End;
End;
Seek(Fl, FOffs); { смещаемся в файле-архиве в то место, где начинается файл }
GetMem(P, FSz); { выделяем кусок памяти под его размер }
BlockRead(Fl, P^, FSz); { читаем его в память }
AssignFile(F, S); { создаём новый файл }
ReWrite(F, 1);
BlockWrite(F, P^, FSz); { и засовываем туда содержимое буфера }
CloseFile(F);
FreeMem(P, FSz); { чистим память }
Seek(Fl, FPos); { перемещаемся обратно к заголовку, где остановились }
WriteLn(S); { для отчётности выводим имя файла, который распаковали }
End;
CloseFile(Fl);
WriteLn;
WriteLn('Total files: ', TF); { выводим количество распакованных файлов }
End. { всё... }