This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

| De/Crunching Class Submitted by

When writing code, sometimes I want to release a demo to public appreciation, but I don't want to expose some files (bitmaps, meshes, sounds, etc). I want to release a single executable file containing all required information, packed in a simple and fast way. The presented code do this, without much pain.
To me, it's specially useful when the app is to read a custom packed file from disk (a game level, for example). So the packed level can just be "crunched" into the executable and read directly from there at run-time. Simple and clean. Decruncher is a simple way of doing this trick. The idea is simple: I feed the cruncher with a list of files and an executable name. The cruncher put a signature, a table of contents (without directory information) and all the files in the list at the end of the specified executable. Inside the app, I just ask to decrunch the table of contents, and start reading from there, using the offsets provided by the decruncher. Decruncher will NOT extract the files from the archive, it'll just tell where the files are stored in the executable and their sizes. NOTE: this is a one-way process, so to crunch again I need to regenerate the original executable (or copy over), as the cruncher just appends to the end of the file. Also, there is NO compression involved, I read back the file exactly as I do without the crunching. I know, there are other more "educated" ways of doing this, but old demo coders usually did it exactly this way, so here it is. For a quick demo release it works fine. Please read the sample included. The sample is a simple console utility that crunchs a list of files to the end of any other file then read back their offsets. Regards, Vander Nunes

Currently browsing [crunch.zip] (33,088 bytes) - [crunch.cpp] - (1,981 bytes)

    //----------------------------------------------------------------------
    // De/Cruncher 1.0
    // Copyleft 1999, Vander Nunes
    //
    // This is free software, provided AS IS - use it at your own risk.
    // If you use this code, a mention is appreciated but not required.
    //
    // This code is released as a technique sample, not meant to be an
    // example of elegancy or portability.
    //
    // Module: crunch.cpp (sample application)
    //----------------------------------------------------------------------

    #include <stdlib.h>
    #include <stdio.h>
    #include "decrunch.h"

    //
    // A simple example of use. This will read a list of files
    // from a text file and crunch all files to the end of the
    // specified target file. Then the list will be read again
    // and listed.
    //
    int main(int argc, char *argv[], char *envp[])
    {
      printf("\n----------------------------------------------\n");
      printf("CRUNCH 1.0\n(C)1999, Vander Nunes");
      printf("\n----------------------------------------------\n");

      if (argc != 3)
      {
        printf("sintax: crunch <filelist.txt> <targetfile>\n");
        printf("sample: crunch bmps.txt demo.exe\n");
        exit(0);
      }

      printf("crunching %s into %s...",argv[1],argv[2]);
      CCrunch Cruncher;

      if (!Cruncher.Crunch(argv[1],argv[2]))
        printf("FAILED!\n");
      else
      {
        printf("--------------------------------------------\n");
        printf("Now testing\n");

        DWORD Offset;

        if (Cruncher.Decrunch(argv[2],&Offset))
        {
          printf("The list was found at offset %u.\n",Offset);
          printf("There are %u files crunched.\n",Cruncher.EntryCount());
          for (WORD n=0;n<Cruncher.EntryCount();n++)
          {
            printf("File  :%s - ",Cruncher.Entry(n)->cName);
            printf("Offset:%u - ",Cruncher.Entry(n)->dwOffset);
            printf("Size  :%u\n",Cruncher.Entry(n)->dwSize);
          }
        }
        else
        {
          printf("Something bad happened while decrunching...\n");
        }
      }

      return 0;
    }

