[Module] ZIP - z.B. ZIP::RemoveFile()

Hier könnt Ihr gute, von Euch geschriebene Codes posten. Sie müssen auf jeden Fall funktionieren und sollten möglichst effizient, elegant und beispielhaft oder einfach nur cool sein.
Benutzeravatar
Thorsten1867
Beiträge: 1359
Registriert: 04.02.2005 15:40
Computerausstattung: [Windows 10 x64] [PB V5.7x]
Wohnort: Kaufbeuren
Kontaktdaten:

[Module] ZIP - z.B. ZIP::RemoveFile()

Beitrag von Thorsten1867 »

Habe im englischen Forum ein interessantes Modul gefunden und etwas optimiert.
(http://www.purebasic.fr/english/viewtop ... 12&t=66053)

Code: Alles auswählen

;/ === ZipModule.pbi ===  [ PureBasic V5.6x ]
;/ 
;/  by nalor (optimized Thorsten1867)
;/ http://www.purebasic.fr/english/viewtopic.php?f=12&t=66053

; ===== Commands ==================
; AddFile(sZipfile.s, sFileToAdd.s, sZippedFileName.s)
; AddFileFromMem(sZipfile.s, *SrcMem, iSrcSize.i, sZippedFileName.s, iPreFileHdl.i=-1, lDateTime.l=-1)
; ChangeFileDateTime(sZipfile.s, sFileToModify.s, lUnixTimeStamp.l, iCaseSensitiv.i=#False)
; Examine(sZipfile.s, List EntryList.ZIP_EntryData(), iPreFileHdl.i=-1)
; RemoveFile(sZipfile.s, sFileToRemove.s, iCaseSensitiv.i=#False, iFileHdl.i=-1)
; GetErrorText(iError.i)
; =================================

;- ## 0-HISTORY:
; 20160306 .. nalor .. add support for PreFileHdl to speed up processing of multiple files inside a single zip file
; 20160501 .. nalor .. add ZIP_ChangeDateTime and insert it into ZIP_AddFileFromMem
; 20160627 .. nalor .. moved into a module
; 20160628 .. nalor .. created examples-section, corrected bug with subdirectories in _ZIP_Common
; 20160629 .. nalor .. add support to set UTF8-flag in header in case the filename requires UTF8 (Windows 8.1 zip engine relies on this flag)

DeclareModule ZIP

  Enumeration
    #ZIP_OK                  = #True
    #ZIP_NOT_OK              = #False
    #ZIP_ERR_INVALID         = -1
    #ZIP_ERR_READ_FILE       = -2
    #ZIP_ERR_ALLOC_MEM       = -3
    #ZIP_ERR_MULTI_FILE      = -4
    #ZIP_ERR_DATA_DESCR      = -5
    #ZIP_ERR_ZIP64           = -6
    #ZIP_ERR_WRITE_FILE      = -7
    #ZIP_ERR_DELETE_DATA     = -8
    #ZIP_ERR_SIGNATURE       = -9
    #ZIP_ERR_ENTRY_NOT_FOUND = -10
    #ZIP_ERR_ADDELEMENT      = -11
    #ZIP_ERR_WRONG_PARAMETER = -12
    #ZIP_ERR_UNSPEC_ERROR    = -13
  EndEnumeration

  Structure ZIP_EntryData
    EntryName.s
    EntrySizeCompressed.q
    EntrySizeUncompressed.q
    EntryType.i                ; #PB_Packer_File / #PB_Packer_Directory
    EntryLastModFileDateTime.l
    EntryCRC32.l
  EndStructure

  Declare.i AddFile(sZipfile.s, sFileToAdd.s, sZippedFileName.s)
  Declare.i AddFileFromMem(sZipfile.s, *SrcMem, iSrcSize.i, sZippedFileName.s, iPreFileHdl.i=-1, lDateTime.l=-1)
  Declare.i ChangeFileDateTime(sZipfile.s, sFileToModify.s, lUnixTimeStamp.l, iCaseSensitiv.i=#False)
  Declare.i Examine(sZipfile.s, List EntryList.ZIP_EntryData(), iPreFileHdl.i=-1)
  Declare.i RemoveFile(sZipfile.s, sFileToRemove.s, iCaseSensitiv.i=#False, iFileHdl.i=-1)
  Declare.s GetErrorText(iError.i)
  
EndDeclareModule

Module ZIP
  
  UseZipPacker()
  
  EnableExplicit
  DisableDebugger

  Procedure.s GetErrorText(iError.i)
    Select iError
      Case #ZIP_OK                  : ProcedureReturn "ZIP OK"
      Case #ZIP_NOT_OK              : ProcedureReturn "ZIP NOT OK"
      Case #ZIP_ERR_INVALID         : ProcedureReturn "ZIP ERR INVALID"
      Case #ZIP_ERR_READ_FILE       : ProcedureReturn "ZIP ERR READ FILE"
      Case #ZIP_ERR_ALLOC_MEM       : ProcedureReturn "ZIP ERR ALLOC MEM"
      Case #ZIP_ERR_MULTI_FILE      : ProcedureReturn "ZIP ERR MULTI FILE"
      Case #ZIP_ERR_DATA_DESCR      : ProcedureReturn "ZIP ERR DATA DESCR"
      Case #ZIP_ERR_ZIP64           : ProcedureReturn "ZIP ERR ZIP64"
      Case #ZIP_ERR_WRITE_FILE      : ProcedureReturn "ZIP ERR WRITE FILE"
      Case #ZIP_ERR_DELETE_DATA     : ProcedureReturn "ZIP ERR DELETE DATA"
      Case #ZIP_ERR_SIGNATURE       : ProcedureReturn "ZIP ERR SIGNATURE"
      Case #ZIP_ERR_ENTRY_NOT_FOUND : ProcedureReturn "ZIP ERR ENTRY NOT FOUND"
      Case #ZIP_ERR_ADDELEMENT      : ProcedureReturn "ZIP ERR ADDELEMENT"
      Case #ZIP_ERR_WRONG_PARAMETER : ProcedureReturn "ZIP ERR WRONG PARAMETER"
      Case #ZIP_ERR_UNSPEC_ERROR    : ProcedureReturn "ZIP ERR UNSPEC ERROR"
      Default
        ProcedureReturn "ZIP UNKNOWN ERROR"
    EndSelect
  EndProcedure

  Procedure.s HexQ2(qInput.q)
    ProcedureReturn "0x"+RSet(Hex(qInput, #PB_Quad), 16, "0")
  EndProcedure

  Procedure.l DosToUnixTime(uDosDate.u, uDosTime.u)
    ProcedureReturn Date(((uDosDate>>9) & $7F)+1980, (uDosDate>>5) & $0F, uDosDate & $1F, (uDosTime>>11) & $1F, (uDosTime>>5) & $3F, (uDosTime<<1) & $3E)
  EndProcedure

  Procedure UnixToDosTime(UnixTime.l, *uDosDate.UNICODE, *uDosTime.UNICODE)
    If Year(UnixTime)<1980
      UnixTime=Date(1980, 1, 1, 0, 0, 0)
    EndIf
    *uDosDate\u=((Year(UnixTime) - 1980) << 9) | (Month(UnixTime) << 5) | (Day(UnixTime))
    *uDosTime\u=(Hour(UnixTime) << 11) | (Minute(UnixTime) << 5) | (Second(UnixTime) >> 1)
  EndProcedure

  Procedure.q FindHexInFileHdl(iFileHdl.i, sHexToFind.s, qStartPosition.q=0, iDirectionFwd.i=#True)
    Protected iSrchDataLen.i = Len(sHexToFind)/2
    Protected *SrchData
    Protected *TestData
    Protected iTmp.i
    Protected qFileSize.q
    Protected qFileDataRead.q
    Protected iChunkRead.i
    Protected qSearchResult.q=-4    ; -4 = nothing found
    Protected iChunkSize.i=10000000 ; 10 Mill. Byte ;)
   
    If Not IsFile(iFileHdl)
      ProcedureReturn -1
    EndIf
    
    If iSrchDataLen=0 Or qStartPosition<0 Or (Len(sHexToFind)%2)<>0
      ProcedureReturn -1
    EndIf
    
    qFileSize=Lof(iFileHdl)
    *SrchData=AllocateMemory(iSrchDataLen)
    *TestData=AllocateMemory(iChunkSize)
    
    If *SrchData=0 Or *TestData=0
      If *SrchData : FreeMemory(*SrchData) : EndIf
      If *TestData : FreeMemory(*TestData) : EndIf
      ProcedureReturn -2
    EndIf
    
    For iTmp=0 To iSrchDataLen-1
      PokeB(*SrchData+iTmp, Val("$"+Mid(sHexToFind,iTmp*2+1, 2)))
    Next
    
    If iDirectionFwd
      qFileDataRead=qStartPosition
      While qFileDataRead<qFileSize
        FileSeek(iFileHdl, qFileDataRead)
        iChunkRead=ReadData(iFileHdl, *TestData, iChunkSize)
        If iChunkRead>iSrchDataLen
          For iTmp=0 To iChunkRead-iSrchDataLen
            If CompareMemory(*TestData+iTmp, *SrchData, iSrchDataLen)
              qSearchResult=qFileDataRead+iTmp
              Break 2
            EndIf
          Next
          qFileDataRead+iTmp
        Else
          qFileDataRead+iChunkRead
        EndIf
      Wend
    Else
      If qStartPosition<>0
        qFileDataRead=qStartPosition
      Else
        qFileDataRead=qFileSize
      EndIf
      If qFileDataRead>=iChunkSize
        qFileDataRead-iChunkSize
      Else
        iChunkSize=qFileDataRead
        qFileDataRead=0
      EndIf
      While qFileDataRead>=0
        FileSeek(iFileHdl, qFileDataRead)
        iChunkRead=ReadData(iFileHdl, *TestData, iChunkSize)
        For iTmp=iChunkRead-iSrchDataLen To 0 Step -1
          If CompareMemory(*TestData+iTmp, *SrchData, iSrchDataLen)
            qSearchResult=qFileDataRead+iTmp
            Break 2
          EndIf
        Next
        If qFileDataRead>(iChunkSize+iSrchDataLen-1)
          qFileDataRead-iChunkSize+iSrchDataLen-1
        ElseIf qFileDataRead>0
          iChunkSize=qFileDataRead+iSrchDataLen-1
          qFileDataRead=0
        Else
          qFileDataRead=-1
        EndIf
      Wend
    EndIf
    
    FreeMemory(*SrchData)
    FreeMemory(*TestData)
    
    ProcedureReturn qSearchResult 
  EndProcedure

  #ZIP_Signature_LFH         = $04034b50 ; LocalFileHeader
  #ZIP_Signature_LFDD        = $08074b50 ; LocalFileDataDescriptor
  #ZIP_Signature_CDH         = $02014b50 ; CentralDirectoryHeader
  #ZIP_Signature_EOCDR       = $06054b50 ; EndOfCentralDirectoryRecord
  #ZIP_Signature_AEDD        = $08064b50 ; ArchiveExtraDataRecord
  #ZIP_Signature_DS          = $05054b50 ; DigitalSignature
  #ZIP_Signature_ZIP64_EOCDR = $06064b50 ; Zip64 EndOfCentralDirectoryRecord
  #ZIP_Signature_ZIP64_EOCDL = $07064b50 ; Zip64 EndOfCentralDirectoryLocator

  Structure ZIP_LocalFileHeaderV2
    Signature.l ; (0x 04 03 4b 50)
    VersionNeededToExtract.u ; 2
    GeneralPurpose.u ;
    CompressionMethod.u
    LastModFileTime.u
    LastModFileDate.u
    CRC32.l
    CompressedSize.l ; ohne
    UncompressedSize.l
    FilenameLength.u
    ExtraFieldLength.u
    ; filename (variable length)
    ; extra field (variable length)
  EndStructure

  Structure ZIP_DataDescriptor   ; exist after the data-block if bit3 of the GeneralPurpose is set - eventually preceeded by a signature 0x08074b50
    CRC32.l
    CompressedSize.l
    UncompressedSize.l
  EndStructure

  Structure ZIP64_DataDescriptor ; exist after the data-block if bit3 of the GeneralPurpose is set (APPNOTE 6.3.4 - section 4.3.9.1)
    CRC32.l
    CompressedSize.q
    UncompressedSize.q
  EndStructure

  Structure ZIP_CentralDirectoryHeaderV2
    Signature.l               ; (0x 02 01 4b 50)
    VersionMadeBy.u
    VersionNeededToExtract.u
    GeneralPurpose.u
    CompressionMethod.u
    LastModFileTime.u
    LastModFileDate.u
    CRC32.l
    CompressedSize.l          ; ohne
    UncompressedSize.l
    FilenameLength.u
    ExtraFieldLength.u
    FileCommentLength.u
    DiskNumberStart.u
    InternalFileAttributes.u
    ExternalFileAttributes.l
    OffsetOfLocalHeader.l     ; Beginn des zugehörigen Datenblocks gemessen vom Fileanfang
  EndStructure

  Structure ZIP_EndOfCentralDirectoryRecordV2
    Signature.l ; (0x 06 05 4b 50)
    NumberOfDisk.u
    NumberCDStart.u
    NumberOfEntries.u
    NumberOfTotalEntries.u
    CentralDirSize.l
    CentralDirStartOffset.l
    CommentLength.u
  EndStructure

  Structure ZIP_MoveEntryData
    CDH_Offset.q
    CDH_Size.q
    LFH_Offset.q
    LFH_Size.q
  EndStructure

  Procedure seems_utf8(*StrMem, iLen.i) 
    Protected iCnt.i
    Protected aCharVal.a
    Protected iNext.i
    Protected iCnt2.i
    Protected iUtf8Detected.i=#False
    
    For iCnt=0 To iLen-1
      aCharVal = PeekA(*StrMem+iCnt)
      If (aCharVal < $80) : iNext = 0 ; # 0bbbbbbb
      ElseIf ((aCharVal & $E0) = $C0) : iNext=1 ; # 110bbbbb
      ElseIf ((aCharVal & $F0) = $E0) : iNext=2 ; # 1110bbbb
      ElseIf ((aCharVal & $F8) = $F0) : iNext=3 ; # 11110bbb
      ElseIf ((aCharVal & $FC) = $F8) : iNext=4 ; # 111110bb
      ElseIf ((aCharVal & $FE) = $FC) : iNext=5 ; # 1111110b
      Else
        ProcedureReturn #False                  ; # Does not match any model
      EndIf
      If iNext>0
        iUtf8Detected=#True
        For iCnt2=1 To iNext ;# n bytes matching 10bbbbbb follow ?
          If iCnt+iCnt2>iLen Or (PeekA(*StrMem+iCnt+iCnt2) & $C0)<>$80
            ProcedureReturn #False
          EndIf
        Next
        iCnt+iCnt2-1
      EndIf 
    Next
    
    ProcedureReturn iUtf8Detected
  EndProcedure

  Procedure StringUtf8Required(sText.s)
    If StringByteLength(sText, #PB_UTF8)>Len(sText) ; in case each character can be stored as ascii - the bytelen in utf8 would be identical to the characterlen, if this isn't >> UTF8 necessary
      ProcedureReturn #True
    EndIf
    ProcedureReturn #False
  EndProcedure

  Procedure.i _ZIP_DropDataFromFileHdl(iFileHdl.i, qOffset.q, qDataSize.q)
    Protected qChunkSize.q=10000000 ; 10MB
    Protected *ChunkMem=0
    Protected iResult.i=#ZIP_OK
    Protected qProcessedData.q
    Protected qTmp.q
    Protected qReadOffset.q
    Protected qWriteOffset.q
    
    Repeat ; single repeat
      If Not IsFile(iFileHdl)
        iResult=#ZIP_ERR_INVALID
        Break
      EndIf
      qReadOffset=qOffset+qDataSize
      If qReadOffset>Lof(iFileHdl)
        iResult=#ZIP_ERR_INVALID
        Break
      EndIf
      *ChunkMem=AllocateMemory(qChunkSize)
      If Not *ChunkMem
        iResult=#ZIP_ERR_ALLOC_MEM
        Break
      EndIf
      qWriteOffset=qOffset
      qProcessedData=0
      Repeat
        FileSeek(iFileHdl, qReadOffset+qProcessedData)
        qTmp=ReadData(iFileHdl, *ChunkMem, qChunkSize)
        If Not qTmp
          iResult=#ZIP_ERR_READ_FILE
          Break 2
        EndIf
        FileSeek(iFileHdl, qWriteOffset+qProcessedData)
        If Not WriteData(iFileHdl, *ChunkMem, qTmp)
          iResult=#ZIP_ERR_WRITE_FILE
          Break 2
        EndIf
        qProcessedData+qTmp
      Until qReadOffset+qProcessedData>=Lof(iFileHdl) ; until the end of the file is reached
      FileSeek(iFileHdl, qWriteOffset+qProcessedData)
      TruncateFile(iFileHdl)
    Until #True
    
    If *ChunkMem : FreeMemory(*ChunkMem) : EndIf
    
    ProcedureReturn iResult
  EndProcedure

  Procedure.i _ZIP_InsertGapIntoFileHdl(iFileHdl.i, qOffset.q, qSize.q, qChunkSize.q=0, *ChunkMem=0)
    Protected iResult.i=#ZIP_OK
    Protected qTmp.q
    Protected aMemAlloc.a=#False
    Protected qReadOffset.q
    Protected qWriteOffset.q
    Protected qReadSize.q
   
    Repeat
      If Not IsFile(iFileHdl)
        iResult=#ZIP_ERR_INVALID
        Break
      EndIf
      If qChunkSize=0 ; only allocate memory in case it's not preallocated
        qChunkSize=10000000 ; 10MB
        *ChunkMem=AllocateMemory(qChunkSize)
        If Not *ChunkMem
          iResult=#ZIP_ERR_ALLOC_MEM
          Break
        Else
          aMemAlloc=#True
        EndIf
      EndIf
      qReadOffset=Lof(iFileHdl)
      qReadSize=qChunkSize
      FileSeek(iFileHdl, 0)
      FileSeek(iFileHdl, qReadOffset+qSize-1, #PB_Relative)
      WriteByte(iFileHdl, $00)
      Repeat
        qReadOffset-qReadSize
        If qReadOffset<qOffset
          qReadSize-(qOffset-qReadOffset)
          qReadOffset=qOffset
        EndIf
        FileSeek(iFileHdl, qReadOffset)
        qTmp=ReadData(iFileHdl, *ChunkMem, qReadSize)
        If Not qTmp
          iResult=#ZIP_ERR_READ_FILE
          Break 2
        EndIf
        FileSeek(iFileHdl, qReadOffset+qSize)
        If Not WriteData(iFileHdl, *ChunkMem, qTmp)
          iResult=#ZIP_ERR_WRITE_FILE
          Break 2
        EndIf
      Until qReadOffset=qOffset
    Until #True
    
    If aMemAlloc : FreeMemory(*ChunkMem) : EndIf
    
    ProcedureReturn iResult
  EndProcedure

  Procedure.i _ZIP_CopyDataFromFile2FileHdl(sSrcFile.s, qSrcOffset.q, qSrcSize.q, iDstFileHdl.i, qDstOffset.q)
    Protected qChunkSize.q=10000000 ; 10MB
    Protected *ChunkMem=0
    Protected iResult.i=#ZIP_OK
    Protected qTmp.q
    Protected qReadOffset.q
    Protected qWriteOffset.q
    Protected qReadSize.q
    Protected iSrcFileHdl.i
    
    Repeat ; single repeat
      If qSrcSize<=0
        iResult=#ZIP_ERR_WRONG_PARAMETER
        Break
      EndIf
      If Not IsFile(iDstFileHdl)
        iResult=#ZIP_ERR_INVALID
        Break
      EndIf
      iSrcFileHdl=ReadFile(#PB_Any, sSrcFile)
      If Not iSrcFileHdl
        iResult=#ZIP_ERR_READ_FILE
        Break
      EndIf
      *ChunkMem=AllocateMemory(qChunkSize)
      If Not *ChunkMem
        iResult=#ZIP_ERR_ALLOC_MEM
        Break
      EndIf
      If qDstOffset<Lof(iDstFileHdl) ; only in case the destination is inside the current file - otherwise there's no data that needs to be moved to the back
        iResult=_ZIP_InsertGapIntoFileHdl(iDstFileHdl, qDstOffset, qSrcSize, qChunkSize, *ChunkMem)
        If iResult<>#ZIP_OK : Break : EndIf
      Else
        FileSeek(iDstFileHdl, 0)
        FileSeek(iDstFileHdl, qDstOffset-1, #PB_Relative)
        WriteByte(iDstFileHdl, $00)
      EndIf
      qReadOffset=qSrcOffset
      qWriteOffset=qDstOffset
      qReadSize=qChunkSize
      Repeat
        If qReadOffset+qReadSize>qSrcOffset+qSrcSize
          qReadSize=qSrcOffset+qSrcSize-qReadOffset
        EndIf
        FileSeek(iSrcFileHdl, qReadOffset)
        qTmp=ReadData(iSrcFileHdl, *ChunkMem, qReadSize)
        If Not qTmp
          iResult=#ZIP_ERR_READ_FILE
          Break 2
        EndIf
        FileSeek(iDstFileHdl, qWriteOffset)
        If Not WriteData(iDstFileHdl, *ChunkMem, qTmp)
          iResult=#ZIP_ERR_WRITE_FILE
          Break 2
        EndIf
        qReadOffset+qReadSize
        qWriteOffset+qReadSize
      Until qReadOffset+qReadSize>=qSrcOffset+qSrcSize
    Until #True
    
    If *ChunkMem : FreeMemory(*ChunkMem) : EndIf
    
    If IsFile(iSrcFileHdl)
      CloseFile(iSrcFileHdl)
    EndIf
    
    ProcedureReturn iResult
  EndProcedure

  Procedure.s _ZIP_GetFilename(iFileHdl.i, qOffset.q, iFilenameLen.i, iGeneralPurpose.i, *Error.Integer)
    Protected *FilenameMem
    Protected sFilename.s=""

    *FilenameMem=AllocateMemory(iFilenameLen+2)
    If *FilenameMem
      FileSeek(iFileHdl, qOffset)
      If Not ReadData(iFileHdl, *FilenameMem, iFilenameLen)
        *Error\i=#ZIP_ERR_READ_FILE
      Else
        If (iGeneralPurpose & $800) Or seems_utf8(*FilenameMem, iFilenameLen)
          sFilename=PeekS(*FilenameMem, -1, #PB_UTF8)
        Else
          sFilename=PeekS(*FilenameMem, -1, #PB_Ascii)
        EndIf
      EndIf
      FreeMemory(*FilenameMem)
    Else
      *Error\i=#ZIP_ERR_ALLOC_MEM
    EndIf
   
    ProcedureReturn sFilename
  EndProcedure

  Procedure.i _ZIP_VerifySignature(iFileHdl.i, qOffset.q, lSignature.l)
    Protected lFileSignature.l
    Protected iResult.i
    
    FileSeek(iFileHdl, qOffset)
    If Not ReadData(iFileHdl, @lFileSignature, 4)
      iResult=#ZIP_ERR_READ_FILE
    Else
      If lSignature=lFileSignature
        iResult=#ZIP_OK
      Else
        iResult=#ZIP_NOT_OK
      EndIf
    EndIf
    
    ProcedureReturn iResult
  EndProcedure

  Procedure _ZIP_ReadDataDescriptor(iFileHdl, qOffset.q, *DataDescriptor.ZIP_DataDescriptor, *DataDescriptorSize.Integer)
    Protected iResult.i
    Protected iSignatureCnt.i
    Protected qNewOffset.q
    Protected aDataDescriptorFound.a
    Protected aDataDescriptorSigFound.a
    iResult=#ZIP_OK
    iSignatureCnt=0
    aDataDescriptorFound=#False
    aDataDescriptorSigFound=#False
    ; Method 1 - search for a Signature '08074B50'
    qNewOffset=FindHexInFileHdl(iFileHdl, "504B0708", qOffset)
    If qNewOffset
      qNewOffset+4 ; we don't need the signature
      Debug "Signature of DataDescriptor found!", 3
      aDataDescriptorFound=#True
      aDataDescriptorSigFound=#True
    EndIf
    ; Method 2 - search for another Signature and guess that the DataDescriptor has to be in the bytes before the next Signature! 
    If Not aDataDescriptorFound
      qNewOffset=FindHexInFileHdl(iFileHdl, "504B0304", qOffset) ; search for another LocalFileHeader
      If qNewOffset
        qNewOffset-SizeOf(ZIP_DataDescriptor)
        aDataDescriptorFound=#True
      EndIf   
    EndIf
    If Not aDataDescriptorFound
      qNewOffset=FindHexInFileHdl(iFileHdl, "504B0608", qOffset) ; search for an ArchiveDecryptionHeader
      If qNewOffset
        qNewOffset-SizeOf(ZIP_DataDescriptor)
        aDataDescriptorFound=#True
      EndIf   
    EndIf
    If Not aDataDescriptorFound
      qNewOffset=FindHexInFileHdl(iFileHdl, "504B0102", qOffset) ; search for an CentralDirectoryHeader
      If qNewOffset
        qNewOffset-SizeOf(ZIP_DataDescriptor)
        aDataDescriptorFound=#True
      EndIf   
    EndIf
    If aDataDescriptorFound ; read DataDescriptorValues
      FileSeek(iFileHdl, qNewOffset)
      If Not ReadData(iFileHdl, *DataDescriptor, SizeOf(ZIP_DataDescriptor))
        iResult=#ZIP_ERR_READ_FILE
      EndIf
      If iResult=#ZIP_OK ; check if the DataDescriptionHeader is the correct one
        If qOffset+*DataDescriptor\CompressedSize<>qNewOffset-aDataDescriptorSigFound*4
          Debug "ERROR! Couldn't find correct DataDescriptor! Offset >"+qOffset+"< ComprSize >"+*DataDescriptor\CompressedSize+"< DataDescriptorOffset >"+Str(qNewOffset+aDataDescriptorSigFound*4)+"<"
          iResult=#ZIP_ERR_INVALID
          *DataDescriptor\CompressedSize=0
          *DataDescriptor\UncompressedSize=0
          *DataDescriptor\CRC32=0
        EndIf
      EndIf
      If iResult=#ZIP_OK
        *DataDescriptorSize\i=SizeOf(ZIP_DataDescriptor)+aDataDescriptorSigFound*4
      EndIf
    EndIf
    
    ProcedureReturn iResult
  EndProcedure

  Enumeration
    #ZIP_Mode_RemoveFile = 1
    #ZIP_Mode_ExtractFile = 2
    #ZIP_Mode_ExamineFile = 3
    #ZIP_Mode_GetEntryData = 4
    #ZIP_Mode_CopyEntry_File2File = 5
    #ZIP_Mode_ModifyHeader = 6
  EndEnumeration

  Structure ZipEntryData
    List EntryList.ZIP_EntryData()
  EndStructure

  Procedure _ZIP_Common(sZipFile.s, iProcessMode.i, sFileToProcess.s="", iCaseSensitiv.i=#False, *EntryList.ZipEntryData=0, *MoveEntryData.ZIP_MoveEntryData=0, iPreFileHdl.i=-1)
    Protected ZIP_EOCDR.ZIP_EndOfCentralDirectoryRecordV2
    Protected ZIP_CDH.ZIP_CentralDirectoryHeaderV2
    Protected ZIP_LFH.ZIP_LocalFileHeaderV2
    Protected ZIP_LFDD.ZIP_DataDescriptor
    Protected iDataDescriptorSize.i
    Protected *ZIP_CDH_Filename
    Protected EndOfCentralDirectory_Offset.l
    Protected iFileHdl.i
    Protected iResult.i=#ZIP_OK
    Protected qCurrentOffset.q
    Protected iFileCnt.i
    Protected sFilenameLFH.s
    Protected sFilenameCDH.s
    Protected qTemp.q
    Protected iFileFound.i
    Protected iCorrectHeaderOffset.i=#False
    Protected qFileRemove_DataOffset.q
    Protected qFileRemove_DataLen.q
    Protected qFileRemove_CDH_Offset.q
    Protected qFileRemove_CDH_Len.q
    Protected qFileRemove_CompleteDataLen.q
    Protected *CompressedMemory
    Protected *UncompressedMemory
    Protected DstFileEntry.ZIP_MoveEntryData
    Protected iUnixTime.i
    Protected iUtf8Filename.i
   
    If iProcessMode=#ZIP_Mode_ExamineFile And *EntryList=0 : iResult=#ZIP_ERR_WRONG_PARAMETER : EndIf
    If iProcessMode=#ZIP_Mode_GetEntryData And *MoveEntryData=0 : iResult=#ZIP_ERR_WRONG_PARAMETER :     EndIf
    If iProcessMode=#ZIP_Mode_CopyEntry_File2File And (sFileToProcess="" Or *MoveEntryData=0) : iResult=#ZIP_ERR_WRONG_PARAMETER : EndIf
    If iProcessMode=#ZIP_Mode_ModifyHeader 
      iUnixTime=*EntryList
      iUtf8Filename=*MoveEntryData
    EndIf
    If iProcessMode<>#ZIP_Mode_ExamineFile
      ReplaceString(sFileToProcess, "\", "/", #PB_String_InPlace) ; all backslashes are converted to slashes to support subdirectories properly
    EndIf
    
    If iResult=#ZIP_OK
      If iPreFileHdl=-1
        iFileHdl=ReadFile(#PB_Any, sZipFile, #PB_File_SharedRead)
        If iFileHdl=0 : iResult=#ZIP_ERR_READ_FILE : EndIf
      Else
        If IsFile(iPreFileHdl)
          iFileHdl=iPreFileHdl
        Else
          iResult=#ZIP_ERR_READ_FILE
        EndIf
      EndIf
    EndIf
    If iResult=#ZIP_OK
      EndOfCentralDirectory_Offset=FindHexInFileHdl(iFileHdl, "504B0506", 0, #False) ; find 'end of central dir signature'
      If EndOfCentralDirectory_Offset<0
        iResult=#ZIP_ERR_INVALID
      EndIf
    EndIf
    If iResult=#ZIP_OK
      FileSeek(iFileHdl, EndOfCentralDirectory_Offset)
      If Not ReadData(iFileHdl, @ZIP_EOCDR, SizeOf(ZIP_EndOfCentralDirectoryRecordV2))
        iResult=#ZIP_ERR_READ_FILE
      EndIf
    EndIf
    If iResult=#ZIP_OK
      If ZIP_EOCDR\NumberOfDisk>0 Or ZIP_EOCDR\NumberOfEntries<>ZIP_EOCDR\NumberOfTotalEntries
        iResult=#ZIP_ERR_MULTI_FILE
      EndIf
    EndIf
    If iResult=#ZIP_OK
      If iProcessMode=#ZIP_Mode_ExamineFile
        ClearList(*EntryList\EntryList())
      EndIf
    EndIf
    
    If iResult=#ZIP_OK And iProcessMode<>#ZIP_Mode_RemoveFile ; we don't need to parse all CDH/LFH in the zip file to remove it
      qCurrentOffset=ZIP_EOCDR\CentralDirStartOffset
      iFileCnt=0
      
      iFileFound=#False
      Repeat
        If _ZIP_VerifySignature(iFileHdl, qCurrentOffset, #ZIP_Signature_CDH)<#ZIP_OK ; verify CentralDirectoryHeader-Signature
          iResult=#ZIP_ERR_SIGNATURE
          Break
        EndIf
        FileSeek(iFileHdl, qCurrentOffset)
        If Not ReadData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
          iResult=#ZIP_ERR_READ_FILE
          Break
        EndIf
        sFilenameCDH=_ZIP_GetFilename(iFileHdl, qCurrentOffset+SizeOf(ZIP_CentralDirectoryHeaderV2), ZIP_CDH\FilenameLength, ZIP_CDH\GeneralPurpose, @iResult)
        If iResult<>#ZIP_OK
          Break
        EndIf
        If ZIP_CDH\CompressedSize=-1 Or ZIP_CDH\UncompressedSize=-1 Or ZIP_CDH\CRC32=-1
          iResult=#ZIP_ERR_ZIP64
          Break
        EndIf
        iDataDescriptorSize=0
        If iProcessMode=#ZIP_Mode_ExamineFile
          If AddElement(*EntryList\EntryList())
            *EntryList\EntryList()\EntryName=sFilenameCDH
            *EntryList\EntryList()\EntrySizeCompressed=ZIP_CDH\CompressedSize
            *EntryList\EntryList()\EntrySizeUncompressed=ZIP_CDH\UncompressedSize
            *EntryList\EntryList()\EntryLastModFileDateTime=DosToUnixTime(ZIP_CDH\LastModFileDate, ZIP_CDH\LastModFileTime)
            *EntryList\EntryList()\EntryCRC32=ZIP_CDH\CRC32
            If ZIP_CDH\CompressedSize=0
              *EntryList\EntryList()\EntryType=#PB_Packer_Directory
            Else
              *EntryList\EntryList()\EntryType=#PB_Packer_File
            EndIf
          Else
            iResult=#ZIP_ERR_ADDELEMENT
            Break
          EndIf  
        ElseIf ((iCaseSensitiv=#True And sFilenameCDH=sFileToProcess) Or (iCaseSensitiv=#False And LCase(sFilenameCDH)=LCase(sFileToProcess)))  And iProcessMode<>#ZIP_Mode_CopyEntry_File2File
          iFileFound=#True 
          If _ZIP_VerifySignature(iFileHdl, ZIP_CDH\OffsetOfLocalHeader, #ZIP_Signature_LFH)<#ZIP_OK ; verify LocalFileHeader-Signature
            iResult=#ZIP_ERR_SIGNATURE
            Break
          EndIf
          FileSeek(iFileHdl, ZIP_CDH\OffsetOfLocalHeader)
          If Not ReadData(iFileHdl, @ZIP_LFH, SizeOf(ZIP_LocalFileHeaderV2))
            iResult=#ZIP_ERR_READ_FILE
            Break
          EndIf
          sFilenameLFH=_ZIP_GetFilename(iFileHdl, ZIP_CDH\OffsetOfLocalHeader+SizeOf(ZIP_LocalFileHeaderV2), ZIP_LFH\FilenameLength, ZIP_CDH\GeneralPurpose, @iResult)
          If iResult<>#ZIP_OK
            Break
          EndIf
          If ZIP_LFH\CompressedSize=-1 Or ZIP_LFH\UncompressedSize=-1 Or ZIP_LFH\CRC32=-1
            iResult=#ZIP_ERR_ZIP64
            Break
          EndIf
          If (ZIP_LFH\GeneralPurpose & $8)
            iResult=_ZIP_ReadDataDescriptor(iFileHdl, ZIP_CDH\OffsetOfLocalHeader+SizeOf(ZIP_LocalFileHeaderV2)+ZIP_LFH\FilenameLength+ZIP_LFH\ExtraFieldLength, @ZIP_LFDD, @iDataDescriptorSize)
            If iResult>0
              ZIP_LFH\CRC32            = ZIP_LFDD\CRC32
              ZIP_LFH\CompressedSize   = ZIP_LFDD\CompressedSize
              ZIP_LFH\UncompressedSize = ZIP_LFDD\UncompressedSize
            Else
              Break
            EndIf
          EndIf
          If sFilenameCDH<>sFilenameLFH
            iResult=#ZIP_ERR_INVALID
            Break
          EndIf
          
          If iProcessMode=#ZIP_Mode_GetEntryData
            *MoveEntryData\CDH_Offset = qCurrentOffset
            *MoveEntryData\CDH_Size   = SizeOf(ZIP_CentralDirectoryHeaderV2)+ZIP_CDH\FilenameLength+ZIP_CDH\ExtraFieldLength+ZIP_CDH\FileCommentLength
            *MoveEntryData\LFH_Offset = ZIP_CDH\OffsetOfLocalHeader
            *MoveEntryData\LFH_Size   = SizeOf(ZIP_LocalFileHeaderV2)+ZIP_LFH\FilenameLength+ZIP_LFH\ExtraFieldLength+ZIP_LFH\CompressedSize+iDataDescriptorSize
            Break
          EndIf
        
          If iProcessMode=#ZIP_Mode_ModifyHeader
            If iUnixTime<>-1
              UnixToDosTime(iUnixTime, @ZIP_LFH\LastModFileDate, @ZIP_LFH\LastModFileTime)
              ZIP_CDH\LastModFileDate=ZIP_LFH\LastModFileDate
              ZIP_CDH\LastModFileTime=ZIP_LFH\LastModFileTime
            EndIf
            If iUtf8Filename<>-1
              If iUtf8Filename=1 ; in case filename is UTF8 encoded (set BIT11 to 1)
                ZIP_LFH\GeneralPurpose=ZIP_LFH\GeneralPurpose | $800
              ElseIf iUtf8Filename=0 ; in case filename is ASCII only (set BIT11 to 0)
                ZIP_LFH\GeneralPurpose=ZIP_LFH\GeneralPurpose & ~$800
              EndIf
              ZIP_CDH\GeneralPurpose=ZIP_LFH\GeneralPurpose
            EndIf 
            If iPreFileHdl=-1 ; only in case we opened the file ourselves
              CloseFile(iFileHdl)
              iFileHdl=OpenFile(#PB_Any, sZipFile, #PB_File_SharedRead)
              If iFileHdl=0
                iResult=#ZIP_ERR_READ_FILE
                Break
              EndIf
            EndIf
            FileSeek(iFileHdl, ZIP_CDH\OffsetOfLocalHeader)
            If Not WriteData(iFileHdl, @ZIP_LFH, SizeOf(ZIP_LocalFileHeaderV2))
              iResult=#ZIP_ERR_WRITE_FILE
              Break
            EndIf
            FileSeek(iFileHdl, qCurrentOffset)
            If Not WriteData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
              iResult=#ZIP_ERR_WRITE_FILE
              Break
            EndIf
           Break
          EndIf
        EndIf
        qCurrentOffset+SizeOf(ZIP_CentralDirectoryHeaderV2)+ZIP_CDH\FilenameLength+ZIP_CDH\ExtraFieldLength+ZIP_CDH\FileCommentLength
        iFileCnt+1
      Until qCurrentOffset>=ZIP_EOCDR\CentralDirStartOffset+ZIP_EOCDR\CentralDirSize Or iFileCnt>=ZIP_EOCDR\NumberOfEntries
      
      If iProcessMode<>#ZIP_Mode_CopyEntry_File2File And iProcessMode<>#ZIP_Mode_ExamineFile And iFileFound=#False
        iResult=#ZIP_ERR_ENTRY_NOT_FOUND
      EndIf
    EndIf
    
    If iResult=#ZIP_OK And iProcessMode=#ZIP_Mode_CopyEntry_File2File
      Repeat ; single repeat - allows to simply break in case of an error
        iResult=_ZIP_Common(sZipfile, #ZIP_Mode_GetEntryData, sFilenameCDH, 0, 0, @DstFileEntry, iFileHdl) ; 'sFilenameCDH' has the name of the last entry in the CentralDirectory
        If iResult<>#ZIP_OK : Break : EndIf
        If iPreFileHdl=-1 ; only in case we opened the file ourselves
          CloseFile(iFileHdl)
          iFileHdl=OpenFile(#PB_Any, sZipFile, #PB_File_SharedRead)
          If iFileHdl=0
             iResult=#ZIP_ERR_READ_FILE
             Break
          EndIf
        EndIf
        iResult=_ZIP_CopyDataFromFile2FileHdl(sFileToProcess, *MoveEntryData\LFH_Offset, *MoveEntryData\LFH_Size, iFileHdl, DstFileEntry\LFH_Offset+DstFileEntry\LFH_Size)
        If iResult<>#ZIP_OK : Break : EndIf
        qCurrentOffset=ZIP_EOCDR\CentralDirStartOffset+*MoveEntryData\LFH_Size
        If _ZIP_VerifySignature(iFileHdl, qCurrentOffset, #ZIP_Signature_CDH)<#ZIP_OK ; verify CentralDirectoryHeader-Signature
          iResult=#ZIP_ERR_SIGNATURE
          Break
        EndIf
        qCurrentOffset+ZIP_EOCDR\CentralDirSize ; add the current CentralDirSize to the currentPosition
        iResult=_ZIP_CopyDataFromFile2FileHdl(sFileToProcess, *MoveEntryData\CDH_Offset, *MoveEntryData\CDH_Size, iFileHdl, qCurrentOffset)
        If iResult<>#ZIP_OK : Break : EndIf
        FileSeek(iFileHdl, qCurrentOffset)
        If Not ReadData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
          iResult=#ZIP_ERR_READ_FILE
          Break
        EndIf   
        ZIP_CDH\OffsetOfLocalHeader=DstFileEntry\LFH_Offset+DstFileEntry\LFH_Size
        FileSeek(iFileHdl, qCurrentOffset)
        If Not WriteData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
          iResult=#ZIP_ERR_WRITE_FILE
          Break
        EndIf
        
        ZIP_EOCDR\NumberOfEntries+1                                                  ; increase entry count by one
        ZIP_EOCDR\NumberOfTotalEntries+1                                             ; increase total entry count by one
        ZIP_EOCDR\CentralDirStartOffset+*MoveEntryData\LFH_Size                      ; increase offset of beginning of central dir by len of new entry
        ZIP_EOCDR\CentralDirSize+*MoveEntryData\CDH_Size                             ; increase central dir size by size of added directory entry
        EndOfCentralDirectory_Offset+*MoveEntryData\LFH_Size+*MoveEntryData\CDH_Size ; increase the 'EndOfCentralDirectory_Offset' by the complete len of added bytes
        
        If _ZIP_VerifySignature(iFileHdl, EndOfCentralDirectory_Offset, #ZIP_Signature_EOCDR)<#ZIP_OK ; verify EndOfCentralDirectory-Signature
          iResult=#ZIP_ERR_SIGNATURE
          Break
        EndIf
        FileSeek(iFileHdl, EndOfCentralDirectory_Offset)
        If Not WriteData(iFileHdl, @ZIP_EOCDR, SizeOf(ZIP_EndOfCentralDirectoryRecordV2))
          iResult=#ZIP_ERR_WRITE_FILE
          Break
        EndIf
        If _ZIP_VerifySignature(iFileHdl, EndOfCentralDirectory_Offset, #ZIP_Signature_EOCDR)<#ZIP_OK ; verify EndOfCentralDirectory-Signature
          iResult=#ZIP_ERR_SIGNATURE
          Break
        EndIf
      Until #True
    EndIf
    
    If iResult=#ZIP_OK And iProcessMode=#ZIP_Mode_RemoveFile
      Repeat ; single repeat - allows to simply break in case of an error
        iResult=_ZIP_Common(sZipfile, #ZIP_Mode_GetEntryData, sFileToProcess, 0, 0, @DstFileEntry, iFileHdl)
        If iResult<>#ZIP_OK : Break : EndIf
        If iPreFileHdl=-1 ; only in case we opened the file ourselves
          CloseFile(iFileHdl)
          iFileHdl=OpenFile(#PB_Any, sZipFile, #PB_File_SharedRead)
          If iFileHdl=0
             iResult=#ZIP_ERR_READ_FILE
             Break
          EndIf
        EndIf
        iResult=_ZIP_DropDataFromFileHdl(iFileHdl, DstFileEntry\CDH_Offset, DstFileEntry\CDH_Size)
        If iResult<>#ZIP_OK
          iResult=#ZIP_ERR_DELETE_DATA
          Break
        EndIf
        iResult=_ZIP_DropDataFromFileHdl(iFileHdl, DstFileEntry\LFH_Offset, DstFileEntry\LFH_Size)
        If iResult<>#ZIP_OK
          iResult=#ZIP_ERR_DELETE_DATA
          Break
        EndIf
        ZIP_EOCDR\NumberOfEntries-1                                              ; reduce entry count by one
        ZIP_EOCDR\NumberOfTotalEntries-1                                         ; reduce total entry count by one
        ZIP_EOCDR\CentralDirStartOffset-DstFileEntry\LFH_Size                    ; reduce offset of beginning of central dir by deleted data len
        ZIP_EOCDR\CentralDirSize-DstFileEntry\CDH_Size                           ; reduce central dir size by size of deleted directory entry
        EndOfCentralDirectory_Offset-DstFileEntry\LFH_Size-DstFileEntry\CDH_Size ; reduce the 'EndOfCentralDirectory_Offset' by the complete len of removed bytes

        If _ZIP_VerifySignature(iFileHdl, EndOfCentralDirectory_Offset, #ZIP_Signature_EOCDR)<#ZIP_OK ; verify EndOfCentralDirectory-Signature
          iResult=#ZIP_ERR_SIGNATURE
          Break
        EndIf
        FileSeek(iFileHdl, EndOfCentralDirectory_Offset)
        If Not WriteData(iFileHdl, @ZIP_EOCDR, SizeOf(ZIP_EndOfCentralDirectoryRecordV2))
          iResult=#ZIP_ERR_WRITE_FILE
          Break
        EndIf
        qCurrentOffset=ZIP_EOCDR\CentralDirStartOffset
        iFileCnt=0
        Repeat
          FileSeek(iFileHdl, qCurrentOffset)
          If Not ReadData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
            iResult=#ZIP_ERR_READ_FILE
            Break
          EndIf   
          If ZIP_CDH\OffsetOfLocalHeader>DstFileEntry\LFH_Offset
            ZIP_CDH\OffsetOfLocalHeader-DstFileEntry\LFH_Size
            FileSeek(iFileHdl, qCurrentOffset)
            If Not WriteData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
              iResult=#ZIP_ERR_WRITE_FILE
              Break
            EndIf
          EndIf
          qCurrentOffset+SizeOf(ZIP_CentralDirectoryHeaderV2)+ZIP_CDH\FilenameLength+ZIP_CDH\ExtraFieldLength+ZIP_CDH\FileCommentLength
          iFileCnt+1
        Until qCurrentOffset>=ZIP_EOCDR\CentralDirStartOffset+ZIP_EOCDR\CentralDirSize Or iFileCnt>=ZIP_EOCDR\NumberOfEntries
      Until #True
    EndIf
    If iPreFileHdl=-1 ; only in case we opened the file ourselves
      If IsFile(iFileHdl)
        CloseFile(iFileHdl)
      EndIf
    EndIf
    
    ProcedureReturn iResult
  EndProcedure

  Procedure RemoveFile(sZipfile.s, sFileToRemove.s, iCaseSensitiv.i=#False, iFileHdl.i=-1)
    ProcedureReturn _ZIP_Common(sZipfile, #ZIP_Mode_RemoveFile, sFileToRemove, iCaseSensitiv, 0, 0, iFileHdl)
  EndProcedure
  
  Procedure ChangeFileDateTime(sZipfile.s, sFileToModify.s, lUnixTimeStamp.l, iCaseSensitiv.i=#False)
    ProcedureReturn _ZIP_Common(sZipfile, #ZIP_Mode_ModifyHeader, sFileToModify, iCaseSensitiv, lUnixTimeStamp, -1)
  EndProcedure

  Procedure ZIP_ModifyHeader(sZipfile.s, sFileToModify.s, lUnixTimeStamp.l=-1, iUtf8Filename.i=-1, iCaseSensitiv.i=#False)
    ProcedureReturn _ZIP_Common(sZipfile, #ZIP_Mode_ModifyHeader, sFileToModify, iCaseSensitiv, lUnixTimeStamp, iUtf8Filename)
  EndProcedure

  Procedure AddFile(sZipfile.s, sFileToAdd.s, sZippedFileName.s)
    Protected iZipHdl.i
    Protected sZipTempFile.s
    Protected iResult.i
    Protected SrcItemData.ZIP_MoveEntryData
    
    iResult=#ZIP_OK
    Repeat ; single repeat
      If FileSize(sZipfile)<0 ; destination file does not exist - so we don't need a temp file
        sZipTempFile=sZipfile
      Else
        sZipTempFile=GetTemporaryDirectory()+GetFilePart(sZipfile)+"_AddFileTemp"
      EndIf
      iZipHdl=CreatePack(#PB_Any, sZipTempFile, #PB_PackerPlugin_Zip)
      If iZipHdl
        If Not AddPackFile(iZipHdl, sFileToAdd, sZippedFileName)
          iResult=#ZIP_ERR_ADDELEMENT
          ClosePack(iZipHdl)
          Break
        EndIf
        ClosePack(iZipHdl)
      Else
        iResult=#ZIP_ERR_WRITE_FILE
        Break
      EndIf
      If StringUtf8Required(sZippedFileName)
        If Not ZIP_ModifyHeader(sZipTempFile, sZippedFileName, -1, #True)
          iResult=#ZIP_ERR_WRITE_FILE
          Break
        EndIf
      EndIf
      If sZipTempFile<>sZipfile ; in case the temp file is different from the final file we need to copy the added-entry from the temp file to the destination file
        iResult=_ZIP_Common(sZipTempFile, #ZIP_Mode_GetEntryData, sZippedFileName, 0, 0, @SrcItemData)
        If iResult<0 : Break : EndIf
        iResult=_ZIP_Common(sZipfile, #ZIP_Mode_CopyEntry_File2File, sZipTempFile, 0, 0, @SrcItemData)
        If iResult<0 : Break : EndIf
        DeleteFile(sZipTempFile)
      EndIf
    Until #True
   
    ProcedureReturn iResult
  EndProcedure

  Procedure AddFileFromMem(sZipfile.s, *SrcMem, iSrcSize.i, sZippedFileName.s, iPreFileHdl.i=-1, lDateTime.l=-1)
    Protected iZipHdl.i
    Protected sZipTempFile.s
    Protected iResult.i
    Protected SrcItemData.ZIP_MoveEntryData
   
    iResult=#ZIP_OK
   
    If lDateTime=-1 : lDateTime=Date() : EndIf
   
    Repeat ; single repeat
      If FileSize(sZipfile)<0 ; destination file does not exist - so we don't need a temp file
        sZipTempFile=sZipfile
      Else
        sZipTempFile=GetTemporaryDirectory()+GetFilePart(sZipfile)+"_AddFileTemp"
      EndIf
      iZipHdl=CreatePack(#PB_Any, sZipTempFile, #PB_PackerPlugin_Zip)
      If iZipHdl
        If Not AddPackMemory(iZipHdl, *SrcMem, iSrcSize, sZippedFileName)
          iResult=#ZIP_ERR_ADDELEMENT
          ClosePack(iZipHdl)
          Break
        EndIf
        ClosePack(iZipHdl)
      Else
        iResult=#ZIP_ERR_WRITE_FILE
        Break
      EndIf
      If Not ZIP_ModifyHeader(sZipTempFile, sZippedFileName, lDateTime, StringUtf8Required(sZippedFileName) )
        iResult=#ZIP_ERR_WRITE_FILE
        Break
      EndIf
      If sZipTempFile<>sZipfile ; in case the temp file is different from the final file we need to copy the added-entry from the temp file to the destination file
        iResult=_ZIP_Common(sZipTempFile, #ZIP_Mode_GetEntryData, sZippedFileName, 0, 0, @SrcItemData)
        If iResult<0
          Break
        EndIf
        iResult=_ZIP_Common(sZipfile, #ZIP_Mode_CopyEntry_File2File, sZipTempFile, 0, 0, @SrcItemData, iPreFileHdl)
        If iResult<0 : Break : EndIf
        DeleteFile(sZipTempFile)
      EndIf
    Until #True
   
    ProcedureReturn iResult
  EndProcedure

  Procedure.i Examine(sZipfile.s, List EntryList.ZIP_EntryData(), iPreFileHdl.i=-1)
    Protected ListHlp.ZipEntryData
    Protected iResult.i
    iResult=_ZIP_Common(sZipfile, #ZIP_Mode_ExamineFile, "", 0, @ListHlp, 0, iPreFileHdl)
    If Not CopyList(ListHlp\EntryList(), EntryList())
      iResult=#ZIP_ERR_UNSPEC_ERROR
    EndIf
    ProcedureReturn iResult
  EndProcedure

EndModule


CompilerIf #PB_Compiler_IsMainFile

  Procedure.i Mem_Dump2File(*StartAddr, sFilename.s)
    Protected iResult.i
    iResult=CreateFile(#PB_Any, sFilename)
    If (iResult<>0)
      WriteData(iResult, *StartAddr, MemorySize(*StartAddr))
      CloseFile(iResult)
    Else
      Debug "Mem_Dump2File - CreateFile Error >"+Str(iResult)+"<"
    EndIf   
  EndProcedure

  Procedure.s FormatTime(iMilliseconds.i)
    Protected iSeconds.i
    Protected iMinutes.i
    Protected iHours.i
    iSeconds=iMilliseconds/1000
    iMilliseconds-(iSeconds*1000)
    iMinutes=iSeconds/60
    iSeconds-(iMinutes*60)
    iHours=iMinutes/60
    iMinutes-(iHours*60)
    ProcedureReturn StrU(iHours)+":"+Right("00"+StrU(iMinutes),2)+":"+Right("00"+StrU(iSeconds),2)+","+Right("000"+StrU(iMilliseconds),3)
  EndProcedure

  Define.i iResult, iCnt, iFileHdl, iStartime, iEndtime
  Define.s sZipfile, sTmpFile, sRandom
  Define *SrcMem
  Define NewList ZipList.ZIP::ZIP_EntryData()
  
  sZipfile=GetTemporaryDirectory()+"ZipFile.zip"
  sTmpFile=GetTemporaryDirectory()+"FileFromFilesystem.dat"
  *SrcMem=AllocateMemory(100000, #PB_Memory_NoClear)
  Mem_Dump2File(*SrcMem, sTmpFile)
  
  Debug "Example 1 - add files from mem"
  iResult=ZIP::AddFileFromMem(sZipfile, *SrcMem, MemorySize(*SrcMem), "SubDirMem\FileFromMemWithSubdir.dat")
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  iResult=ZIP::AddFileFromMem(sZipfile, *SrcMem, MemorySize(*SrcMem), "FileFromMemWithoutSubdir.dat", -1, Date(2010,01,02,03,04,05)) ; date is stored in DOS format in ZIP file with a resolution of 2 seconds - so second '05' is stored as 04
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  iResult=ZIP::AddFileFromMem(sZipfile, *SrcMem, MemorySize(*SrcMem), "FileFromMemWithUTF8CharŪƝƗƇƠƉĖ.dat", -1, Date(2010,01,02,03,04,05)) ; date is stored in DOS format in ZIP file with a resolution of 2 seconds - so second '05' is stored as 04
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  
  Debug "Example 2 - add files from filesystem"
  iResult=ZIP::AddFile(sZipfile, sTmpFile, "SubDirFS\"+GetFilePart(sTmpFile))
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  iResult=ZIP::AddFile(sZipfile, sTmpFile, "FileFromFilesystemWithoutSubdir.dat")
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  iResult=ZIP::AddFile(sZipfile, sTmpFile, "FileFromFilesystemWithUTF8CharŪƝƗƇƠƉĖ.dat")
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  
  Debug "Example 3 - show details of files in ZIP file"
  iResult=ZIP::Examine(sZipfile, ZipList())
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  ForEach ZipList()
    Debug "Filename >"+ZipList()\EntryName+"< Size Comp >"+ZipList()\EntrySizeCompressed+"< Size Uncomp >"+ZipList()\EntrySizeUncompressed+"< Type >"+ZipList()\EntryType+"< Date >"+FormatDate("%yyyy.%mm.%dd %hh:%ii:%ss", ZipList()\EntryLastModFileDateTime)+"<"
  Next
  
  Debug "Example 4 - Delete files from ZIP"
  iResult=ZIP::RemoveFile(sZipfile, "SubDirMem\FileFromMemWithSubdir.dat")
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  iResult=ZIP::RemoveFile(sZipfile, "FileFromMemWithoutSubdir.dat")
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  iResult=ZIP::RemoveFile(sZipfile, "FileFromMemWithUTF8CharŪƝƗƇƠƉĖ.dat")
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf

  Debug "Example 5 - Modify date of existing entry"
  iResult=ZIP::ChangeFileDateTime(sZipfile, "SubDirFS\"+GetFilePart(sTmpFile), Date(2020,03,05,08,22,33))
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  iResult=ZIP::ChangeFileDateTime(sZipfile, "FileFromFilesystemWithoutSubdir.dat", Date(2021,03,05,08,22,33))
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  iResult=ZIP::ChangeFileDateTime(sZipfile, "FileFromFilesystemWithUTF8CharŪƝƗƇƠƉĖ.dat", Date(2022,03,05,08,22,33))
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  
  Debug "show details of files in ZIP file again"
  iResult=ZIP::Examine(sZipfile, ZipList())
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  ForEach ZipList()
    Debug "Filename >"+ZipList()\EntryName+"< Size Comp >"+ZipList()\EntrySizeCompressed+"< Size Uncomp >"+ZipList()\EntrySizeUncompressed+"< Type >"+ZipList()\EntryType+"< Date >"+FormatDate("%yyyy.%mm.%dd %hh:%ii:%ss", ZipList()\EntryLastModFileDateTime)+"<"
  Next
  
  Debug "Example 6a - add a lot of files with PreFileHdl and remove it again" ; with PreFileHdl the purebasic file caching is used and it's a lot faster than opening/closing the zipfile for each add-operation (at least on slow devices like usb sticks or network drives)
  DisableDebugger
  iStartime=ElapsedMilliseconds()
  iFileHdl=OpenFile(#PB_Any, sZipfile)
  If iFileHdl
    For iCnt=100 To 200 Step 1
      sRandom=RSet(Str(iCnt),3,"0")+"-mem.txt"
      iResult=ZIP::AddFileFromMem(sZipfile, *SrcMem, 1000, sRandom, iFileHdl)
      If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
    Next
    For iCnt=100 To 200 Step 1
      sRandom=RSet(Str(iCnt),3,"0")+"-mem.txt"
      iResult=ZIP::RemoveFile(sZipfile, sRandom, #False, iFileHdl)
      If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
    Next
    CloseFile(iFileHdl)
  EndIf
  iEndtime=ElapsedMilliseconds()
  EnableDebugger
  Debug "Duration >"+FormatTime(iEndtime-iStartime)+"<"
  
  Debug "Example 6b - add a lot of files without PreFileHdl (and remove it again)"
  DisableDebugger
  iStartime=ElapsedMilliseconds()
  For iCnt=300 To 400 Step 1
    sRandom=RSet(Str(iCnt),3,"0")+"-mem.txt"
    iResult=ZIP::AddFileFromMem(sZipfile, *SrcMem, 1000, sRandom)
    If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  Next
  For iCnt=300 To 400 Step 1
    sRandom=RSet(Str(iCnt),3,"0")+"-mem.txt"
    iResult=ZIP::RemoveFile(sZipfile, sRandom)
    If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  Next
  iEndtime=ElapsedMilliseconds()
  EnableDebugger
  Debug "Duration >"+FormatTime(iEndtime-iStartime)+"<"
  
  Debug "show details of files in ZIP file again"
  iResult=ZIP::Examine(sZipfile, ZipList())
  If iResult<>ZIP::#ZIP_OK : Debug ZIP::GetErrorText(iResult) : EndIf
  ForEach ZipList()
    Debug "Filename >"+ZipList()\EntryName+"< Size Comp >"+ZipList()\EntrySizeCompressed+"< Size Uncomp >"+ZipList()\EntrySizeUncompressed+"< Type >"+ZipList()\EntryType+"< Date >"+FormatDate("%yyyy.%mm.%dd %hh:%ii:%ss", ZipList()\EntryLastModFileDateTime)+"<"
  Next

  DeleteFile(sZipfile)
  DeleteFile(sTmpFile)
  
CompilerEndIf
Download of PureBasic - Module
Download of PureBasic - Programmes

[Windows 11 x64] [PB V6]

Bild