Click here to Skip to main content
Click here to Skip to main content

XResFile - Files Stored in Resources: Part 2 - Zip Files

, 19 Jul 2007
Rate this:
Please Sign up or sign in to vote.
This series of articles is a step-by-step guide to reading files stored in your program's resources. Along the way I will present some non-MFC classes to assist you in reading text, binary, zip, and even encrypted files that have been compiled into your program as resources.
Prize winner in Competition "Best C++/MFC article of Jun 2007"

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:

/////////////////////////////////////////////////////////////
//
// BINARY
//
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:

screenshot

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:

screenshot

The following code from demo app shows how this is done:

/////////////////////////////////////////////////////////////////////////////
// OnListEntries
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:

screenshot

By searching for directory name, you can find all entries in that directory. Here is code in demo app that implements search:

/////////////////////////////////////////////////////////////////////////////
// OnFindEntries
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:

/////////////////////////////////////////////////////////////////////////////
//
// FindFirst()
//
// Purpose:     Finds first zip entry matching lpszFile, initializes for
//              subsequent calls to FindNext().  No wildcards are supported.
//
// Parameters:  lpszFile - pointer to full or partial file name
//              eSearch  - specifies how to search:
//                           prefix - lpszFile should be matched with start
//                                    of name stored in zip
//                           suffix - should be matched with end of
//                                    name stored in zip
//                           any    - should be matched anywhere in the
//                                    named stored in zip
//                         All matches are case-insensitive.
//              zed      - pointer to data struct that receives info
//
// Returns:     BOOL     - returns TRUE if matching zip entry found
//
BOOL CResourceZip::FindFirst(LPCTSTR lpszFile, 
                             SearchType eSearch, 
                             ZipEntryData *zed)

The SearchType enum is defined as:

    enum SearchType
    {
        prefix = 0,                    // match prefix only
        suffix,                        // match suffix only
        any                            // match anywhere
    };

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:

screenshot

The final search example shows an "anywhere" search, for file names containing "3":

screenshot

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:

screenshot

Here is code in demo app that implements this:

////////////////////////////////////////////////////////////////////////////
// OnViewContents
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);

                        // we use CResourceTextFile to read lines from the 
                        // text file
                        CResourceTextFile rf;
                        CResourceTextFile::ConvertAction eConvert = 
                            CResourceTextFile::NoConvertAction;
    #ifdef _UNICODE
                        eConvert = CResourceTextFile::ConvertToUnicode;
    #endif
                        if (m_nViewIndex == 6)        // entry 6 is a Unicode
                                                      // file
    #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")))   // open zip resource
    {
        BYTE * p = rz.UnZip(index);     // unzip entry
        if (p)
        {
            --- do something ---
        }
        .
        .
        .
        // rz goes out of scope and resource file is closed
    }

CResourceZip Quick Reference

Here is complete list of functions available in CResourceZip:

//   Close()             Close a file resource
//   DetachByteBuffer()  Return pointer to byte buffer and set flag so buffer
//                       will not be deleted when resource is closed
//   FindFirst()         Find first file that matches
//   FindNext()          Find next file that matches
//   GetByteBuffer()     Get pointer to zip resource buffer
//   GetCount()          Get number of entries in zip
//   GetEntry()          Get info for zip entry
//   GetLength()         Get length of zip resource
//   IsOpen()            Check if zip resource is open
//   Open()              Open a zip resource
//   SetByteBuffer()     Set a new buffer for the zip file resource
//   UnZip()             Unzip an entry

How to Add CResourceZip to Your Project

To integrate CResourceZip class into your app, do the following:

  1. 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
  2. In Visual Studio settings, select Not using pre-compiled header for ResourceFile.cpp, ResourceZip.cpp, XString.cpp, and XUnzip.cpp.
  3. Next, include header file ResourceZip.h in source file where you want to use CResourceZip.
  4. 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

  • Initial public release

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.

License

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

About the Author

Hans Dietrich
Software Developer (Senior) Hans Dietrich Software
United States United States
I attended St. Michael's College of the University of Toronto, with the intention of becoming a priest. A friend in the University's Computer Science Department got me interested in programming, and I have been hooked ever since.
 
Recently, I have moved to Los Angeles where I am doing consulting and development work.
 
For consulting and custom software development, please see www.hdsoft.org.






Comments and Discussions

 
Generallink is missing - for content table entries (part1, part3) Pinmemberkrzysk19-Feb-08 12:43 
GeneralRe: link is missing - for content table entries (part1, part3) PinmvpHans Dietrich19-Feb-08 12:57 
QuestionAny plans to build a self-extractor out of this, anyone? PinmemberPaul Sanders (AlpineSoft)20-Oct-07 6:46 
GeneralVery very easy 5 PinmemberDaTxomin8-Jul-07 1:18 
General.NET PinsitebuilderUwe Keim7-Jul-07 22:30 
GeneralRe: .NET Pinmemberirrdev7-Jul-07 22:41 
GeneralRe: .NET PinmemberAnna-Jayne Metcalfe8-Jul-07 22:32 
GeneralRe: .NET Pinmemberandrewtruckle10-Jul-07 6:04 

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

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

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 19 Jul 2007
Article Copyright 2007 by Hans Dietrich
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid