Click here to Skip to main content
15,890,557 members
Articles / Desktop Programming / MFC
Article

Creating a Self Extracting Executable

Rate me:
Please Sign up or sign in to vote.
4.98/5 (22 votes)
20 Aug 2002CPOL3 min read 309.1K   5.7K   108   75
A class that allows you to create self extracting executables for use in distribution or setup programs

Self Extractor Builder

Introduction

This class allows you to create self extracting executables for use in distribution or setup programs. Many people have helped me along the way to produce this class but here are a couple I would like to thank.

  • Levente Farkas - For the suggestion of how the class might be implemented
  • Roger Allen - For further advice on the reading and writing of the data
  • Jamie Thornback - For help with the callback procedures
  • Tim Johnson - For his CShellFileOp class which is used briefly in this class

A new addition to this class is compression courtesy of Zlib and the following contributors: -

  • Luca Piergentili - For his suggestions and source code contributions for the compression features.
  • Mark Nelson - For his Zlib wrapper class which I mercylessly butchered into my own code.

Description of the Self-Extracting (SFX) executable

The SFX file which this class creates consists of an extraction executable with the data files appended to the end of it. The data on the end of the file does not affect the executable image and so the file executes as if the data wasn't even there. So to extract the data, the executable must first detach the data from itself and then create the approriate files. The way I have chosen to do this is to write a 'Table of Contents' (TOC) after the data which can be read by the extractor to find out where the various files are stored in the data segment.

File Layout

The layout of the TOC is as follows:-

Starting from the end of the archive and working backwards :

Header Info

  • 10 bytes - Signature (Identifier for SFX archive)
  • 4 bytes - Version number of SFX archive
  • 4 bytes - Number of files in archive

Table of Contents

This section contains one record in the following format for each file

  • 4 bytes - Length of filename
  • variable length - Filename
  • 4 bytes - Length of File (compressed)
  • 4 bytes - Length of File (uncompressed)
  • 4 bytes - Offset in archive to data

Data Segment

Each file is the compressed in memory using zlib and then written in the order of the TOC. After this is the extractor executable.

How To Use it

Having said all that, you don't need to know any of that stuff above to use it. All you need to do is create an instance of CSelfExtractor and then call AddFile() to add in all the files that you want to include. Next call Create() to create the archive.

The demo project consists of two projects - 'Extractor' which is the executable which extracts the archive and Self Extractor which is the program for building Self Extracting archives. Self Extractor allows you to specify an external extractor program to use for the archive or alternatively you can use the extractor which has been compiled into the program inside the resources. Read the source code to find out more.

The Zlib source code is subject to the licence documented here. The demos make use of classes written by other people at both codeguru.com and here at codeproject.com so any bugs in those should be directed at their respective authors.

Updates

21st August 2002 - Updated code with fixes suggested by readers relating to file permissions and CFileDialog. Also updated Zlib to v1.14 which fixes an important security problem.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
United Kingdom United Kingdom
James is currently working as a Software Engineer providing large scale Warehouse Management Systems and Airport Baggage Handling Systems. He is a Windows specialist but nowadays spends about 65% of his time fighting with VI in a vain attempt to get his UNIX C code to compile. He has been programming in C/C++ for 6 years and Visual C++/MFC for 4 years.

In his spare time James plays a variety of musical instruments including guitar and piano with varying degrees of success. He has been told he spends too much time and money in the pub but doesn't everyone have their own stool at the bar?

James is originally from Nottingham (no Robin Hood jokes please) but is now based in sunny Manchester, UK.

The attached photo shows James in his favourite position, drinking beer with a hand growing out of his neck.

