====== Запаковка ресурсов программы в ZIP архив, и работа с ним из памяти. Нативно из Delphi. ======
===== Концепт и статья =====
Люди которые программируют на Delphi очень давно уже ищут способ, каким образом можно сохранить ресурсы программы внутри самой программы, ну и желательно что-бы до файлов не было доступа из вне. Вот и я однажды об этом задумался, когда разберал программку написанную на Java. Ведь язык Java создаёт нативные пакеты jar которые по сути обычный zip файл. Содержащий в себе все ресурсы и бинарные исполняемые пакеты .class. Конечно в самом Java это понять можно, у них всё сконцентрировано на кроссплатформенность. То-есть логический пакет jar должен свободно запускаться как на суперкомпьютере так и на микроволновке. Конечно если программист который писал код подумал о том что я захочу его запустить скажем на кпк или той-же микроволновке. Ну ладно что-то я отошел от темы.
Итак задачи :
- Лёгкая компиляция архива .zip при компиляции кода delphi.
- Лёгкий импорт кода внутрь приложения.
- Быстрое чтение архива из памяти и возможность сохранения на диск (кэш, инсталлеры).
- Защита архива от шальных ручёнок и любопытных глаз.
Приступим :
Для начала я поднял мануалы по Delphi XE и нашел в них нативный компонент для работы с zip архивом. Уж ей богу Borland и CodeGear отличные компании которые создали максимально доступный и очень функциональный язык в котором есть практический всё в одном пакете. Купил, поставил, работаешь. Поэтому я не сторонник качать кучу библиотек из интернета и потом непонятно как она работает, что в ней реализовано, что нет, и каждый раз платить за платные версии дописанных библиотек плагинов и.т.п. Вообщем класс называется TZipFile и лежит он в библиотеке System.Zip. Итак изучаем мануал, конструктор у него обычный TZipFile.Create() без параметров что поможет загружать-выгружать его через try/finally. А ещё он имеет в себе функцию TZipFile.Open которая уже гораздо интереснее. Функция Open перегруженная (overload) и имеет 2 вида. 1й это загрузить zip из файла (const ZipFileName: string; OpenMode: TZipMode), но нас интересует больше 2й способ - загрузить zip из потока (ZipFileStream: TStream; OpenMode: TZipMode). Где первый параметр у нас сам поток, смею предположить что в моём случае будет TMemoryStream, а второй параметр это режим открытия файла в нашем случае открыть файл на чтение zmRead ( type TZipMode = (zmClosed, zmRead, zmReadWrite, zmWrite)).
Так-же есть функции\\
* распаковки - TZipFile.Extract и TZipFile.ExtractAll
* чтения заголовков - TZipFile.GetFileCount, TZipFile.GetFileInfo, TZipFile.GetFileName
* и сохранение распакованных данных в поток TStream или массив байтов TBytes - TZipFile.Read.
По поводу как спрятать архив, идеи следующие. Ресурсный файл отпадает автоматом. Уже лет 10 есть программа Resource Hacker (кстати тоже написана на Delphi). Которая совершенно свободно распаковывает, запаковывает и изменяет любой ресурс приложения. А этого-бы очень не хотелось. Была так-же идея добавить zip в конце кода exe и просто подправить контрольную сумму файла CRC. Что конечно-же гемор. Не буду-же я при каждой компиляции открывать PE редактор и менять CRC. Тут я было чуток огорчился, но на помощь пришла гениальная идея! Когда-то очень давно наши отцы и деды писали на паскале и держали заготовленные файлы в константах. Константа неотъемлемая часть языка, загружается с программой непосредственно в память приложения и нагрузку на исходный код кроме как компиляции и времени на запуск приложения не несёт. Из чего этот вариант очень приемлем для увеличения безопасности, но бьёт по скорости загрузки приложения. Итак если в архиве нечего важного нету, то используем TResourceStream и кладём архив в файл .res через brcc32. Если нам нужна безопасность то есть скриптик написанный на PHP, который сделает из zip константу для Delphi.
"$00, $00, $00, $00"
return '$' . substr( chunk_split(bin2hex($buffer), 2, ', $'), 0, -3);
}
if ( $src !== false )
{
# Получить размер файла
fseek($src, 0, SEEK_END);
$src_size = ftell($src);
fseek($src, 0, SEEK_SET);
# Открыть файл получатель
$dst = fopen('include_me.inc', 'wb');
if ( $dst !== false )
{
fwrite($dst, 'const buffer_size = '. $src_size .";\r\n");
fwrite($dst, 'const buffer: array[0..'. ($src_size - 1) .'] of Byte = ('."\r\n");
$buffsz = $src_size;
while ( $src_size > 0 )
{
$buffsz = $src_size > 80 ? 80 : $src_size;
$buffer = fread($src, $buffsz);
$buffer = convert2pascal($buffer);
fwrite($dst, $buffer);
$src_size -= $buffsz;
if ( $src_size > 0 ) fwrite($dst, ",\r\n");
}
fwrite($dst, "\r\n);");
}
fclose($src);
}
Что делает этот код - берёт файл Resources.zip и делает из него файл include_me.inc для Delphi.
Сожержимое файла примерно следующее.
const buffer_size = 56548;
const buffer: array[0..56547] of Byte = (
$50, $4b, $03, $04, $0a, $00, $00, $00, $00, $00, $b5, $60, $0c, $49, $00, $00,
$00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $0a, $00, $00, $00, $52, $65,
$73, $6f, $75, $72, $63, $65, $73, $2f // ... и так далее до 56547го байта ...
);
Теперь у нас есть файл который мы можем добавить через дерективу компелятора Include - {$I 'include_me.inc'}
Имея бинарные данные нам надо сделать из них TMemoryStream. Как мы можем это сделать. Например просто создать TMemoryStream и скопировать блок памяти в блок памяти TMemoryStream.
PMem := TMemoryStream.Create; /// Создаём поток памяти
try
PMem.SetSize(BufferSize); /// Размер потока = Размеру нашего буффера
Move(@buffer[0]^, PMem.Memory^, BufferSize); // Скопировать Наш буффер в буффер памяти TMemoryStream на размер буффера байт
// Хочу отдельно заметить PMem.Memory = @buffer недопустимая ошибка! Удалив изначалььный адрес в TMemoryStream.Memory создаст нехилую утечку памяти
// Некогда не делайте так когда вы работаете с указателями (Pointers)
// Тут мы можем что-то сделать с нашим потоком в котором лежит наш zip файл целиком.
finally
FreeAndNil(PMem);
end
Ну а теперь можем передать поток первым параметром самому TZipFile.Open и работать с ним как с обычным архивом.
PZip := TZipFile.Create();
try
PZip.Open(PMem, zmRead);
// Тут работаем как с обычным zip файлом.
finally
FreeAndNil(PZip);
end;
===== Исходный код программы =====
Сам исходный код примера программы, написанный на Delphi XE. Быстро топорно и на коленке. Просто проверить как эта хрень работает.
program TestProject;
{$APPTYPE CONSOLE}
{$R *.res}
{$DEFINE INC_FROM_FILE}
uses
System.SysUtils,
System.Classes,
System.Zip;
{ LZ Data }
{$IFDEF INC_FROM_FILE}
{$INCLUDE 'include_me.inc'}
{$ELSE}
const buffer_size = 2;
const buffer: array[0..1] of Byte = ($00, $00);
{$ENDIF}
type
TLZBin = class(TObject)
private
PMem : TMemoryStream;
PZip : TZipFile;
public
constructor Create(BaseAddr: Pointer; BufferSize: Int64);
destructor Destroy; override;
function HaveFile(FileName: string): Boolean;
function FileToStream(FileName: string; WriteToStream: TStream): Boolean;
end;
{ TLZBin }
constructor TLZBin.Create(BaseAddr: Pointer; BufferSize: Int64);
begin
inherited Create();
PMem := TMemoryStream.Create;
PMem.SetSize(BufferSize);
Move(BaseAddr^, PMem.Memory^, BufferSize);
PZip := TZipFile.Create();
PZip.Open(PMem, zmRead);
end;
destructor TLZBin.Destroy;
begin
FreeAndNil(PZip);
FreeAndNil(PMem);
inherited;
end;
function TLZBin.FileToStream(FileName: string; WriteToStream: TStream): Boolean;
var
DecompressionStream: TStream;
LocalHeader: TZipHeader;
begin
Result := False;
Assert(Assigned(WriteToStream), '2nd param must be defined first');
PZip.Read(FileName, DecompressionStream, LocalHeader);
try
WriteToStream.CopyFrom(DecompressionStream, DecompressionStream.Size);
Result := True;
finally
DecompressionStream.Free;
end;
end;
function TLZBin.HaveFile(FileName: string): Boolean;
begin
Result := PZip.IndexOf(FileName) > -1;
end;
{ $EXE }
var lz: TLZBin;
file_contents: TStringStream;
begin
try
lz := TLZBin.Create(@buffer[0], buffer_size);
try
if lz.HaveFile('Resources/Kbits.txt') then
begin
Writeln('File Exists, unpacking...');
// read an file
file_contents := TStringStream.Create();
try
lz.FileToStream('Resources/Kbits.txt', file_contents);
file_contents.Seek(0,0);
Writeln(file_contents.ReadString(file_contents.Size));
finally
FreeAndNil(file_contents);
end;
// end of read
end
else
Writeln('File Not Exists');
finally
FreeAndNil(lz);
end;
except on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
===== Исходный код архивчиком для скачивания =====
{{works:programmer:delphi:delphi-zip-resource.zip}}