Have you noticed how some compressors like winrar ® or winzip ® can create self-extracting files. Today we will see how we can generate these files using the ZLib unit wich is included with delphi.
The logic goes like this
1) Create a exe which the capacity of decompress a resource, this project is responsible for extract the compressed data, which was previously stored as a resource inside of self.
This is the code to extract the data stored inside of the resource
procedure Extract;
var
DeCompressStream : TDeCompressionStream;
ResourceStream : TResourceStream;
DestFileStream : TFileStream;
FileNameDest : String;
RecSFX : TRecSFX;
begin
if FindResource(0, 'SFXDATA', RT_RCDATA)=0 then //find the compressed data
begin
Application.MessageBox('Sorry i am empty','Warning',MB_OK+MB_ICONWARNING);
Exit;
end
else
if FindResource(0, 'SFXREC', RT_RCDATA)=0 then //find the header data
begin
Application.MessageBox('Sorry i dont have header data','Warning',MB_OK+MB_ICONWARNING);
Exit;
end;
try
ResourceStream:= TResourceStream.Create(0,'SFXREC',RT_RCDATA); //read the header stored in the resorce named SFXREC
try
ResourceStream.Position:=0;
Move(ResourceStream.Memory^,RecSFX,SizeOf(RecSFX));
ProgressBarSfx.Max:=RecSFX.Size;
finally
ResourceStream.Free;
end;
ResourceStream:= TResourceStream.Create(0,'SFXDATA',RT_RCDATA); //read the compressed data stores in the SFXDATA resource
try
ProgressBarSfx.Max:=ResourceStream.Size;
FileNameDest := EditPath.Text+ChangeFileExt(ExtractFileName(ParamStr(0)),'');
DestFileStream := TFileStream.Create(FileNameDest,fmCreate); //create the file to uncompress the data
try
DeCompressStream:=TDeCompressionStream.Create(ResourceStream);
DeCompressStream.OnProgress:=DoProgress; //assign the OnProgress event to see the progress
try
DestFileStream.CopyFrom(DeCompressStream,RecSFX.Size); //decompress the data
finally
DeCompressStream.Free;
end;
finally
DestFileStream.Free;
end;
finally
ResourceStream.Free;
end;
except on e : exception do
Application.MessageBox(PAnsiChar(e.Message),'Error',MB_OK+MB_ICONERROR);
end;
end;
2) Transform this project in a resource and attach this resource to the second project, using the BRCC32.exe tool
create a file called Stub.rc with this content
STUB RT_RCDATA "SfxExtractor.exe"
now compile the rc file
BRCC32.exe Stub.rc
3) include the generated Stub.res file in the second project
{$R Stub.res}
4) Now the second project select a file to compress
5) Extract the STUB resource and then create a new exe file
var
StubStream: TResourceStream;
begin
StubStream := TResourceStream.Create( HInstance, 'STUB', 'RT_RCDATA');
try
DeleteFile(FSfxFileName);
StubStream.SaveToFile(FSfxFileName);
finally
StubStream.Free;
end;
Result:=FileExists(FSfxFileName);
end;
6) Compress the selected file using the TCompressionStream class and add two resources to the New STUB exe,
one resource store the header info (Original filename, size) and the another store the compressed data.
check the code wich compress the data in a resource and create the two resources in the STUB exe.
procedure CreateSFX;
var
SrcFileStream : TFileStream;
CompressedStream: TMemoryStream;
hDestRes : THANDLE;
Compressor : TCompressionStream;
RecSFX : TRecSFX;
begin
SrcFileStream := TFileStream.Create(FSrcFileName,fmOpenRead or fmShareDenyNone); //open the file to compress
ProgressBarSfx.Max := SrcFileStream.Size;
try
try
CompressedStream:= TMemoryStream.Create;
try
Compressor:=TCompressionStream.Create(GetCompressionLevel,CompressedStream); //create the stream to compress the data
try
Compressor.OnProgress:=DoProgress;
Compressor.CopyFrom(SrcFileStream,0);
finally
Compressor.Free;
end;
//Write the header
FillChar(RecSFX,SizeOf(RecSFX),#0);
RecSFX.Size:=SrcFileStream.Size;
Move(ExtractFileName(FSrcFileName)[1],RecSFX.Name,Length(ExtractFileName(FSrcFileName)));
hDestRes:= BeginUpdateResource(PAnsiChar(FSfxFileName), False);
if hDestRes <> 0 then
if UpdateResource(hDestRes, RT_RCDATA,'SFXREC',0,@RecSFX,SizeOf(RecSFX)) then //create the resource in the exe with the header info
if EndUpdateResource(hDestRes,FALSE) then
else
RaiseLastOSError
else
RaiseLastOSError
else
RaiseLastOSError;
hDestRes:= BeginUpdateResource(PAnsiChar(FSfxFileName), False);
if hDestRes <> 0 then
if UpdateResource(hDestRes, RT_RCDATA,'SFXDATA',0,CompressedStream.Memory,CompressedStream.Size) then //create the resource in the exe with the compressed data
if EndUpdateResource(hDestRes,FALSE) then //if all is ok show the summary info
begin
LabelInfo.Caption:=
Format('SFX Created %sOriginal Size %s %sCompressed Size %s Ratio %n %%',[#13,FormatFloat('#,',SrcFileStream.Size),#13,FormatFloat('#,',CompressedStream.Size),CompressedStream.Size*100/SrcFileStream.Size]);
ProgressBarSfx.Position:=ProgressBarSfx.Max;
ButtonCreateSFX.Enabled:=False;
end
else
RaiseLastOSError
else
RaiseLastOSError
else
RaiseLastOSError;
finally
CompressedStream.Free;
end;
finally
SrcFileStream.Free;
end;
except on e : exception do
Application.MessageBox(PAnsiChar(e.Message),'Error',MB_OK+MB_ICONERROR);
end;
end;
You can add many features like password, checksum validation, encryption and others to the STUB Application. just keep in mind final file size of the STUB.
Finally when you run the second application (Project CreateSFX) and select a file the aplication will create a SFX file.
Project SfxExtractor
{$SetPEFlags 1} // remove relocation table
unit MainSFX;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls;
type
TFrmMain = class(TForm)
ButtonSelDir: TButton;
EditPath: TEdit;
ButtonExtract: TButton;
ProgressBarSfx: TProgressBar;
Label1: TLabel;
procedure ButtonSelDirClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure ButtonExtractClick(Sender: TObject);
private
procedure Extract;
procedure DoProgress(Sender: TObject);
public
end;
var
FrmMain: TFrmMain;
implementation
{$R *.dfm}
uses
ShlObj,
ZLib,
Common;
function SelectFolderCallbackProc(hwnd: HWND; uMsg: UINT; lParam: LPARAM; lpData: LPARAM): Integer; stdcall;
begin
if (uMsg = BFFM_INITIALIZED) then
SendMessage(hwnd, BFFM_SETSELECTION, 1, lpData);
Result := 0;
end;
function SelectFolder(hwndOwner: HWND;const Caption: string; var InitFolder: string): Boolean;
var
ItemIDList: PItemIDList;
idlRoot : PItemIDList;
Path : PAnsiChar;
BrowseInfo: TBrowseInfo;
begin
Result := False;
Path := StrAlloc(MAX_PATH);
SHGetSpecialFolderLocation(hwndOwner, CSIDL_DRIVES, idlRoot);
with BrowseInfo do
begin
hwndOwner := GetActiveWindow;
pidlRoot := idlRoot;
SHGetSpecialFolderLocation(hwndOwner, CSIDL_DRIVES, idlRoot);
pszDisplayName := StrAlloc(MAX_PATH);
lpszTitle := PAnsiChar(Caption);
lpfn := @SelectFolderCallbackProc;
lParam := LongInt(PAnsiChar(InitFolder));
ulFlags := BIF_RETURNONLYFSDIRS OR BIF_USENEWUI;
end;
ItemIDList := SHBrowseForFolder(BrowseInfo);
if (ItemIDList <> nil) then
if SHGetPathFromIDList(ItemIDList, Path) then
begin
InitFolder := Path;
Result := True;
end;
end;
procedure TFrmMain.Extract;
var
DeCompressStream : TDeCompressionStream;
ResourceStream : TResourceStream;
DestFileStream : TFileStream;
FileNameDest : String;
RecSFX : TRecSFX;
begin
if FindResource(0, 'SFXDATA', RT_RCDATA)=0 then
begin
Application.MessageBox('Sorry i am empty','Warning',MB_OK+MB_ICONWARNING);
Exit;
end
else
if FindResource(0, 'SFXREC', RT_RCDATA)=0 then
begin
Application.MessageBox('Sorry i dont have header data','Warning',MB_OK+MB_ICONWARNING);
Exit;
end;
try
ResourceStream:= TResourceStream.Create(0,'SFXREC',RT_RCDATA);
try
ResourceStream.Position:=0;
Move(ResourceStream.Memory^,RecSFX,SizeOf(RecSFX));
ProgressBarSfx.Max:=RecSFX.Size;
finally
ResourceStream.Free;
end;
ResourceStream:= TResourceStream.Create(0,'SFXDATA',RT_RCDATA);
try
ProgressBarSfx.Max:=ResourceStream.Size;
FileNameDest := EditPath.Text+ChangeFileExt(ExtractFileName(ParamStr(0)),'');
DestFileStream := TFileStream.Create(FileNameDest,fmCreate);
try
DeCompressStream:=TDeCompressionStream.Create(ResourceStream);
DeCompressStream.OnProgress:=DoProgress;
try
DestFileStream.CopyFrom(DeCompressStream,RecSFX.Size);
finally
DeCompressStream.Free;
end;
finally
DestFileStream.Free;
end;
finally
ResourceStream.Free;
end;
except on e : exception do
Application.MessageBox(PAnsiChar(e.Message),'Error',MB_OK+MB_ICONERROR);
end;
end;
procedure TFrmMain.ButtonExtractClick(Sender: TObject);
begin
Extract;
end;
procedure TFrmMain.ButtonSelDirClick(Sender: TObject);
var
Path: String;
begin
Path:=EditPath.Text;
if SelectFolder(Handle,'Select the output directory',Path) then
EditPath.Text:=IncludeTrailingPathDelimiter(Path);
end;
procedure TFrmMain.DoProgress(Sender: TObject);
begin
ProgressBarSfx.Position:=TCustomZLibStream(Sender).Position;
end;
procedure TFrmMain.FormCreate(Sender: TObject);
begin
EditPath.Text:=IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)));
end;
end.
Project CreateSFX
unit MainCreateSFX;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, ExtCtrls, ZLib;
type
TFrmCreateSFX = class(TForm)
ButtonCreateSFX: TButton;
OpenDialog1: TOpenDialog;
EditFile: TEdit;
ButtonSelect: TButton;
ProgressBarSfx: TProgressBar;
LabelInfo: TLabel;
Label1: TLabel;
RadioButton1: TRadioButton;
RadioButton2: TRadioButton;
RadioButton3: TRadioButton;
RadioButton4: TRadioButton;
procedure ButtonCreateSFXClick(Sender: TObject);
procedure ButtonSelectClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
FSrcFileName : string;
FSfxFileName : string;
function CreateStub:Boolean;
function GetCompressionLevel: TCompressionLevel;
procedure CreateSFX;
procedure DoProgress(Sender: TObject);
end;
var
FrmCreateSFX: TFrmCreateSFX;
implementation
uses Common;
{$R *.dfm}
{$R Stub.res}
procedure TFrmCreateSFX.ButtonCreateSFXClick(Sender: TObject);
begin
if CreateStub then
CreateSFX;
end;
procedure TFrmCreateSFX.ButtonSelectClick(Sender: TObject);
begin
if OpenDialog1.Execute(Handle) then
begin
EditFile.Text:=OpenDialog1.FileName;
FSrcFileName:=OpenDialog1.FileName;
FSfxFileName:=ExtractFilePath(ParamStr(0))+ExtractFileName(EditFile.Text)+'.exe';
ButtonCreateSFX.Enabled:=True;
ButtonSelect.Enabled:=False;
end;
end;
procedure TFrmCreateSFX.CreateSFX;
var
SrcFileStream : TFileStream;
CompressedStream: TMemoryStream;
hDestRes : THANDLE;
Compressor : TCompressionStream;
RecSFX : TRecSFX;
begin
SrcFileStream := TFileStream.Create(FSrcFileName,fmOpenRead or fmShareDenyNone);
ProgressBarSfx.Max := SrcFileStream.Size;
try
try
CompressedStream:= TMemoryStream.Create;
try
Compressor:=TCompressionStream.Create(GetCompressionLevel,CompressedStream);
try
Compressor.OnProgress:=DoProgress;
Compressor.CopyFrom(SrcFileStream,0);
finally
Compressor.Free;
end;
FillChar(RecSFX,SizeOf(RecSFX),#0);
RecSFX.Size:=SrcFileStream.Size;
Move(ExtractFileName(FSrcFileName)[1],RecSFX.Name,Length(ExtractFileName(FSrcFileName)));
hDestRes:= BeginUpdateResource(PAnsiChar(FSfxFileName), False);
if hDestRes <> 0 then
if UpdateResource(hDestRes, RT_RCDATA,'SFXREC',0,@RecSFX,SizeOf(RecSFX)) then
if EndUpdateResource(hDestRes,FALSE) then
else
RaiseLastOSError
else
RaiseLastOSError
else
RaiseLastOSError;
hDestRes:= BeginUpdateResource(PAnsiChar(FSfxFileName), False);
if hDestRes <> 0 then
if UpdateResource(hDestRes, RT_RCDATA,'SFXDATA',0,CompressedStream.Memory,CompressedStream.Size) then
if EndUpdateResource(hDestRes,FALSE) then
begin
LabelInfo.Caption:=
Format('SFX Created %sOriginal Size %s %sCompressed Size %s Ratio %n %%',[#13,FormatFloat('#,',SrcFileStream.Size),#13,FormatFloat('#,',CompressedStream.Size),CompressedStream.Size*100/SrcFileStream.Size]);
ProgressBarSfx.Position:=ProgressBarSfx.Max;
ButtonCreateSFX.Enabled:=False;
end
else
RaiseLastOSError
else
RaiseLastOSError
else
RaiseLastOSError;
finally
CompressedStream.Free;
end;
finally
SrcFileStream.Free;
end;
except on e : exception do
Application.MessageBox(PAnsiChar(e.Message),'Error',MB_OK+MB_ICONERROR);
end;
end;
function TFrmCreateSFX.CreateStub:Boolean;
var
StubStream: TResourceStream;
begin
StubStream := TResourceStream.Create( HInstance, 'STUB', 'RT_RCDATA');
try
DeleteFile(FSfxFileName);
StubStream.SaveToFile(FSfxFileName);
finally
StubStream.Free;
end;
Result:=FileExists(FSfxFileName);
end;
procedure TFrmCreateSFX.DoProgress(Sender: TObject);
begin
ProgressBarSfx.Position:=TCustomZLibStream(Sender).Position;
LabelInfo.Caption:=Format('Compressed %s bytes %n %%',[FormatFloat('#,',TCustomZLibStream(Sender).Position),100*TCustomZLibStream(Sender).Position/ProgressBarSfx.Max]);
LabelInfo.Update;
end;
procedure TFrmCreateSFX.FormCreate(Sender: TObject);
begin
LabelInfo.Caption:='';
end;
function TFrmCreateSFX.GetCompressionLevel: TCompressionLevel;
var
i : Integer;
begin
Result:=clMax;
for i:= 0 to ComponentCount - 1 do
if Components[i].ClassType = TRadioButton then
if TRadioButton(Components[i]).Checked then
Result:=TCompressionLevel(TRadioButton(Components[i]).Tag);
end;
end.
Notes:
* You can improve the final results if your rewrite the stub application using a library like KOL or avoid the use of the VCL using the WINAPI, to reduce the final exe size.
* These samples applications are for educational purposes only and not pretend be an alternative to another professional tools to generate a SFX file.
* The concept discussed in this entry can help you to build compressed and encrypted files using your own logic.


November 25, 2010 at 3:38 am
If you don’t want to use resources (which must be created at compile time), but append a .ZIP archive to the SFX exe, see http://synopse.info/forum/viewtopic.php?pid=232#p232
With our LVCL units (i.e. Light VCL replacements) together with the PasZip unit, you can create a whole SFX exe which is less than 30 KB, with a form and labels and buttons… and without using UPX (which is to be avoided).
November 25, 2010 at 8:14 am
Thanks for you recommendation, can be a good option to create a small SFX file if you want use third party libraries.
November 25, 2010 at 9:43 am
@Rogrigo – Indeed: it needs more configuration of your project. And the LVCL is far from perfect. It’s tested up to Delphi 2007 only.
Your article is very good. And, IMHO it’s a good idea to put the source inside the text. Much faster to get an idea. Source code is easier to understand than plain English, when it is well designed.
November 25, 2010 at 4:50 am
very good article, however a better practice(my opinion) is to attach the file(s) at the end of the executable…
November 25, 2010 at 8:18 am
Dorin, thanks for your comments, about your opinion is valid but remember wich this sample show a way to use resources and the zlib unit to create a sfx file. Exist many more methods to create a SFX file this is just another way ;)
November 25, 2010 at 10:30 pm
Please note that you can adjust / add / remove resources in a PE file (exe/dll) after compilation. See BeginUpdateResource, UpdateResource and EndUpdateResource in MSDN. I use it to alter some resources after compilation (using Open Tools API) like embedding the mapfile into the executable.
November 25, 2010 at 10:36 pm
Ritsaert, that’s exactly what the application CreateSFX does, adds the compressed resource and the header resource to the stub file using the function UpdateResource. check the code of the Project CreateSFX.
February 14, 2011 at 8:49 am
Great article. The code works great and it’s exactly what I needed. If you’re ever in Romania, you’ve got a beer from me :)
February 14, 2011 at 11:59 am
Oh, and I have a question, is it possible to add another row to the CreateSFX exe file that will allow me to write a string (URL) in the Stub?
I’m asking this because I want the installer to be able to open an URL when the installation finished, but the URL will be different for different installations.
So the end result is this, after the installation is complete, the stub reads a string from itself which is an URL and it will open it.
If it can be done, can you please tell me how? Thanks.