The new packer-implementation since Purebasic 5.4.x has a few drawbacks:
- 'RemovePackFile' got removed - http://www.purebasic.fr/english/viewtopic.php?t=63739
- 'AddPackFile' cannot be used with existing archives - http://www.purebasic.fr/english/viewtop ... 83#p481883
- 'AddPackMemory' does not set a useful filedate in the archive and there's no way to modify the timestamp of existing files (date >01.01.1980 00:00< is set)
- 'AddPackFile' and 'AddPackMemory' do not set the UTF8-Filename bit in the archive in case the filename is stored as UTF8 >> not all ZIP engines can decrypt the filenames correctly (e.g. windows 8.1 zip engine relies on this utf8-flag in the archive)
Here's the result as a module - it has only a few public procedures and they're fairly easy to use, I've created an examples section at the bottom of the code that shows nearly everything that is possible with the module:
- ZIP_RemoveFile >> to remove files from existing zip files
- ZIP_AddFileFromMem >> add a new file from memory to a new or existing zip file
- ZIP_AddFile >> add a new file from filesystem to a new or existing zip file
- ZIP_ChangeFileDateTime >> change the stored FileDateTime of a file in an existing zip file
- ZIP_Examine >> return all files in a zip file as list with some details like compressed size, umcompressed size, filedatetime
Here's the code (it's too long for one post - so I've split into 2 parts in 2 posts):
PART1:
Code: Select all
EnableExplicit
UseZipPacker()
;- ## 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)
DebugLevel 0 ; set it from 0-3 to get different debug levels (and uncomment disabledebugger at beginning of module)
;- ## 1-DeclareModule
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 ZIP_AddFile(sZipfile.s, sFileToAdd.s, sZippedFileName.s)
Declare.i ZIP_AddFileFromMem(sZipfile.s, *SrcMem, iSrcSize.i, sZippedFileName.s, iPreFileHdl.i=-1, lDateTime.l=-1)
Declare.i ZIP_ChangeFileDateTime(sZipfile.s, sFileToModify.s, lUnixTimeStamp.l, iCaseSensitiv.i=#False)
Declare.i ZIP_Examine(sZipfile.s, List EntryList.ZIP_EntryData(), iPreFileHdl.i=-1)
Declare.i ZIP_RemoveFile(sZipfile.s, sFileToRemove.s, iCaseSensitiv.i=#False, iFileHdl.i=-1)
Declare.s ZIP_GetErrorText(iError.i)
EndDeclareModule
;- ## 2-Module
Module ZIP
DisableDebugger
Procedure.s ZIP_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)
; http://stackoverflow.com/questions/33918288/how-to-convert-dos-time-to-unix-time-in-php
; https://opensource.apple.com/source/gcc_os/gcc_os-1256/fastjar/dostime.c
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)
; Convert the date y/m/d and time h:m:s to 2 two-byte variables - dos-date and -time
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)
Debug "FindHexInFileHdl >"+iFileHdl+"< ToFind >"+sHexToFind+"<", 2
; 20150706 .. nalor .. correct result - but not that fast...
; 20150807 .. nalor .. bugfix: starposition wasn't used
; 20160304 .. nalor .. bugfix: backward-search didn't work properly
; DisableDebugger
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)
Debug "ERROR!! FileHdl is not valid >"+iFileHdl+"<"
ProcedureReturn -1
EndIf
If iSrchDataLen=0 Or qStartPosition<0 Or (Len(sHexToFind)%2)<>0
Debug "Error! Invalid parameter values!"
ProcedureReturn -1
EndIf
qFileSize=Lof(iFileHdl)
*SrchData=AllocateMemory(iSrchDataLen)
*TestData=AllocateMemory(iChunkSize)
Debug "SrchDataLen >"+iSrchDataLen+"<", 3
If *SrchData=0 Or *TestData=0
Debug "Error! Allocating memory!", 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
; Print(".") ; for every started chunk a dot is
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
; Debug "FINAL hex value found! >"+qSearchResult+"< >0x"+Hex(qSearchResult, #PB_Quad)+"<"
Break 2
EndIf
Next
qFileDataRead+iTmp ; wenn er hier ankommt hat er kein Ergebnis gefunden - dann die durchsuchten Bytes addieren, von hier aus wird dann im nächsten Durchlauf der nächste Chunk eingelesen
Else
Debug "special error case", 0
qFileDataRead+iChunkRead ; in diesem Fall ist der letzte gelesene Datenblock kleiner als die Suchgröße gewesen...
EndIf
Wend
Else
Debug "backward search", 3
If qStartPosition<>0
qFileDataRead=qStartPosition
Else
qFileDataRead=qFileSize
EndIf
If qFileDataRead>=iChunkSize
qFileDataRead-iChunkSize
Else
iChunkSize=qFileDataRead
qFileDataRead=0
EndIf
While qFileDataRead>=0
; Print(".") ; for every started chunk a dot is printed
FileSeek(iFileHdl, qFileDataRead)
Debug "ChunkSize >"+iChunkSize+"<", 3
iChunkRead=ReadData(iFileHdl, *TestData, iChunkSize)
For iTmp=iChunkRead-iSrchDataLen To 0 Step -1 ; am ende anfangen und immer einen schritt nach vorne gehen
If CompareMemory(*TestData+iTmp, *SrchData, iSrchDataLen)
qSearchResult=qFileDataRead+iTmp
Debug "FINAL hex value found! >"+qSearchResult+"< >0x"+Hex(qSearchResult, #PB_Quad)+"<", 2
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 Specification
; https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT
;- ZIP Constants
#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
;- ZIP Structures
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 (APPNOTE 6.3.4 - section 4.3.9.1) - 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
; ZIP_CentralDirectoryHeaderV2
; 50 4B 01 02 3F 00 14 00 00 00 08 00 2E A5 64 47 12 A1 C9 AD E1 47 00 00 00 8E 00 00 0D 00 24 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 61 61 63 73 5F 69 6E 66 6F 2E 65 78 65 0A 00 20 00 00 00 00 00 01 00 18 00 00 24 15 CC 38 17 D1 01 00 24 15 CC 38 17 D1 01 34 13 D4 EC 38 17 D1 01
; ----------- central file header signature ----------- compressed size ----------- RelativeOffsetOfLocalHeader
; ----- version made by ----------- uncompressed size
; ----- version needed to extract ----- filename length
; ----- general purpose ----- extra field length
; ----- compression method ----- file comment length
; ----- last mod file time ----- disk number start
; ----- last mod file date ----- InternalFileAttributes
; ----------- CRC32 ----------- ExternalFileAttributes
; filename (variable size)
; extra field (variable size)
; file comment (variable size)
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
; ZIP_EndOfCentralDirectoryRecordV2
; 50 4B 05 06 00 00 00 00 04 00 04 00 84 01 00 00 0E FD 03 00 00 00 + zipfile comment (variable size)
; ----------- end of central dir signature
; ----- number of this disk
; ----- number of start with central dir
; ----- total number of entries in this central dir
; ----- total number of entries in central dir
; ----------- size of central dir
; ----------- offset of start of central directory With respect To the starting disk number
; ----- zipfile comment length
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
;- ZIP Custom Structures
Structure ZIP_MoveEntryData
CDH_Offset.q
CDH_Size.q
LFH_Offset.q
LFH_Size.q
EndStructure
Procedure seems_utf8(*StrMem, iLen.i)
; http://stackoverflow.com/questions/1473441/check-to-see-if-a-string-is-encoded-as-utf-8
Protected iCnt.i
Protected aCharVal.a
Protected iNext.i
Protected iCnt2.i
Protected iUtf8Detected.i=#False
; get length, For utf8 this means bytes and not characters
; we need to check each byte in the string
For iCnt=0 To iLen-1
; get the byte code 0-255 of the i-th byte
aCharVal = PeekA(*StrMem+iCnt)
; Debug "Cnt >"+iCnt+"< CharVal >"+aCharVal+"< - >"+Right("00000000"+Bin(aCharVal, #PB_Ascii),8)+"< Char>"+Chr(aCharVal)+"<"
; # utf8 characters can take 1-6 bytes, how much
; # exactly is decoded in the first character If
; # it has a character code >= 128 (highest bit set).
; # For all <= 127 the ASCII is the same As UTF8.
; # The number of bytes per character is stored in
; # the highest bits of the first byte of the UTF8
; # character. The bit pattern that must be matched
; # For the different length are shown As comment.
; #
; # So $n will hold the number of additonal characters
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
; Debug "error >"+iCnt+"< iLen >"+iLen+"< "
ProcedureReturn #False; # Does not match any model
EndIf
; # the code now checks the following additional bytes
; # First in the If checks that the byte is really inside the
; # string And running over the string End.
; # The second just check that the highest two bits of all
; # additonal bytes are always 1 And 0 (hexadecimal 0x80)
; # which is a requirement For all additional UTF-8 bytes
If iNext>0
; Debug "Next >"+iNext+"<"
iUtf8Detected=#True
For iCnt2=1 To iNext ;# n bytes matching 10bbbbbb follow ?
If iCnt+iCnt2>iLen Or (PeekA(*StrMem+iCnt+iCnt2) & $C0)<>$80
; Debug "false iCnt >"+iCnt+"< iCnt2>"+iCnt2+"< iLen >"+iLen+"< iNext >"+iNext+"< CHAR >"+PeekA(*StrMem+iCnt)
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
Debug ">>> _ZIP_DropDataFromFileHdl - FileHdl >"+iFileHdl+"< Offset >"+HexQ2(qOffset)+"< Size >"+HexQ2(qDataSize)+"<", 2
Repeat ; single repeat
If Not IsFile(iFileHdl)
Debug "ERROR! Invalid Handle", 0
iResult=#ZIP_ERR_INVALID
Break
EndIf
qReadOffset=qOffset+qDataSize
If qReadOffset>Lof(iFileHdl)
Debug "ERROR! Invalid ReadOffset >"+qReadOffset+"< (Offset >"+qOffset+"< Size >"+qDataSize+"<) - is beyond file boundaries >"+Lof(iFileHdl)+"<", 0
iResult=#ZIP_ERR_INVALID
Break
EndIf
*ChunkMem=AllocateMemory(qChunkSize)
If Not *ChunkMem
Debug "ERROR! Allocating memory", 0
iResult=#ZIP_ERR_ALLOC_MEM
Break
EndIf
qWriteOffset=qOffset
qProcessedData=0
Repeat
FileSeek(iFileHdl, qReadOffset+qProcessedData)
qTmp=ReadData(iFileHdl, *ChunkMem, qChunkSize)
If Not qTmp
Debug "ERROR! Read file error!", 0
iResult=#ZIP_ERR_READ_FILE
Break 2
EndIf
FileSeek(iFileHdl, qWriteOffset+qProcessedData)
If Not WriteData(iFileHdl, *ChunkMem, qTmp)
Debug "ERROR! Writing data failed!", 0
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)
Debug "_ZIP_InsertGapIntoFileHdl", 2
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)
Debug "ERROR! Invalid Handle", 0
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
Debug "ERROR! Allocating memory", 0
iResult=#ZIP_ERR_ALLOC_MEM
Break
Else
aMemAlloc=#True
EndIf
EndIf
qReadOffset=Lof(iFileHdl)
qReadSize=qChunkSize
; resize file to new destination-size (to circumvent 'cannot absolute seek beyond the end of the file' problem with fileseek)
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
Debug "ReadOffset >"+qReadOffset+"<", 4
FileSeek(iFileHdl, qReadOffset)
qTmp=ReadData(iFileHdl, *ChunkMem, qReadSize)
If Not qTmp
Debug "ERROR! Read file error!", 0
iResult=#ZIP_ERR_READ_FILE
Break 2
EndIf
FileSeek(iFileHdl, qReadOffset+qSize)
If Not WriteData(iFileHdl, *ChunkMem, qTmp)
Debug "ERROR! Writing data failed!", 0
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
Debug ">> _ZIP_CopyDataFromFile2FileHdl - SrcFile >"+sSrcFile+"< SrcOffset >"+Str(qSrcOffset)+"< SrcSize >"+Str(qSrcSize)+"< DstOffset >"+Str(qDstOffset)+"<", 2
Repeat ; single repeat
If qSrcSize<=0
Debug "ERROR! Invalid SrcSize! >"+qSrcSize+"< Filesize >"+FileSize(sSrcFile)+"<"
iResult=#ZIP_ERR_WRONG_PARAMETER
Break
EndIf
If Not IsFile(iDstFileHdl)
Debug "ERROR! Invalid Handle"
iResult=#ZIP_ERR_INVALID
Break
EndIf
iSrcFileHdl=ReadFile(#PB_Any, sSrcFile)
If Not iSrcFileHdl
Debug "ERROR! Couldn't read source file"
iResult=#ZIP_ERR_READ_FILE
Break
EndIf
*ChunkMem=AllocateMemory(qChunkSize)
If Not *ChunkMem
Debug "ERROR! Allocating memory"
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
; part 1 - move everything in dstfile behind DstOffset to the end (by qSrcSize)
iResult=_ZIP_InsertGapIntoFileHdl(iDstFileHdl, qDstOffset, qSrcSize, qChunkSize, *ChunkMem)
If iResult<>#ZIP_OK
Debug "ERROR! Couldn't insert gap"
Break
EndIf
Else
; the destination offset is outside the current file >> resize file to the new size
FileSeek(iDstFileHdl, 0)
FileSeek(iDstFileHdl, qDstOffset-1, #PB_Relative)
WriteByte(iDstFileHdl, $00)
EndIf
; now we have a 'gap' in the size of qSrcSize beginning at qDstOffset
; jetzt die daten chunk für chunk aus srcfile kopieren
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
Debug "ERROR! Read file error!"
iResult=#ZIP_ERR_READ_FILE
Break 2
EndIf
FileSeek(iDstFileHdl, qWriteOffset)
If Not WriteData(iDstFileHdl, *ChunkMem, qTmp)
Debug "ERROR! Writing data failed!"
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
Macro _ZIP_DEBUG_LocalFileHeader
; DisableDebugger
Debug ">>>> _ZIP_DEBUG_LocalFileHeader <<<<", 2
Debug "Signature HEX >"+Hex(ZIP_LFH\Signature, #PB_Long)+"<", 3
Debug "GeneralPurpose >"+ZIP_LFH\GeneralPurpose+"< HEX >"+RSet(Hex(ZIP_LFH\GeneralPurpose, #PB_Unicode), 4, "0")+"<", 3
Debug "VersionExtract >"+ZIP_LFH\VersionNeededToExtract+"< HEX >"+RSet(Hex(ZIP_LFH\VersionNeededToExtract, #PB_Unicode), 4, "0")+"<", 3
Debug "CompressionMethod >"+ZIP_LFH\CompressionMethod+"< HEX >"+RSet(Hex(ZIP_LFH\CompressionMethod, #PB_Unicode), 4, "0")+"<", 3
Debug "CRC32 >"+ZIP_LFH\CRC32+"< HEX >"+RSet(Hex(ZIP_LFH\CRC32, #PB_Long), 8, "0")+"<", 3
Debug "CompressedSize >"+ZIP_LFH\CompressedSize+"< HEX >"+RSet(Hex(ZIP_LFH\CompressedSize, #PB_Long), 8, "0")+"<", 3
Debug "UncompressedSize >"+ZIP_LFH\UncompressedSize+"< HEX >"+RSet(Hex(ZIP_LFH\UncompressedSize, #PB_Long), 8, "0")+"<", 3
Debug "FilenameLength >"+ZIP_LFH\FilenameLength+"< HEX >"+RSet(Hex(ZIP_LFH\FilenameLength, #PB_Unicode), 4, "0")+"<", 3
Debug "ExtraFieldLength >"+ZIP_LFH\ExtraFieldLength+"< HEX >"+RSet(Hex(ZIP_LFH\ExtraFieldLength, #PB_Unicode), 4, "0")+"<", 3
Debug "Filename >"+sFilenameLFH+"<"
Debug "-----------------------------------------------", 2
; EnableDebugger
EndMacro
Macro _ZIP_DEBUG_LocalFileDataDescriptor
Debug ">>>> _ZIP_DEBUG_LocalFileDataDescriptor <<<<", 2
Debug "CRC32 >"+ZIP_LFDD\CRC32+"< HEX >"+RSet(Hex(ZIP_LFDD\CRC32, #PB_Long), 8, "0")+"<", 3
Debug "CompressedSize >"+ZIP_LFDD\CompressedSize+"< HEX >"+RSet(Hex(ZIP_LFDD\CompressedSize, #PB_Long), 8, "0")+"<", 3
Debug "UncompressedSize >"+ZIP_LFDD\UncompressedSize+"< HEX >"+RSet(Hex(ZIP_LFDD\UncompressedSize, #PB_Long), 8, "0")+"<", 3
Debug "-----------------------------------------------"
EndMacro
Macro _ZIP_DEBUG_CentralDirectoryHeader
; DisableDebugger
Debug ">>>> _ZIP_DEBUG_CentralDirectoryHeader <<<<", 2
Debug "Signature HEX >"+Hex(ZIP_CDH\Signature, #PB_Long)+"<", 3
Debug "VersionMadeBy >"+ZIP_CDH\VersionMadeBy+"< HEX >"+RSet(Hex(ZIP_CDH\VersionMadeBy, #PB_Unicode), 4, "0")+"<", 3
Debug "VersionForExtract >"+ZIP_CDH\VersionNeededToExtract+"< HEX >"+RSet(Hex(ZIP_CDH\VersionNeededToExtract, #PB_Unicode), 4, "0")+"<", 3
Debug "GeneralPurpose >"+ZIP_CDH\GeneralPurpose+"< HEX >"+RSet(Hex(ZIP_CDH\GeneralPurpose, #PB_Unicode), 4, "0")+"<", 3
Debug "CompressionMethod >"+ZIP_CDH\CompressionMethod+"< HEX >"+RSet(Hex(ZIP_CDH\CompressionMethod, #PB_Unicode), 4, "0")+"<", 3
Debug "CRC32 >"+ZIP_CDH\CRC32+"< HEX >"+RSet(Hex(ZIP_CDH\CRC32, #PB_Long), 8, "0")+"<", 3
Debug "CompressedSize >"+ZIP_CDH\CompressedSize+"< HEX >"+RSet(Hex(ZIP_CDH\CompressedSize, #PB_Long), 8, "0")+"<", 3
Debug "UncompressedSize >"+ZIP_CDH\UncompressedSize+"< HEX >"+RSet(Hex(ZIP_CDH\UncompressedSize, #PB_Long), 8, "0")+"<", 3
Debug "FilenameLength >"+ZIP_CDH\FilenameLength+"< HEX >"+RSet(Hex(ZIP_CDH\FilenameLength, #PB_Unicode), 4, "0")+"<", 3
Debug "ExtraFieldLength >"+ZIP_CDH\ExtraFieldLength+"< HEX >"+RSet(Hex(ZIP_CDH\ExtraFieldLength, #PB_Unicode), 4, "0")+"<", 3
Debug "FileCommentLength >"+ZIP_CDH\FileCommentLength+"< HEX >"+RSet(Hex(ZIP_CDH\FileCommentLength, #PB_Unicode), 4, "0")+"<", 3
Debug "DiskNumberStart >"+ZIP_CDH\DiskNumberStart+"< HEX >"+RSet(Hex(ZIP_CDH\DiskNumberStart, #PB_Unicode), 4, "0")+"<", 3
Debug "InternalFileAttrib>"+ZIP_CDH\InternalFileAttributes+"< HEX >"+RSet(Hex(ZIP_CDH\InternalFileAttributes, #PB_Unicode), 4, "0")+"<", 3
Debug "ExternalFileAttrib>"+ZIP_CDH\ExternalFileAttributes+"< HEX >"+RSet(Hex(ZIP_CDH\ExternalFileAttributes, #PB_Long), 8, "0")+"<", 3
Debug "OffsetOfLocalHeadr>"+ZIP_CDH\OffsetOfLocalHeader+"< HEX >"+RSet(Hex(ZIP_CDH\OffsetOfLocalHeader, #PB_Unicode), 8, "0")+"<", 3
Debug "Filename >"+sFilenameCDH+"<", 3
Debug "-----------------------------------------------", 3
; EnableDebugger
EndMacro
Macro _ZIP_DEBUG_EndOfCentralDirectory
; DisableDebugger
Debug ">>>> _ZIP_DEBUG_EndOfCentralDirectory <<<<", 2
Debug "EndOfCentralDirectory-Offset >"+Str(EndOfCentralDirectory_Offset)+"< HEX >"+RSet(Hex(EndOfCentralDirectory_Offset, #PB_Quad), 16, "0")+"<", 3
Debug "Signature HEX >"+RSet(Hex(ZIP_EOCDR\Signature, #PB_Long), 8)+"<" , 3
Debug "CommentLength >"+ZIP_EOCDR\CommentLength+"< HEX >"+RSet(Hex(ZIP_EOCDR\CommentLength, #PB_Unicode), 4, "0")+"<", 3
Debug "NumberOfDisk >"+ZIP_EOCDR\NumberOfDisk+"< HEX >"+RSet(Hex(ZIP_EOCDR\NumberOfDisk, #PB_Unicode), 4, "0")+"<", 3
Debug "NumberOfStart >"+ZIP_EOCDR\NumberCDStart+"< HEX >"+RSet(Hex(ZIP_EOCDR\NumberCDStart, #PB_Unicode), 4, "0")+"<" , 3
Debug "NumberOfEntries >"+ZIP_EOCDR\NumberOfEntries+"< HEX >"+RSet(Hex(ZIP_EOCDR\NumberOfEntries, #PB_Unicode), 4, "0")+"<" , 3
Debug "NumberOfTotalEntries >"+ZIP_EOCDR\NumberOfTotalEntries+"< HEX >"+RSet(Hex(ZIP_EOCDR\NumberOfTotalEntries, #PB_Unicode), 4, "0")+"<" , 3
Debug "CentralDirSize >"+ZIP_EOCDR\CentralDirSize+"< HEX >"+RSet(Hex(ZIP_EOCDR\CentralDirSize, #PB_Long), 8, "0")+"<" , 3
Debug "CentralDirStartOffset >"+ZIP_EOCDR\CentralDirStartOffset+"< HEX >"+RSet(Hex(ZIP_EOCDR\CentralDirStartOffset, #PB_Long), 8, "0")+"<", 3
Debug "-----------------------------------------------", 3
; EnableDebugger
EndMacro
Procedure.s _ZIP_GetFilename(iFileHdl.i, qOffset.q, iFilenameLen.i, iGeneralPurpose.i, *Error.Integer)
Debug "_ZIP_GetFilename", 2
Protected *FilenameMem
Protected sFilename.s=""
; EnableDebugger
;
; If (iGeneralPurpose & $800)
; Debug "GeneralPurpose BIT11 set - UTF8 encoded filename and comment" ; If this bit is set, the filename and comment fields For this file MUST be encoded using UTF-8. (see APPENDIX D)
; EndIf
;
; DisableDebugger
*FilenameMem=AllocateMemory(iFilenameLen+2)
If *FilenameMem
FileSeek(iFileHdl, qOffset)
If Not ReadData(iFileHdl, *FilenameMem, iFilenameLen)
Debug "ERROR! Reading file"
*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
Debug "ERROR! Allocating memory - Filename"
*Error\i=#ZIP_ERR_ALLOC_MEM
EndIf
Debug "GetFilename-Result >"+sFilename+"<", 3
ProcedureReturn sFilename
EndProcedure
Procedure.i _ZIP_VerifySignature(iFileHdl.i, qOffset.q, lSignature.l)
Debug "_ZIP_VerifySignature", 2
Protected lFileSignature.l
Protected iResult.i
FileSeek(iFileHdl, qOffset)
If Not ReadData(iFileHdl, @lFileSignature, 4)
Debug "ERROR! Reading file"
iResult=#ZIP_ERR_READ_FILE
Else
; Debug "Signature >"+RSet(Hex(lSignature, #PB_Long), 8, "0")+"< File-Signature >"+RSet(Hex(lFileSignature, #PB_Long), 8, "0")+"<"
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)
Debug "_ZIP_ReadDataDescriptor", 2
; problem ist - die Signatur für den DataDescriptor muss nicht zwangsweise vorhanden sein (wurde nachträglich in Spec eingefügt)
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!
; after a DataDescriptor there can only be 4 different Signatures: [local file header n], [archive decryption header], [archive extra Data record], [central directory header 1]
If Not aDataDescriptorFound
qNewOffset=FindHexInFileHdl(iFileHdl, "504B0304", qOffset) ; search for another LocalFileHeader
If qNewOffset
Debug "Signature of LocalFileHeader found!", 3
qNewOffset-SizeOf(ZIP_DataDescriptor)
aDataDescriptorFound=#True
EndIf
EndIf
If Not aDataDescriptorFound
qNewOffset=FindHexInFileHdl(iFileHdl, "504B0608", qOffset) ; search for an ArchiveDecryptionHeader
If qNewOffset
Debug "Signature of ArchiveDecryptionHeader found!",3
qNewOffset-SizeOf(ZIP_DataDescriptor)
aDataDescriptorFound=#True
EndIf
EndIf
If Not aDataDescriptorFound
qNewOffset=FindHexInFileHdl(iFileHdl, "504B0102", qOffset) ; search for an CentralDirectoryHeader
If qNewOffset
Debug "Signature of CentralDirectoryHeader found!",3
qNewOffset-SizeOf(ZIP_DataDescriptor)
aDataDescriptorFound=#True
EndIf
EndIf
; read DataDescriptorValues
If aDataDescriptorFound
FileSeek(iFileHdl, qNewOffset)
If Not ReadData(iFileHdl, *DataDescriptor, SizeOf(ZIP_DataDescriptor))
Debug "ERROR! Reading file"
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