Распакуй файло

2008.09.20

Содержимое это страницы сильно устарело и не поддерживается!

Все замечания по поводу содержимого будут игнорироваться.

Страница оставлена исключительно только для истории.


В данной статье будут рассматриваться архивы от игр, т. н. "без сжатия", где куча файлов просто сливается в один (в играх 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 (ASCIIZ-строка).

Пункты 5-7 повторить пока не пройдёте все файлы в архиве (generals.asc, который распакуется, можете переименовать в .TGA и полюбоваться на него).


--- Листинг программы для вытаскивания музыки из MUSIC.BIG файла ---

(готовую программу можно взять из раздела B3/\OM)


{ Распаковщик 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)');
  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;
      { эта проверка определяет символ-разделитель каталогов (NFSU2) }
      If Pos('/', St)<>0 Then SChar:='/' 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. { всё... }