Currently browsing [crunch.zip] (33,088 bytes) - [decrunch.cpp] - (7,412 bytes)

    //----------------------------------------------------------------------
    // De/Cruncher 1.0
    // Copyleft 1999, Vander Nunes
    //
    // This is free software, provided AS IS - use it at your own risk.
    // If you use this code, a mention is appreciated but not required.
    //
    // This code is released as a technique sample, not meant to be an
    // example of elegancy or portability.
    //
    // Module: decrunch.cpp
    //----------------------------------------------------------------------

    #include "stdio.h"
    #include "decrunch.h"

    CCrunch::CCrunch()
    {
      m_pEntries = NULL;
      m_wEntryCount=0;
    }

    CCrunch::~CCrunch()
    {
      Reset();
    }

    //-------------------------------------------
    // crunch a bunch of files listed in a txt file (ListFile)
    // to OutFile
    //-------------------------------------------
    BOOL CCrunch::Crunch(char *ListFile,char *OutFile)
    {
      WORD n;
      char Name[33];
      FILE *fin, *fout;

      //--------------------------------
      // read the list of filenames
      //--------------------------------

      fin = fopen(ListFile, "rb");
      if (fin==NULL) return FALSE;

      m_wEntryCount = 0;
      while(fscanf(fin,"%s",Name)!=EOF)
      {
        m_wEntryCount++;
        if (m_pEntries==NULL)
          m_pEntries = (CEntry**)malloc(m_wEntryCount*sizeof(CEntry*));
        else
          m_pEntries = (CEntry**)realloc(m_pEntries,m_wEntryCount*sizeof(m_pEntries));

        m_pEntries[m_wEntryCount-1] = new CEntry();
        memset(&m_pEntries[m_wEntryCount-1]->cName[0],0,33*sizeof(char));
        strcpy(m_pEntries[m_wEntryCount-1]->cName,Name);
      }
      fclose(fin);

      //--------------------------------
      // find each file and get
      // their size, calculating
      // offsets
      //--------------------------------

      DWORD Offset = sizeof(WORD) + m_wEntryCount * 41;

      HANDLE h;
      WIN32_FIND_DATA Srch;

      for (n=0; n<m_wEntryCount;n++)
      {
        h = FindFirstFile(m_pEntries[n]->cName,&Srch);
        FindClose(h);

        if (h == INVALID_HANDLE_VALUE)
        {
          // file not found - just fail the entire process!
          Reset();
          return FALSE;
        }

        m_pEntries[n]->dwOffset = Offset;
        m_pEntries[n]->dwSize = Srch.nFileSizeHigh*MAXDWORD+Srch.nFileSizeLow;

        Offset += m_pEntries[n]->dwSize;
      }

      //--------------------------------
      // crunch all files to OutFile
      //--------------------------------

      fout = fopen(OutFile, "ab");

      // write cruncher signature
      char cSignature[] = CRUNCH_SIGNATURE;

      // crypt to avoid signature duplication
      // in the exe file.
      for (n=0; n<strlen(cSignature); n++)
        cSignature[n]+=1;

      // write signature
      fwrite(cSignature,strlen(cSignature),1,fout);

      // write filecount
      fwrite(&m_wEntryCount,sizeof(WORD),1,fout);

      // write list
      for (n=0; n<m_wEntryCount;n++)
      {
        fwrite(&m_pEntries[n]->cName[0],33*sizeof(char),1,fout);
        fwrite(&m_pEntries[n]->dwOffset,sizeof(DWORD),1,fout);
        fwrite(&m_pEntries[n]->dwSize,sizeof(DWORD),1,fout);
      }

      // write files
      char *pBuffer;
      DWORD dwRead,dwWrite;
      DWORD dwTotalRead,dwTotalWrite, dwTotal;

      pBuffer = (char*)malloc(BUFFER_SIZE*sizeof(char));
      if (pBuffer==NULL)
      {
        fclose(fout);
        Reset();
        return FALSE;
      }

      printf("\n");

      dwTotal = 0;
      for (n=0; n<m_wEntryCount;n++)
      {
        printf("adding: %s ... ",m_pEntries[n]->cName);

        fin = fopen(m_pEntries[n]->cName, "rb");
        if (fin)
        {
          dwTotalRead = 0;
          dwTotalWrite = 0;
          do {
            dwRead = fread(pBuffer,sizeof(char),BUFFER_SIZE,fin);
            dwWrite = fwrite(pBuffer,sizeof(char),dwRead,fout);
            dwTotalRead += dwRead;
            dwTotalWrite += dwWrite;
          } while (dwRead==BUFFER_SIZE);

          //printf("%s: read %u written %u\n",m_pEntries[n].cName,dwTotalRead,dwTotalWrite);

          dwTotal += dwTotalWrite;
          fclose(fin);

          printf("OK\n");
        }
        else
        {
          printf("ERROR!\n");
        }
      }

      printf("%u files, total: %u bytes\n",m_wEntryCount,dwTotal);
      fclose(fout);

      Reset();
      free(pBuffer);

      return TRUE;
    }

    //-------------------------------------------
    // return the offset of the list in a
    // crunched file.
    //-------------------------------------------
    BOOL CCrunch::Decrunch(char *File, DWORD *dwOffset)
    {
      char *pBuffer;
      BYTE p;
      WORD c;
      DWORD dwRead;
      FILE *fin;
      char cSignature[] = CRUNCH_SIGNATURE;

      for (c=0; c<strlen(cSignature); c++)
        cSignature[c] += 1;

      fin = fopen(File, "rb");
      if (fin==NULL) return FALSE;

      // allocate buffer
      pBuffer = (char*)malloc(BUFFER_SIZE*sizeof(char));
      if (pBuffer==NULL)
      {
        fclose(fin);
        return FALSE;
      }

      *dwOffset = 0;
      p = 0;

      do {
        dwRead = fread(pBuffer,sizeof(char),BUFFER_SIZE,fin);

        // search through buffer
        for (WORD c=0; c<BUFFER_SIZE; c++)
        {
          if (pBuffer[c] == cSignature[p])
          {
            p++;
            if (p == strlen(cSignature))
            {
              // signature found.
              *dwOffset += (c+1);
              goto readok;
            }
          } else p=0;
        }
        *dwOffset += dwRead;
      } while (dwRead==BUFFER_SIZE);

      readok:

      fclose(fin);
      free(pBuffer);

      // read the file list
      return ReadList(File,*dwOffset);
    }

    //---------------------------------------------------------------------------------------------
    // reads the crunched files list from the source crunched file.
    //
    // list format:
    //
    // WORD  FileCount;         // number of crunched files in the list
    //
    // char  FileName[33];      // name of crunched file
    // DWORD Offset;            // file offset (counting list size but not counting exe filesize)
    // DWORD Size;              // file size
    // ...
    // file
    // file
    // file
    //---------------------------------------------------------------------------------------------
    BOOL CCrunch::ReadList(char *SrcFile,DWORD ListOffset)
    {
      FILE *f;
      WORD n;

      // free any previously allocated list
      Reset();

      // try opening the file
      f = fopen(SrcFile, "rb");
      if (f == NULL) return FALSE;

      // position over the list
      if (ListOffset)
        fseek(f, ListOffset, SEEK_SET);

      // read files counting
      if (fread(&m_wEntryCount, sizeof(WORD), 1, f) != 1)
      {
        fclose(f);
        return FALSE;
      }

      // alloc memory for the entire list
      m_pEntries = (CEntry**)malloc(m_wEntryCount*sizeof(CEntry));
      if (m_pEntries==NULL)
      {
        fclose(f);
        return FALSE;
      }

      // read entries
      for (n=0;n<m_wEntryCount;n++)
      {
        m_pEntries[n] = new CEntry();
        fread(&m_pEntries[n]->cName[0],sizeof(char),33,f);
        fread(&m_pEntries[n]->dwOffset,sizeof(DWORD),1,f);
        fread(&m_pEntries[n]->dwSize,sizeof(DWORD),1,f);
      }

      fclose(f);
      return TRUE;
    }

    //-------------------------------------------
    // return a pointer to a listed file entry
    //-------------------------------------------
    CEntry* CCrunch::FindEntry(char *File)
    {
      WORD n;
      for (n=0;n<m_wEntryCount;n++)
        if (!_stricmp(m_pEntries[n]->cName,File))
          return m_pEntries[n];

      return NULL;
    }

    //-------------------------------------------
    // Free entries
    //-------------------------------------------
    void CCrunch::Reset(void)
    {
      if (m_pEntries)
      {
        for (WORD n=0;n<m_wEntryCount;n++)
          delete m_pEntries[n];

        free(m_pEntries);
        m_pEntries = NULL;
      }
      m_wEntryCount = 0;
    }