Comments and Discussions

 
GeneralMAX_FILE -- Suggestion Pin
J Chandra Sekhar Achary11-Feb-04 20:39
J Chandra Sekhar Achary11-Feb-04 20:39 
GeneralRe: MAX_FILE -- Suggestion Pin
James Spibey11-Feb-04 21:02
James Spibey11-Feb-04 21:02 
GeneralRe: MAX_FILE -- Suggestion Pin
J Chandra Sekhar Achary11-Feb-04 21:14
J Chandra Sekhar Achary11-Feb-04 21:14 
GeneralRe: MAX_FILE -- Suggestion Pin
jauming20-Feb-12 5:52
jauming20-Feb-12 5:52 
GeneralMaintaining file path information Pin
Lee.W.Spencer20-Nov-03 5:47
Lee.W.Spencer20-Nov-03 5:47 
GeneralRe: Maintaining file path information Pin
James Spibey20-Nov-03 7:42
James Spibey20-Nov-03 7:42 
GeneralRe: Maintaining file path information Pin
Lee.W.Spencer20-Nov-03 10:39
Lee.W.Spencer20-Nov-03 10:39 
GeneralRe: Maintaining file path information Pin
jauming19-Feb-12 22:06
jauming19-Feb-12 22:06 
fix: must make ascii-z c-string:

modify: SelfExtracter.cpp

CSelfExtractor::CreateArchive() and CSelfExtractor::ReadTOC():

fixed project source (self_extractor120220_2.7z) uploaded to :

https://skydrive.live.com/redir.aspx?cid=c17d9b5257172422&resid=C17D9B5257172422!2159&parid=C17D9B5257172422!819&authkey=!AFysZDoThtTrK3k[^]

int CSelfExtractor::CreateArchive(CFile& File, funcPtr pFn, void* userData)
{
char buffer[1000]; // Buffer for data
CFile data; // Input file

try
{
//Copy all the inout files into the archive
for(int i = 0; i < m_nFiles; i++)
{
// Open the input file
if(data.Open(m_InfoArray[i].GetPathname() , CFile::modeRead | CFile::shareDenyNone))
{
// Call the user defined CallBack
if(pFn != NULL)
pFn(SFX_FILE_START, static_cast<void*>(&m_InfoArray[i]), userData);

// Record Start Offset
m_InfoArray[i].SetOffset(File.GetPosition());

// Zlib Stuff
int err = Z_OK;
avail_in = 0;
avail_out = ZLibOutputLength;
next_out = m_ZLibOutputBuffer;
m_ZLibFileLength = data.GetLength();
int level = 6;

deflateInit(this, level);

for ( ; ; )
{
// Load input data
if(!ZLibLoadInput(data))
break;

err = deflate(this, Z_NO_FLUSH);

ZLibFlushOutput(File);

if(err != Z_OK)
break;

// Call the user defined CallBack
if(pFn != NULL)
{
int nPercent = Percent();
pFn(SFX_ZIP_INFO, &nPercent, userData);
}
}

// Close this input file
data.Close();

for ( ; ; )
{
err = deflate(this, Z_FINISH);

// Write the output
if (!ZLibFlushOutput(File))
break;

if(err != Z_OK)
break;
}

deflateEnd(this);

// Update the info array with the new compressed size
m_InfoArray[i].SetCompressedSize(total_out);

// Call the user defined CallBack
if(pFn != NULL)
pFn(SFX_FILE_END, static_cast<void*>(&m_InfoArray[i]), userData);
}
else
return INPUT_FILE_ERROR;
}

//Now Write the TOC //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
for(int j = 0; j < m_nFiles; j++)
{
//Write the File Offset
int Offset = m_InfoArray[j].GetFileOffset();
File.Write(&Offset, sizeof(int));

//Write the File Size
int len = m_InfoArray[j].GetFileSize();
File.Write(&len, sizeof(int));

//Write the Compressed File Size
len = m_InfoArray[j].GetCompressedSize();
File.Write(&len, sizeof(int));

//Write the filename
len = m_InfoArray[j].GetFilename().GetLength();
strncpy(buffer, m_InfoArray[j].GetFilename(), len);
File.Write(buffer, len);

//Write the length of the filename
File.Write(&len, sizeof(int));

//Write the relative path //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
len = m_InfoArray[j].GetRelativePath().GetLength();
strncpy(buffer, m_InfoArray[j].GetRelativePath(), len);

if (len) buffer[len]=0;
if (!len)
{
strcpy(buffer,".\\");
len=2;
}
CString tmp;
tmp.Format("[%s][%d]",buffer,len);
//AfxMessageBox(tmp);
if (len) /*pFile->*/File.Write(buffer, len);
else
{
strcpy(buffer,".\\");
len=2;
}

//Write the length of the relative path
/*pFile->*/File.Write(&len, sizeof(int));
}

//Write the total number of files
File.Write((void*)&m_nFiles, sizeof(int));

//Write the vesion
strcpy(buffer, SFX_VERSION);
File.Write(buffer, strlen(SFX_VERSION));

//Write the SIG
strcpy(buffer, SIGNATURE);
File.Write(buffer, strlen(SIGNATURE));
}
catch(CFileException* e)
{
//Got sick of seeing 'unreferenced local variable'
e->m_cause;
return OUTPUT_FILE_ERROR;
}

return UNKNOWN_ERROR;
}


