Introduction
This series of articles discusses three typical requirements when working with file resources (by that I mean, files that have been compiled into your program as resources):
Part 1 | Text and binary files |
Part 2 | Zip files |
Part 3 | Encrypted files |
Embedding a Zip File as a Resource
In Part 1, I covered some general topics about resources, and presented CResourceFile
and CResourceTextFile
, classes that allow you to read binary and text files that have been embedded as resources in your app. In this article, I will talk about CResourceZip
, a class that allows you to read, search, and unzip entries in a zip file.
For this article's demo program, I have set up a resource to include a zip file. Here are actual lines taken from XResFileTest.rc:
IDU_ZIP BINARY DISCARDABLE "test_files.zip"
For a detailed description of this Resource Compiler directive, please see Part 1.
The contents of zip file are shown below:
All of the files in the zip are text files, containing one, two, or three lines of text - 15 characters plus (sometimes) a carriage-return/line-feed. Just by looking at file sizes, it is easy to guess that test3c.txt must be Unicode.
Working with Zip Files
CResourceZip
is derived from CResourceFile
(see Part 1) and uses the XUnzip code from my article XZip and XUnzip.
The demo app allows you to list contents of embedded zip file:
The following code from demo app shows how this is done:
void CXResFileTestDlg::OnListEntries()
{
m_List.Printf(CXListBox::Blue, CXListBox::White, 0, _T(""));
m_List.Printf(CXListBox::Blue, CXListBox::White, 0,
_T("=== Listing zip entries ==="));
BOOL rc = FALSE;
CResourceZip rz;
rc = rz.Open(NULL, _T("IDU_ZIP"));
if (rc)
{
m_List.Printf(CXListBox::Green, CXListBox::White, 0,
_T("\tZip resource opened OK"));
int nCount = rz.GetCount();
if (nCount == ZIP_ENTRIES)
{
m_List.Printf(CXListBox::Green, CXListBox::White, 0,
_T("\tZip resource contains %d entries"), nCount);
CResourceZip::ZipEntryData zed = { 0 };
CString strType = _T("");
for (int i = 0; i < nCount; i++)
{
rz.GetEntry(i, &zed);
if (rz.IsDirectory(zed))
strType = _T("Dir");
else
strType = _T("File");
m_List.Printf(CXListBox::Black, CXListBox::White, 0,
_T("\t\t%4d:\t%s\t%s"), i, strType, zed.name);
}
}
else
{
m_List.Printf(CXListBox::Red, CXListBox::White, 0,
_T("\tZip resource contains %d entries, incorrect count"),
nCount);
}
rz.Close();
m_List.Printf(CXListBox::Green, CXListBox::White, 0,
_T("\tZip resource closed"));
}
else
{
m_List.Printf(CXListBox::Red, CXListBox::White, 0,
_T("Failed to open zip resource IDU_ZIP"));
}
}
In above code, the key lines are highlighted. First CResourceZip::Open()
is used to open zip resource, then CResourceZip::GetCount()
is called to get the number of zip entries, and finally CResourceZip::GetEntry()
is called to get zip entry and display the entry name.
CResourceZip
provides file name search capability via the functions CResourceZip::FindFirst()
and CResourceZip::FindNext()
. In the following screenshot, the prefix string "test files/temp1/test2/temp3/" is searched for:
By searching for directory name, you can find all entries in that directory. Here is code in demo app that implements search:
void CXResFileTestDlg::OnFindEntries()
{
BOOL rc = FALSE;
CResourceZip rz;
UpdateData(TRUE);
CString strSearchType = _T("");
switch ((CResourceZip::SearchType) m_nSearchType)
{
default:
case CResourceZip::prefix:
strSearchType = _T("prefix");
break;
case CResourceZip::suffix:
strSearchType = _T("suffix");
break;
case CResourceZip::any:
strSearchType = _T("anywhere");
break;
}
if (m_strFind.IsEmpty())
{
AfxMessageBox(_T("Please enter a file or directory to find."));
return;
}
m_List.Printf(CXListBox::Blue, CXListBox::White, 0, _T(""));
m_List.Printf(CXListBox::Blue, CXListBox::White, 0,
_T("=== Finding zip entries ==="));
m_List.Printf(CXListBox::Black, CXListBox::White, 0,
_T("\tLooking for entries matching '%s' (%s)"), m_strFind,
strSearchType);
rc = rz.Open(NULL, _T("IDU_ZIP"));
if (rc)
{
m_List.Printf(CXListBox::Green, CXListBox::White, 0,
_T("\tZip resource opened OK"));
int nCount = rz.GetCount();
if (nCount == ZIP_ENTRIES)
{
CResourceZip::ZipEntryData zed = { 0 };
rc = rz.FindFirst(m_strFind, (CResourceZip::SearchType)
m_nSearchType, &zed);
int nFound = 0;
while (rc)
{
m_List.Printf(CXListBox::Black, CXListBox::White, 0,
_T("\t\t%4d:\t%s"), zed.index, zed.name);
nFound++;
rc = rz.FindNext(&zed);
}
if (nFound > 0)
{
m_List.Printf(CXListBox::Green, CXListBox::White, 0,
_T("\tFound %d entries matching '%s' (%s)"),
nFound, m_strFind, strSearchType);
}
else
{
CString strFmt = _T("\t\tCannot locate '%s' as %s");
if ((CResourceZip::SearchType) m_nSearchType ==
CResourceZip::any)
strFmt = _T("\t\tCannot locate '%s' %s");
m_List.Printf(CXListBox::Red, CXListBox::White, 0, strFmt,
m_strFind, strSearchType);
}
}
else
{
m_List.Printf(CXListBox::Red, CXListBox::White, 0,
_T("\tZip resource contains %d entries, incorrect count"),
nCount);
}
rz.Close();
m_List.Printf(CXListBox::Green, CXListBox::White, 0,
_T("\tZip resource closed"));
}
else
{
m_List.Printf(CXListBox::Red, CXListBox::White, 0,
_T("Failed to open zip resource IDU_ZIP"));
}
}
The above code relies on CResourceZip::FindFirst()
to initialize the search:
BOOL CResourceZip::FindFirst(LPCTSTR lpszFile,
SearchType eSearch,
ZipEntryData *zed)
The SearchType
enum is defined as:
enum SearchType
{
prefix = 0, suffix, any };
This allows you to specify partial file matches, by searching only at the beginning of file name, only at the end, or anywhere in the name. Note that when I use the term "file name", I mean what is stored in ZipEntryData::name
member, which is usually a relative path name.
This example shows searching for a suffix, which is handy when you are looking for files:
The final search example shows an "anywhere" search, for file names containing "3":
The third option in the demo app is to display the contents of a zip entry. After selecting zip entry, press View Contents and you will see:
Here is code in demo app that implements this:
void CXResFileTestDlg::OnViewContents()
{
BOOL rc = FALSE;
CResourceZip rz;
UpdateData(TRUE);
m_List.Printf(CXListBox::Blue, CXListBox::White, 0, _T(""));
m_List.Printf(CXListBox::Blue, CXListBox::White, 0,
_T("=== Displaying zip entry %d ==="), m_nViewIndex);
rc = rz.Open(NULL, _T("IDU_ZIP"));
if (rc)
{
m_List.Printf(CXListBox::Green, CXListBox::White, 0,
_T("\tZip resource opened OK"));
int nCount = rz.GetCount();
if (nCount == ZIP_ENTRIES)
{
if (m_nViewIndex >= nCount)
{
CString str = _T("");
str.Format(_T("Please enter a number between 0 and %d"),
nCount-1);
AfxMessageBox(str);
return;
}
CResourceZip::ZipEntryData zed = { 0 };
if (rz.GetEntry(m_nViewIndex, &zed))
{
if (!rz.IsDirectory(zed))
{
BYTE * buf = rz.UnZip(m_nViewIndex);
if (buf)
{
m_List.Printf(CXListBox::Green, CXListBox::White, 0,
_T("\tOpened entry %d: '%s'"), m_nViewIndex,
zed.name);
CResourceTextFile rf;
CResourceTextFile::ConvertAction eConvert =
CResourceTextFile::NoConvertAction;
#ifdef _UNICODE
eConvert = CResourceTextFile::ConvertToUnicode;
#endif
if (m_nViewIndex == 6) #ifdef _UNICODE
eConvert = CResourceTextFile::NoConvertAction;
#else
eConvert = CResourceTextFile::ConvertToAnsi;
#endif
rf.SetTextBuffer((TCHAR *)buf, zed.unc_size/sizeof(
TCHAR), eConvert);
int nLine = 0;
CString strLine = _T("");
while (!rf.IsAtEOF())
{
nLine++;
strLine.Format(_T("This is line %d."), nLine);
TCHAR s[100] = { _T('\0') };
int nLen = rf.ReadLine(s, sizeof(s)/sizeof(
TCHAR)-1);
m_List.Printf(CXListBox::Black, CXListBox::White,
0, _T("\t\t%d: length=%d <%s>"),
nLine, nLen, s);
}
free(buf);
}
else
{
m_List.Printf(CXListBox::Red, CXListBox::White, 0,
_T("\tUnZip() failed for %d"), m_nViewIndex);
}
}
else
{
m_List.Printf(CXListBox::Red, CXListBox::White, 0,
_T("\t\tEntry %d is a directory"), m_nViewIndex);
}
}
else
{
CString str = _T("");
str.Format(_T("Entry %d is a directory.\r\nPlease" +
"select a file."), m_nViewIndex);
AfxMessageBox(str);
return;
}
}
else
{
m_List.Printf(CXListBox::Red, CXListBox::White, 0,
_T("\tZip resource contains %d entries, incorrect count"),
nCount);
}
rz.Close();
m_List.Printf(CXListBox::Green, CXListBox::White, 0,
_T("\tZip resource closed"));
}
else
{
m_List.Printf(CXListBox::Red, CXListBox::White, 0,
_T("Failed to open zip resource IDU_ZIP"));
}
}
Note that CResourceTextFile
is used to read lines from a zip entry. By passing the buffer pointer returned by CResourceZip::UnZip()
to CResourceTextFile::SetTextBuffer()
, the zip resource file can be opened, an entry unzipped, and the contents of that entry retrieved, all without having to write anything to disk.
Summary: Reading Zip Files
The code presented above can be boiled down to:
CResourceZip rz;
if (rz.Open(NULL, _T("IDU_ZIP"))) {
BYTE * p = rz.UnZip(index); if (p)
{
--- do something ---
}
.
.
.
}
CResourceZip Quick Reference
Here is complete list of functions available in CResourceZip
:
How to Add CResourceZip to Your Project
To integrate CResourceZip
class into your app, do the following:
- You first need to add following files to your project:
- ResourceFile.cpp
- ResourceFile.h
- ResourceZip.cpp
- ResourceZip.h
- XString.cpp
- XString.h
- XUnzip.cpp
- XUnzip.h
- In Visual Studio settings, select Not using pre-compiled header for ResourceFile.cpp, ResourceZip.cpp, XString.cpp, and XUnzip.cpp.
- Next, include header file ResourceZip.h in source file where you want to use
CResourceZip
. - Now you are ready to start using
CResourceZip
. See above for sample code.
Implementation Notes
CResourceZip
has been implemented using C++, without any MFC or STL. It has been compiled under both VS6 and VS2005, and has been tested on XP and Vista. It should also work on Win2000 and Win98, but has not been tested on those platforms.
Summary
I have presented a class that allows you to unzip an entry in a zip resource file, and access the contents of that entry via memory buffer. Using the CResourceTextFile
class I presented in Part 1, you read text file entries line by line, just like you can with disk files.
Embedding zip files as resources provides a significant improvement in terms of protecting your resources from being ripped, since there is no obvious indication that the resource is actually a zip file. In the next article, I will discuss possibility of encrypting resources.
Revision History
Version 1.0 — 2007 July 8
Usage
This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.