P.S. Прошу Админа данный текст скопировать в шапку и удалить это сообщение.Я решил для начала разобрать WAD, чтобы уже рассмотреть по отдельности каждый файл в архиве.
Начнём с открытия файла в HEX редакторе, давайте ка откроем файл System.WAD и посмотрим содержимое:
(http://s7.hostingkartinok.com/uploads/images/2014/11/658791c7bca2db7e3aa655514fa646db.png)
А теперь расскажу о том, что я выделил:
Чёрный - TOC, можно использовать как проверку, тот ли архив открываем. Никогда не меняется. Скорей всего указывает на блок TOC.
Тёмно-серый - Размер блока TOC, блок TOC начинается c $1C.(Значение, перед которым стоит "$", означает HEX значение)
Серый - Кол-во файлов.
А дальше идёт TOC, на каждый файл выделяется $20, то есть будет цикл считывания от 1 до Кол-во файлов.
Красный - Имя файла, максимальный размер $10.
Оранжевый - Расширение файла, максимальный размер $4.
Жёлтый - Поинтер(указатель) на начала блока с файлом.
Синий - Размер блока с файлом.
А вот теперь последнее:
Коричневый - Перед каждым блоком с файлом есть информация в размере $1C, посмотрев на эти $1C, можно понять, что это информация о блоке, который идёт после неё. Она нам не нужна, т.к. мы считали эту информацию из TOC, но работать с ней придётся, т.к. это понадобится для запаковки
Разобрав этот архив, приступим к написанию программы для распаковки.
Я составил структуру WAD, для удобности:
Type
TTOCBlock = record
NameFile : array[1..199] of AnsiString;//Имя файла
ExtFile : array[1..199] of AnsiString;//Расширение файла
PointerFile : array[1..199] of integer;//Позиция файла
SizeFile : array[1..199] of integer;//размер файла
end;
Type
TInfoBlock = record
NameFile : array[1..199] of AnsiString;//Имя файла
ExtFile : array[1..199] of AnsiString;//Расширение файла
SizeFile : array[1..199] of integer;//размер файла
end;
Type
TWADFile = record//Big-Endian
CheckTOC : AnsiString;//Переходим на $10 и читаем 3 байта в String, если String = 'TOC', то позиция стримки + 1
SizeTOCBlock : Integer;//Размер TOC
CountFiles : Integer;//Кол-во файлов
TOCBlock : TTOCBlock;
InfoBlock : TInfoBlock;
end;Ну что ж, начнём писать программу. Весь код я постараюсь комментировать, для удобности.
Открываем Delphi XE7(можно и постарее, просто у меня стоит именно эта версия), создаём VCL Forms Application(Да, я люблю именно программы на формах) и копируем мной составленную структуру выше "var":
(http://s7.hostingkartinok.com/uploads/images/2014/11/e7b613eb1cc782aba81c00656c5958c9.png)
(старый скрин, вместо string - AnsiString)После "var" напишем
WADFile : TWADFile;
F:TFileStream;
Так же, после {$R *.dfm} добавим такой код:
function SwapEndian(Value: integer): integer; register; overload;
asm
bswap eax
end;
Он понадобится, чтобы конвертировать Little-Endian значение в Big-Endian и наоборот.
Теперь добавим на форму OpenDialog, SaveDialog, MainMenu, StringGrid, StatusBar и ProgressBar.
Стандартное имя компонентов я менять не буду, чтобы вы не запутались.
Так, начнём с оформления:
Меню я сделал такое, пока сойдёт :D
(http://s7.hostingkartinok.com/uploads/images/2014/11/79a918da3b46c2eab057cbae6a2cba7e.png)
Теперь настроим StringGrid:
Align = alClient
ColCount = 3
FixedCols = 0
Options -> goRowSelect = Включить
RowCount = 2
Так же добавим 3 панели у StatusBar
У 1 панели ставим Width = 100, у 2 Width = 300 и Text="File:", у 3 Width Можно любой, но не меньше 100, а Text="Mode:".
Так, вроде всё, перейдём к написанию кода, создадим у Form1 процедуру OnCreate(Надеюсь, вы умеете самое простое).
Пишем туда такой код:
begin
with ProgressBar1 do//Добавляем progressbar на 1 панель statusbar
begin
Parent := StatusBar1;
Position := 0;
Top := 2;
Left := 1;
Height := StatusBar1.Height - Top;
Width := StatusBar1.Panels[0].Width - Left;
end;
StringGrid1.Cells[0,0]:='Name File';//Пишем текст в первую строку
StringGrid1.Cells[1,0]:='Pointer';
StringGrid1.Cells[2,0]:='Size';
StringGrid1.ColWidths[0]:=450;//Меняем размер у колонок
StringGrid1.ColWidths[1]:=80;
StringGrid1.ColWidths[2]:=80;
end;Так же у формы создадим процедуру OnCanResize и напишем туда этот код:
StringGrid1.ColWidths[0]:=StringGrid1.Width-(StringGrid1.ColWidths[1]+StringGrid1.ColWidths[2]+10);//При изменении размера формы, расширяем колонку с названием файла
Ещё добавим фильтр у OpenDialog
WAD File (*.wad)|*.wad
Вот и всё, с оформлением пока всё.
Ну что ж, начнём писать процедуру распаковки:
Давайте ка я распишу как будет работать распаковка:
- Переходим на $10 и читаем 3 байта, если это TOC, то продолжаем работу и , если нет, то выводим сообщение об ошибке.
- К позиции стримки(Stream) прибавляем 1 и считываем 4 байта, конвертируем из Big-Endian в Little-Endian с помощью функции, которую мы написали с самого начала (SwapEndian) и сохраняем в WADFile.SizeTOCBlock.
- Считываем 4 байта, конвертируем из Big-Endian в Little-Endian и сохраняем в WADFile.CountFiles.
- Начинаем цикл с 1 до WADFile.CountFiles и в цикле считываем по одному байту, пока не встретится $00, сохраняем размер от прошлой позиции стримки до $00, читаем в WADFile.TOCBlock.NameFile столько, сколько получили при считывании до позиции $00. Дальше переходим к позиции расширения и считываем его в WADFile.TOCBlock.ExtFile. Потом читаем 4 байта, конвертируем, записываем в WADFile.TOCBlock.PointerFile, читаем ещё 4 байта, конвертируем, сохраняем в WADFile.TOCBlock.SizeFile, добавляем к позиции стримки 4, добавляем всё данные в StringGrid и на этом цикл кончается.
А теперь пишем процедуру для распаковки:
Добавим
procedure ExtractWad;
procedure ExtractBlock(NumFile:Integer);в Type формы(в самом верху)
А теперь добавим саму процедуру распаковки:
Procedure TForm1.ExtractWad;//Узнаём всё о файлах
var
I,TempPos,Size:Integer;
B:Byte;
S:String;
begin
F:=TFileStream.Create(OpenDialog1.FileName,fmOpenReadWrite);
F.Position:=$10;
SetLength(WADFile.CheckTOC,3);
F.Read(WADFile.CheckTOC[1],3);
F.Position:=F.Position+1;
if WADFile.CheckTOC='TOC' then
begin
F.Read(WADFile.SizeTOCBlock,4);
WADFile.SizeTOCBlock:=SwapEndian(WADFile.SizeTOCBlock);
F.Read(WADFile.CountFiles,4);
WADFile.CountFiles:=SwapEndian(WADFile.CountFiles);
StringGrid1.RowCount:=WADFile.CountFiles+1;
StatusBar1.Panels[2].Text:='Mode:WAD File';
StatusBar1.Panels[1].Text:='File:'+OpenDialog1.FileName;
for I := 1 to WADFile.CountFiles do
begin
TempPos:=F.Position;
Size:=1;
F.Read(B,1);
while B<>$00 do
begin
F.Read(B,1);
Size:=Size+1;
end;
Size:=Size-1;
F.Position:=TempPos;
SetLength(WADFile.TOCBlock.NameFile[i],Size);
F.Read(WADFile.TOCBlock.NameFile[i][1],Size);
while b=$00 do
f.Read(b,1);
f.Position:=f.Position-1;
SetLength(WADFile.TOCBlock.ExtFile[i],3);//временно
F.Read(WADFile.TOCBlock.ExtFile[i][1],3);
f.Position:=f.position + 1;
F.Read(WADFile.TOCBlock.PointerFile[i],4);
WADFile.TOCBlock.PointerFile[i]:=SwapEndian(WADFile.TOCBlock.PointerFile[i]);
F.Read(WADFile.TOCBlock.SizeFile[i],4);
WADFile.TOCBlock.SizeFile[i]:=SwapEndian(WADFile.TOCBlock.SizeFile[i]);
F.Position:=F.Position+4;
StringGrid1.Cells[0,i]:=WADFile.TOCBlock.NameFile[i]+'.'+WADFile.TOCBlock.ExtFile[i];
StringGrid1.Cells[1,i]:='0x'+IntToHex(WADFile.TOCBlock.PointerFile[i],WADFile.TOCBlock.PointerFile[i].Size);
StringGrid1.Cells[2,i]:='0x'+IntToHex(WADFile.TOCBlock.SizeFile[i],WADFile.TOCBlock.SizeFile[i].Size);
end;
end else ShowMessage('Это не WAD:(');
end;
procedure TForm1.ExtractBlock(NumFile: Integer);//Извлечение определённого блока
var
Temp:TFileStream;
I:Integer;
begin
SaveDialog1.FileName:=WADFile.TOCBlock.NameFile[NumFile];
SaveDialog1.DefaultExt:=WADFile.TOCBlock.ExtFile[NumFile];
if not SaveDialog1.Execute then exit else begin
f.Position:=WADFile.TOCBlock.PointerFile[NumFile];
ShowMessage(IntToHex(WADFile.TOCBlock.SizeFile[NumFile],4));
Temp:=TFileStream.Create(SaveDialog1.FileName,fmCreate);
Temp.CopyFrom(F,WADFile.TOCBlock.SizeFile[NumFile]);
Temp.Free;
end;
end;Теперь на кнопку Файл->Открыть повесим код
if not OpenDialog1.Execute then exit else begin
ExtractWad;
end;А на кнопку Архив->Извлечь файл повесим код
ExtractBlock(StringGrid1.Row);Я ещё этот код повесил на двойной клик по StringGrid, мне так удобней ^_^
Ну, вроде, всё работает B)
Давайте напишем ещё функцию извлечения всех файлов и пойдём пить чай с тортиком.
Так, сначала в Uses добавим ShlObj, ActiveX, потом в код добавим функцию(лучше в начало):
function AdvSelectDirectory(const Caption: string; const Root: WideString;
var Directory: string; EditBox: Boolean = False; ShowFiles: Boolean = False;
AllowCreateDirs: Boolean = True): Boolean;
function SelectDirCB(Wnd: HWND; uMsg: UINT; lParam, lpData: lParam): Integer;
stdcall;
var
PathName: array[0..MAX_PATH] of Char;
begin
case uMsg of
BFFM_INITIALIZED: SendMessage(Wnd, BFFM_SETSELECTION, Ord(True), Integer(lpData));
end;
Result := 0;
end;
var
WindowList: Pointer;
BrowseInfo: TBrowseInfo;
Buffer: PChar;
RootItemIDList, ItemIDList: PItemIDList;
ShellMalloc: IMalloc;
IDesktopFolder: IShellFolder;
Eaten, Flags: LongWord;
const
BIF_USENEWUI = $0040;
BIF_NOCREATEDIRS = $0200;
begin
Result := False;
if not DirectoryExists(Directory) then
Directory := '';
FillChar(BrowseInfo, SizeOf(BrowseInfo), 0);
if (ShGetMalloc(ShellMalloc) = S_OK) and (ShellMalloc <> nil) then
begin
Buffer := ShellMalloc.Alloc(MAX_PATH);
try
RootItemIDList := nil;
if Root <> '' then
begin
SHGetDesktopFolder(IDesktopFolder);
IDesktopFolder.ParseDisplayName(Application.Handle, nil,
POleStr(Root), Eaten, RootItemIDList, Flags);
end;
OleInitialize(nil);
with BrowseInfo do
begin
hwndOwner := Application.Handle;
pidlRoot := RootItemIDList;
pszDisplayName := Buffer;
lpszTitle := PChar(Caption);
ulFlags := BIF_RETURNONLYFSDIRS or BIF_USENEWUI or
BIF_EDITBOX * Ord(EditBox) or BIF_BROWSEINCLUDEFILES * Ord(ShowFiles) or
BIF_NOCREATEDIRS * Ord(not AllowCreateDirs);
lpfn := @SelectDirCB;
if Directory <> '' then
lParam := Integer(PChar(Directory));
end;
WindowList := DisableTaskWindows(0);
try
ItemIDList := ShBrowseForFolder(BrowseInfo);
finally
EnableTaskWindows(WindowList);
end;
Result := ItemIDList <> nil;
if Result then
begin
ShGetPathFromIDList(ItemIDList, Buffer);
ShellMalloc.Free(ItemIDList);
Directory := Buffer;
end;
finally
ShellMalloc.Free(Buffer);
end;
end;
end;
Так, теперь в Type формы добавим:
procedure ExtractWadAll(Path:String);И начнём писать функцию для распаковки всех файлов:
Procedure TForm1.ExtractWadAll(Path:String);
var
I:Integer;
Temp:TFileStream;
begin
ProgressBar1.Max:=WADFile.CountFiles;
if path<>'' then
for I := 1 to WADFile.CountFiles do
begin
F.Position:=WADFile.TOCBlock.PointerFile[i];
Temp:=TFileStream.Create(Path+'\'+WADFile.TOCBlock.NameFile[i]+'.'+WADFile.TOCBlock.ExtFile[i],fmcreate);
Temp.CopyFrom(F,WADFile.TOCBlock.SizeFile[i]);
Temp.Free;
ProgressBar1.Position:=i;
end;
ProgressBar1.Position:=0;
ShowMessage('Готово');
end;
Осталось только добавить на кнопку извлечения всех файлов вот этот код:
var
s : string;
begin
AdvSelectDirectory('Выберите папку для распаковки', '', s, False, False, True);
ExtractWadAll(s);
end;Всё, готово, радуемся от того, какие мы молодцы. :)
На этом урок заканчивается, в следующем уроке мы продолжим разработку программы и может быть начнём искать шрифт или текст, спасибо за чтение ;)
Если будут вопросы - спрашивайте.
Исходники данного проекта в прикреплённых файлах, под именем SAEditorSourceLesson2.rar