int CSelfExtractor::ReadTOC(CString Filename)
{

CFile Thisfile; //Archive file
char buffer[1000]; //Buffer to read and write with

//Clear the CSEFileInfo class array
Reset();

//Open the archive
if(!Thisfile.Open(Filename, CFile::modeRead | CFile::shareDenyNone))
return NO_SOURCE;
else
{
//Read in the signature
Thisfile.Seek(- static_cast<int>(strlen(SIGNATURE)), CFile::end);
Thisfile.Read(buffer, strlen(SIGNATURE));

// Check that it matches
buffer[strlen(SIGNATURE)]=0;
CString tmp;
tmp.Format("[%s][%s]",buffer, SIGNATURE);
//AfxMessageBox(tmp);
if(strncmp(buffer, SIGNATURE, strlen(SIGNATURE)) != 0)
{
return INVALID_SIG;
}//else AfxMessageBox("SIGNATURE ok");

// Read in the version
int LastOffset = strlen(SIGNATURE) + strlen(SFX_VERSION);
Thisfile.Seek(-LastOffset, CFile::end);
Thisfile.Read(buffer, strlen(SFX_VERSION));

// Check that the version matches
buffer[strlen(SFX_VERSION)]=0;
CString tmp2;
tmp2.Format("[%s][%s]",buffer, SFX_VERSION);
//AfxMessageBox(tmp2);
if(strncmp(buffer, SFX_VERSION, strlen(SFX_VERSION)) != 0)
{
return INVALID_SIG;
}//else AfxMessageBox("SFX_VERSION ok");

// Read Number of files
LastOffset += sizeof(int);
Thisfile.Seek(-LastOffset, CFile::end);
Thisfile.Read(&m_nFiles, sizeof(int));

//If there are no files in the archive, there is nothing to extract
if(m_nFiles == 0)
{
AfxMessageBox("m_nFiles == 0");
return NOTHING_TO_DO;
}else
{
CString tmp5;
tmp5.Format("m_nFiles=%d,",m_nFiles);
// AfxMessageBox(tmp5);
}

//Read the TOC in. The array is filled in reverse to ensure that it
//corresponds to the data segment
for(int i = (m_nFiles - 1); i >= 0 ; i--)
{
int lenRelPath = 0;
// Get Length of Relative Path
LastOffset += sizeof(int); //<<
Thisfile.Seek(-LastOffset, CFile::end);
Thisfile.Read(&lenRelPath, sizeof(int));
CString tmp6;
tmp6.Format("lenRelPath=%d,",lenRelPath);
//AfxMessageBox(tmp6);

LastOffset += lenRelPath;
// Get Relative Path
Thisfile.Seek(-LastOffset, CFile::end);
Thisfile.Read(buffer, lenRelPath);
//LastOffset += sizeof(int);

buffer[lenRelPath]=0;
CString tmp4;
tmp4.Format("[%s]",buffer);
//AfxMessageBox(tmp4);

CString szRelativePath(buffer); //<<<<<<<<<<<<<<<<<<<<<<<<<<<

int nSize = 0;
int nCompSize = 0;
int nOffset = 0;
int len = 0;
LastOffset += sizeof(int);

// Get Length of Pathname
Thisfile.Seek(-LastOffset, CFile::end);
Thisfile.Read(&len, sizeof(int));
LastOffset += len;

// Get Path Name
Thisfile.Seek(-LastOffset, CFile::end);
Thisfile.Read(buffer, len);
LastOffset += sizeof(int);

buffer[len]=0;
CString tmp3;
tmp3.Format("[%s]",buffer);
// AfxMessageBox(tmp3);

// Get Compressed File Size
Thisfile.Seek(-LastOffset, CFile::end);
Thisfile.Read(&nCompSize, sizeof(int));
LastOffset += sizeof(int);

// Get File Size
Thisfile.Seek(-LastOffset, CFile::end);
Thisfile.Read(&nSize, sizeof(int));
LastOffset += sizeof(int);

// Get File Offset
Thisfile.Seek(-LastOffset, CFile::end);
Thisfile.Read(&nOffset, sizeof(int));

//Set the data in the array
m_InfoArray[i].SetSize(nSize);
m_InfoArray[i].SetCompressedSize(nCompSize);
CString Temp(buffer);
m_InfoArray[i].SetFilename(Temp.Left(len));
m_InfoArray[i].SetOffset(nOffset);

m_InfoArray[i].SetRelatviePath(szRelativePath.Left(lenRelPath));//<<<<<<<<<<<<<<<<
}

//Record the total size of the TOC for use
//when extracting the data segment
m_nTOCSize = LastOffset;
}

//Close the archive
Thisfile.Close();

return SUCCESS;
}
Questionhow to add the zlib folder Pin
Ricky Koh3-Sep-03 0:27
Ricky Koh3-Sep-03 0:27 
QuestionExecute without Extract? Pin
Rogerio Silva5-May-03 17:14
Rogerio Silva5-May-03 17:14 
AnswerRe: Execute without Extract? Pin
James Spibey6-May-03 5:37
James Spibey6-May-03 5:37 
GeneralRe: Execute without Extract? Pin
Rogerio Silva13-Aug-03 8:53
Rogerio Silva13-Aug-03 8:53 
GeneralRe: Execute without Extract? Pin
James Spibey13-Aug-03 9:38
James Spibey13-Aug-03 9:38 
GeneralRe: Execute without Extract? Pin
Rogerio Silva16-Jun-04 11:29
Rogerio Silva16-Jun-04 11:29 
GeneralRe: Execute without Extract? Pin
Anonymous23-Dec-04 3:10
Anonymous23-Dec-04 3:10 
GeneralRe: Execute without Extract? Pin
Alexandre Ravey14-May-07 23:12
Alexandre Ravey14-May-07 23:12 
GeneralExecute unpacked file... Pin
Luke_en21-Feb-03 9:04
Luke_en21-Feb-03 9:04 
GeneralNice Class James Pin
Brian Delahunty21-Aug-02 12:02
Brian Delahunty21-Aug-02 12:02 
GeneralRe: Nice Class James Pin
James Spibey21-Aug-02 21:04
James Spibey21-Aug-02 21:04 
GeneralRe: Nice Class James Pin
Brian Delahunty22-Aug-02 9:10
Brian Delahunty22-Aug-02 9:10 
GeneralRe: Interesting but... Pin
James Spibey21-Aug-02 23:22
James Spibey21-Aug-02 23:22 
Generalinternal error at another machine Pin
Anonymous20-Aug-02 19:13
Anonymous20-Aug-02 19:13 
GeneralRe: internal error at another machine Pin
James Spibey20-Aug-02 21:47
James Spibey20-Aug-02 21:47 
GeneralVery Good!!! - One bug fix. Pin
Matthew R. Miller17-Aug-02 11:52
Matthew R. Miller17-Aug-02 11:52 
GeneralCancel Problem Pin
19-Jun-02 12:59
suss19-Jun-02 12:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.