How does one find the start of the “Central Directory” in zip files?

后端 未结 4 1829
说谎
说谎 2021-02-05 04:20

Wikipedia has an excellent description of the ZIP file format, but the \"central directory\" structure is confusing to me. Specifically this:

This orderin

4条回答
  •  自闭症患者
    2021-02-05 04:52

    Here is a solution I have just had to roll out incase anybody needs this. This involves grabbing the central directory.

    In my case I did not want any of the compression features that are offered in any of the zip solutions. I just wanted to know about the contents. The following code will return a ZipArchive of a listing of every entry in the zip.

    It also uses a minimum amount of file access and memory allocation.

    TinyZip.cpp

    #include "TinyZip.h"
    #include 
    
    namespace TinyZip
    {
    #define VALID_ZIP_SIGNATURE 0x04034b50
    #define CENTRAL_DIRECTORY_EOCD 0x06054b50 //signature
    #define CENTRAL_DIRECTORY_ENTRY_SIGNATURE 0x02014b50
    #define PTR_OFFS(type, mem, offs) *((type*)(mem + offs)) //SHOULD BE OK 
    
        typedef struct {
            unsigned int signature : 32;
            unsigned int number_of_disk : 16;
            unsigned int disk_where_cd_starts : 16;
            unsigned int number_of_cd_records : 16;
            unsigned int total_number_of_cd_records : 16;
            unsigned int size_of_cd : 32;
            unsigned int offset_of_start : 32;
            unsigned int comment_length : 16;
        } ZipEOCD;
    
        ZipArchive* ZipArchive::GetArchive(const char *filepath)
        {
            FILE *pFile = nullptr;
    #ifdef WIN32
            errno_t err;
            if ((err = fopen_s(&pFile, filepath, "rb")) == 0)
    #else
            if ((pFile = fopen(filepath, "rb")) == NULL)
    #endif
            {
                int fileSignature = 0;
                //Seek to start and read zip header
                fread(&fileSignature, sizeof(int), 1, pFile);
                if (fileSignature != VALID_ZIP_SIGNATURE) return false;
    
                //Grab the file size
                long fileSize = 0;
                long currPos = 0;
    
                fseek(pFile, 0L, SEEK_END);
                fileSize = ftell(pFile);
                fseek(pFile, 0L, SEEK_SET);
    
                //Step back the size of the ZipEOCD 
                //If it doesn't have any comments, should get an instant signature match
                currPos = fileSize;
                int signature = 0;
                while (currPos > 0)
                {
                    fseek(pFile, currPos, SEEK_SET);
                    fread(&signature, sizeof(int), 1, pFile);
                    if (signature == CENTRAL_DIRECTORY_EOCD)
                    {
                        break;
                    }
                    currPos -= sizeof(char); //step back one byte
                }
    
                if (currPos != 0)
                {
                    ZipEOCD zipOECD;
                    fseek(pFile, currPos, SEEK_SET);
                    fread(&zipOECD, sizeof(ZipEOCD), 1, pFile);
    
                    long memBlockSize = fileSize - zipOECD.offset_of_start;
    
                    //Allocate zip archive of size
                    ZipArchive *pArchive = new ZipArchive(memBlockSize);
    
                    //Read in the whole central directory (also includes the ZipEOCD...)
                    fseek(pFile, zipOECD.offset_of_start, SEEK_SET);
                    fread((void*)pArchive->m_MemBlock, memBlockSize - 10, 1, pFile);
                    long currMemBlockPos = 0;
                    long currNullTerminatorPos = -1;
                    while (currMemBlockPos < memBlockSize)
                    {
                        int sig = PTR_OFFS(int, pArchive->m_MemBlock, currMemBlockPos);
                        if (sig != CENTRAL_DIRECTORY_ENTRY_SIGNATURE)
                        {
                            if (sig == CENTRAL_DIRECTORY_EOCD) return pArchive;
                            return nullptr; //something went wrong
                        }
    
                        if (currNullTerminatorPos > 0)
                        {
                            pArchive->m_MemBlock[currNullTerminatorPos] = '\0';
                            currNullTerminatorPos = -1;
                        }
    
                        const long offsToFilenameLen = 28;
                        const long offsToFieldLen = 30;
                        const long offsetToFilename = 46;
    
                        int filenameLength = PTR_OFFS(int, pArchive->m_MemBlock, currMemBlockPos + offsToFilenameLen);
                        int extraFieldLen = PTR_OFFS(int, pArchive->m_MemBlock, currMemBlockPos + offsToFieldLen);
                        const char *pFilepath = &pArchive->m_MemBlock[currMemBlockPos + offsetToFilename];
                        currNullTerminatorPos = (currMemBlockPos + offsetToFilename) + filenameLength;
                        pArchive->m_Entries.push_back(pFilepath);
    
                        currMemBlockPos += (offsetToFilename + filenameLength + extraFieldLen);
                    }
    
                    return pArchive;
                }
            }
            return nullptr;
        }
    
        ZipArchive::ZipArchive(long size)
        {
            m_MemBlock = new char[size];
        }
    
        ZipArchive::~ZipArchive()
        {
            delete[] m_MemBlock;
        }
    
        const std::vector  &ZipArchive::GetEntries()
        {
            return m_Entries;
        }
    }
    

    TinyZip.h

    #ifndef __TinyZip__
    #define __TinyZip__
    
    #include 
    #include 
    
    namespace TinyZip
    {
        class ZipArchive
        {
        public:
            ZipArchive(long memBlockSize);
            ~ZipArchive();
    
            static ZipArchive* GetArchive(const char *filepath);
    
            const std::vector  &GetEntries();
    
        private:
            std::vector m_Entries;
            char *m_MemBlock;
        };
    
    }
    
    
    #endif
    

    Usage:

     TinyZip::ZipArchive *pArchive = TinyZip::ZipArchive::GetArchive("Scripts_unencrypt.pak");
     if (pArchive != nullptr)
     {
         const std::vector entries = pArchive->GetEntries();
         for (auto entry : entries)
         {
             //do stuff
         }
     }
    

提交回复
热议问题