Currently browsing [crunch.zip] (33,088 bytes) - [decrunch.h] - (1,999 bytes)

    //----------------------------------------------------------------------
    // De/Cruncher 1.0
    // Copyleft 1999, Vander Nunes
    //
    // This is free software, provided AS IS - use it at your own risk.
    // If you use this code, a mention is appreciated but not required.
    //
    // This code is released as a technique sample, not meant to be an
    // example of elegancy or portability.
    //
    // Module: decrunch.h
    //----------------------------------------------------------------------

    #ifndef _DECRUNCH_H_
      #define _DECRUNCH_H_

      // sorry pals
      #include <windows.h>

      // buffered file io
      #define BUFFER_SIZE 16384

      // signature is "=VRNDCRNCH=" (ASCII -1 for each letter).
      // this was done to avoid duplication in exe file.
      #define CRUNCH_SIGNATURE "<UQMCBQMBG<"

      class CEntry
      {
        public:

          CEntry()
          {
            memset(cName,0,33);
            dwOffset = 0;
            dwSize = 0;
          }

          char  cName[33];    // name of crunched file
          DWORD dwOffset;     // file offset (counting list size but not counting exe filesize)
          DWORD dwSize;       // file size
      };

      class CCrunch
      {
        public:

          CEntry **m_pEntries;
          WORD m_wEntryCount;

          CCrunch();
          virtual ~CCrunch();

          // used when crunching files to the executable
          BOOL Crunch(char *ListFile, char *OutFile);

          // initialize decruncher with a crunched file
          // (this will NOT unpack, just read the file table)
          BOOL Decrunch(char *File, DWORD *dwOffset);

          // get the file count in the crunch
          WORD EntryCount() { return m_wEntryCount; };

          // get an entry from the list (name, offset and size)
          CEntry* Entry(WORD wIndex) { if (wIndex >= m_wEntryCount) return NULL; else return m_pEntries[wIndex]; };

          // clear entries
          void Reset(void);

        private:

          BOOL ReadList(char *SrcFile, DWORD ListOffset);
          CEntry* FindEntry(char *File);
      };

    #endif

The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
Please read our Terms, Conditions, and Privacy information.