ZIP module - remove and add files from/to existing archives

Share your advanced PureBasic knowledge/code with the community.
nalor
Enthusiast
Enthusiast
Posts: 115
Joined: Thu Apr 02, 2009 9:48 pm

ZIP module - remove and add files from/to existing archives

Post by nalor »

Hello!

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)
I started to create a replacement using the original zip specification 'appnote' from pkware to restore the lost functionality and work around the drawbacks.

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
It's not optimised for speed, but fast enough for my purposes. The only thing still missing is ZIP64 support - but I don't need it and don't have plans to implement it (but it returns at least an error in case a ZIP64 archive is detected).

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

Last edited by nalor on Sun Jul 03, 2016 3:22 pm, edited 4 times in total.
nalor
Enthusiast
Enthusiast
Posts: 115
Joined: Thu Apr 02, 2009 9:48 pm

Re: ZIP module - remove and add file operations on archives

Post by nalor »

Code PART 2:

Code: Select all

Procedure _ZIP_Common(sZipFile.s, iProcessMode.i, sFileToProcess.s="", iCaseSensitiv.i=#False, *EntryList.ZipEntryData=0, *MoveEntryData.ZIP_MoveEntryData=0, iPreFileHdl.i=-1)
	
; 	Debug "_ZIP_Common", 2
	
	; History:
	; 20160629 .. nalor .. FEATURE .. modification of Generalpurpose BIT11 - UTF8-Filename possible with mode '#ZIP_Mode_ModifyHeader'
	
	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
	
	; RemoveFileVariables
	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

	; ExtractFileVariables
	Protected *CompressedMemory
	Protected *UncompressedMemory
	
	; AddFile
	Protected DstFileEntry.ZIP_MoveEntryData
	
	; ModifyHeader
	Protected iUnixTime.i
	Protected iUtf8Filename.i
	
	Debug "************* _ZIP_Common ************** Mode >"+iProcessMode+"< ZipFile >"+sZipfile+"< FileToProcess >"+sFileToProcess+"< PreFileHdl >"+iPreFileHdl+"<", 2
	
	If iProcessMode=#ZIP_Mode_ExamineFile And *EntryList=0
		Debug "ERROR! No EntryList Parameter!"
		iResult=#ZIP_ERR_WRONG_PARAMETER
	EndIf
	
	If iProcessMode=#ZIP_Mode_GetEntryData And *MoveEntryData=0
		Debug "ERROR! No MoveEntryData Parameter!"
		iResult=#ZIP_ERR_WRONG_PARAMETER
	EndIf
	
	If iProcessMode=#ZIP_Mode_CopyEntry_File2File And (sFileToProcess="" Or *MoveEntryData=0)
		Debug "ERROR! No FileToProcess / MoveEntryData Parameter!"
		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
			Debug "_ZIP_Common - Open file",2
			iFileHdl=ReadFile(#PB_Any, sZipFile, #PB_File_SharedRead)
			
			If iFileHdl=0
				Debug "ERROR! Reading file >"+sZipfile+"<"
				iResult=#ZIP_ERR_READ_FILE
			EndIf
		Else
			Debug "PreFileHdl ist gesetzt! >"+iPreFileHdl+"<"
			If IsFile(iPreFileHdl)
				iFileHdl=iPreFileHdl
			Else
				Debug "ERROR! PreFileHdl is not valid!!"
				iResult=#ZIP_ERR_READ_FILE
			EndIf
			
		EndIf
	EndIf
	
	Debug "FileHdl in Common >"+iFileHdl+"<"
	
	If iResult=#ZIP_OK
		EndOfCentralDirectory_Offset=FindHexInFileHdl(iFileHdl, "504B0506", 0, #False) ; find 'end of central dir signature'
		If EndOfCentralDirectory_Offset<0
			Debug "ERROR! No EndOfDirectory signature found - not a zip file?"
			iResult=#ZIP_ERR_INVALID
		EndIf
	EndIf
	
	If iResult=#ZIP_OK
		FileSeek(iFileHdl, EndOfCentralDirectory_Offset)
		If Not ReadData(iFileHdl, @ZIP_EOCDR, SizeOf(ZIP_EndOfCentralDirectoryRecordV2))
			Debug "ERROR! Reading file EOCD >"+sZipfile+"<"
			iResult=#ZIP_ERR_READ_FILE
		EndIf
	EndIf
	
	If iResult=#ZIP_OK
		_ZIP_DEBUG_EndOfCentralDirectory
		If ZIP_EOCDR\NumberOfDisk>0 Or ZIP_EOCDR\NumberOfEntries<>ZIP_EOCDR\NumberOfTotalEntries
			Debug "ERROR! Currently only ZIP archives with 1 disk are supported!"
			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

; 			Debug ">>>> ZIP_CDH"
			If _ZIP_VerifySignature(iFileHdl, qCurrentOffset, #ZIP_Signature_CDH)<#ZIP_OK ; verify CentralDirectoryHeader-Signature
				Debug "ERROR! Wrong signature detected! (CDH) 1 >"+HexQ2(qCurrentOffset)+"<"
				iResult=#ZIP_ERR_SIGNATURE
				Break
			EndIf
			
			FileSeek(iFileHdl, qCurrentOffset)
			If Not ReadData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
				Debug "ERROR! Reading file CDH >"+sZipfile+"<"
				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
				Debug "ERROR! Getting Filename failed >"+ZIP_GetErrorText(iResult)+"<", 3
				Break
			EndIf
			If ZIP_CDH\CompressedSize=-1 Or ZIP_CDH\UncompressedSize=-1 Or ZIP_CDH\CRC32=-1
				Debug "ERROR! ZIP64 File detected - is not supported!"
				iResult=#ZIP_ERR_ZIP64
				Break
			EndIf
			_ZIP_DEBUG_CentralDirectoryHeader
			
			iDataDescriptorSize=0
			
; 			Debug "Common - Entry CDH >"+sFilenameCDH+"< ToProcess >"+sFileToProcess+"<"
			
			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
					Debug "ERROR! Couldn't add list element!"
					iResult=#ZIP_ERR_ADDELEMENT
					Break
				EndIf
				
			; all the other ProcessModes need additional data from the LocalFileHeader - but only in case the filename matches	
			ElseIf ((iCaseSensitiv=#True And sFilenameCDH=sFileToProcess) Or (iCaseSensitiv=#False And LCase(sFilenameCDH)=LCase(sFileToProcess)))  And iProcessMode<>#ZIP_Mode_CopyEntry_File2File
				
				Debug "Do IT - FILE FOUND!"
				iFileFound=#True
				
				If _ZIP_VerifySignature(iFileHdl, ZIP_CDH\OffsetOfLocalHeader, #ZIP_Signature_LFH)<#ZIP_OK ; verify LocalFileHeader-Signature
					Debug "ERROR! Wrong signature detected! (LFH)"
					iResult=#ZIP_ERR_SIGNATURE
					Break
				EndIf
				
				FileSeek(iFileHdl, ZIP_CDH\OffsetOfLocalHeader)
				If Not ReadData(iFileHdl, @ZIP_LFH, SizeOf(ZIP_LocalFileHeaderV2))
					Debug "ERROR! Reading file LFH >"+sZipfile+"<"
					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
					Debug "ERROR! Getting Filename failed >"+ZIP_GetErrorText(iResult)+"<", 3
					Break
				EndIf
				
				If ZIP_LFH\CompressedSize=-1 Or ZIP_LFH\UncompressedSize=-1 Or ZIP_LFH\CRC32=-1
					Debug "ERROR! ZIP64 File detected - is not supported!"
					iResult=#ZIP_ERR_ZIP64
					Break
				EndIf
				_ZIP_DEBUG_LocalFileHeader
				
				If (ZIP_LFH\GeneralPurpose & $8)
					Debug "GeneralPurpose BIT03 set - DataDescriptor used!" ; See APPNOTE 6.3.4 section 4.3.9 For details" ; 0x08074b50 - BIT00 is LSB (so BIT03 is the 4th bit)
					
					iResult=_ZIP_ReadDataDescriptor(iFileHdl, ZIP_CDH\OffsetOfLocalHeader+SizeOf(ZIP_LocalFileHeaderV2)+ZIP_LFH\FilenameLength+ZIP_LFH\ExtraFieldLength, @ZIP_LFDD, @iDataDescriptorSize)
					
					If iResult>0
						_ZIP_DEBUG_LocalFileDataDescriptor
						
						Debug "move Data to LocalFileHeader structure"
						ZIP_LFH\CRC32=ZIP_LFDD\CRC32
						ZIP_LFH\CompressedSize=ZIP_LFDD\CompressedSize
						ZIP_LFH\UncompressedSize=ZIP_LFDD\UncompressedSize
					Else
						Debug "ERROR! DataDescriptorError!"
						Break
					EndIf
					
				EndIf
				
; 				If (ZIP_LFH\GeneralPurpose & $800)
; 					Debug "GeneralPurpose BIT11 set - UTF8 encoded filename and comment (used in _ZIP_GetFilename)" ; If this bit is set, the filename And comment fields For this file MUST be encoded using UTF-8. (see APPENDIX D)
; 				EndIf
				
				If sFilenameCDH<>sFilenameLFH
					Debug "ERROR! Different Filenames detected! CDH >"+sFilenameCDH+"< LFH >"+sFilenameLFH+"<"
					iResult=#ZIP_ERR_INVALID
					Break
				EndIf
				
				If iProcessMode=#ZIP_Mode_GetEntryData
					Debug "Assign EntryData"
					*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
					
					Debug "CDH_Offset >"+*MoveEntryData\CDH_Offset, 2
					Debug "CDH_Size   >"+*MoveEntryData\CDH_Size, 2
					Debug "LFH_Offset >"+*MoveEntryData\LFH_Offset, 2
					Debug "LFH_Size   >"+*MoveEntryData\LFH_Size, 2
					
					Break
				EndIf
				
				If iProcessMode=#ZIP_Mode_ModifyHeader
					If iUnixTime<>-1
						Debug "Mode ModifyHeader\ChangeDateTime - current Time >"+FormatDate("%dd.%mm.%yyyy - %hh:%ii:%ss", DosToUnixTime(ZIP_LFH\LastModFileDate, ZIP_LFH\LastModFileTime))+"<"
						
	 					UnixToDosTime(iUnixTime, @ZIP_LFH\LastModFileDate, @ZIP_LFH\LastModFileTime)
	 					
						Debug "Mode ModifyHeader\ChangeDateTime - NEW Time >"+FormatDate("%dd.%mm.%yyyy - %hh:%ii:%ss", DosToUnixTime(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
							Debug "set BIT11 to 1"
							
						ElseIf iUtf8Filename=0 ; in case filename is ASCII only (set BIT11 to 0)
							ZIP_LFH\GeneralPurpose=ZIP_LFH\GeneralPurpose & ~$800
							Debug "set BIT11 to 0"
						EndIf
						
						ZIP_CDH\GeneralPurpose=ZIP_LFH\GeneralPurpose
					EndIf
					
					If iPreFileHdl=-1 ; only in case we opened the file ourselves
						; now we need write access to the file
						CloseFile(iFileHdl)
						iFileHdl=OpenFile(#PB_Any, sZipFile, #PB_File_SharedRead)
						If iFileHdl=0
							Debug "ERROR! opening file >"+sZipfile+"< for write access"
							iResult=#ZIP_ERR_READ_FILE
							Break
						EndIf
					EndIf
					
					; Write changed DATE/TIME/GPURP to LFH
					FileSeek(iFileHdl, ZIP_CDH\OffsetOfLocalHeader)
					If Not WriteData(iFileHdl, @ZIP_LFH, SizeOf(ZIP_LocalFileHeaderV2))
						Debug "ERROR! Writing file >"+sZipfile+"<"
						iResult=#ZIP_ERR_WRITE_FILE
						Break
					EndIf
					
					; Write changed DATE/TIME/GPURP to CDH
					FileSeek(iFileHdl, qCurrentOffset)
					If Not WriteData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
						Debug "ERROR! Writing corrected CentralDirectoryHeader failed! >"+sZipfile+"<"
						iResult=#ZIP_ERR_WRITE_FILE
						Break
					EndIf
					
					Break
					
				EndIf
				
			Else
 				Debug "CurrentFile >"+sFilenameCDH+"<  FileToProcess >"+sFileToProcess+"< Offset >"+HexQ2(qCurrentOffset)+"<",3
			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
			Debug "ERROR! file not found in ZIP FILE >"+sFileToProcess+"<", 3
			iResult=#ZIP_ERR_ENTRY_NOT_FOUND
		EndIf
		
	EndIf
	
	If iResult=#ZIP_OK And iProcessMode=#ZIP_Mode_CopyEntry_File2File
		; step 1 - we need additional data about the latest entry in the zip
		
		Debug "################# CopyEntry-File2File-Section ##########################"
		
		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
				Debug "ERROR! couldn't get data for latest entry >"+sFilenameCDH+"<"
				Break
			EndIf
			
			If iPreFileHdl=-1 ; only in case we opened the file ourselves
				; now we need write access to the file
				CloseFile(iFileHdl)
				iFileHdl=OpenFile(#PB_Any, sZipFile, #PB_File_SharedRead)
				If iFileHdl=0
					Debug "ERROR! opening file >"+sZipfile+"< for write access"
					iResult=#ZIP_ERR_READ_FILE
					Break
				EndIf
			EndIf
			
			; ############ COPY COMPRESSED DATA (LFH) FROM SRC #####################
			
			Debug "CDH_Offset >"+*MoveEntryData\CDH_Offset, 2
			Debug "CDH_Size   >"+*MoveEntryData\CDH_Size, 2
			Debug "LFH_Offset >"+*MoveEntryData\LFH_Offset, 2
			Debug "LFH_Size   >"+*MoveEntryData\LFH_Size, 2
			
			iResult=_ZIP_CopyDataFromFile2FileHdl(sFileToProcess, *MoveEntryData\LFH_Offset, *MoveEntryData\LFH_Size, iFileHdl, DstFileEntry\LFH_Offset+DstFileEntry\LFH_Size)
			If iResult<>#ZIP_OK
				Debug "ERROR! couldn't move data from file >"+sFileToProcess+"< to >"+sZipfile+"< a"
				Break
			EndIf
			
			; ############ COPY DIRECTORY ENTRY (CDH) FROM SRC #####################
			qCurrentOffset=ZIP_EOCDR\CentralDirStartOffset+*MoveEntryData\LFH_Size
			
			If _ZIP_VerifySignature(iFileHdl, qCurrentOffset, #ZIP_Signature_CDH)<#ZIP_OK ; verify CentralDirectoryHeader-Signature
				Debug "ERROR! Wrong signature detected! (CDH) 65"
				iResult=#ZIP_ERR_SIGNATURE
				Break
			EndIf
		
			qCurrentOffset+ZIP_EOCDR\CentralDirSize ; add the current CentralDirSize to the currentPosition
			
			; now we copy the CDH entry from the src-file to the dst-file
			iResult=_ZIP_CopyDataFromFile2FileHdl(sFileToProcess, *MoveEntryData\CDH_Offset, *MoveEntryData\CDH_Size, iFileHdl, qCurrentOffset)
			If iResult<>#ZIP_OK
				Debug "ERROR! couldn't move data from file >"+sFileToProcess+"< to >"+sZipfile+"< b"
				Break
			EndIf
			
			; ############ MODIFY OFFSET in DIRECTORY ENTRY (CDH) #####################
				
			FileSeek(iFileHdl, qCurrentOffset)
			If Not ReadData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
				Debug "ERROR! Reading file CDH Move >"+sZipfile+"<"
				iResult=#ZIP_ERR_READ_FILE
				Break
			EndIf	
				
			ZIP_CDH\OffsetOfLocalHeader=DstFileEntry\LFH_Offset+DstFileEntry\LFH_Size
			
			Debug "New offset for data >"+HexQ2(ZIP_CDH\OffsetOfLocalHeader)+"< lfh offset >"+hexq2(DstFileEntry\LFH_Offset)+"< lfh size >"+hexq2(DstFileEntry\LFH_Size)+"<"
			
			FileSeek(iFileHdl, qCurrentOffset)
			If Not WriteData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
				Debug "ERROR! Writing corrected CentralDirectoryHeader failed! >"+sZipfile+"<"
				iResult=#ZIP_ERR_WRITE_FILE
				Break
			EndIf
			
			; ############ MODIFY END-OF-CENTRAL-DIRECTORY-RECORD #####################
			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
			
			Debug "new CDH offset >"+HexQ2(ZIP_EOCDR\CentralDirStartOffset)+"<"
			
			Debug "NEW EndOfCentralDirectory:"
			_ZIP_DEBUG_EndOfCentralDirectory
			
			If _ZIP_VerifySignature(iFileHdl, EndOfCentralDirectory_Offset, #ZIP_Signature_EOCDR)<#ZIP_OK ; verify EndOfCentralDirectory-Signature
				Debug "ERROR! Wrong signature detected! (EOCD)"
				iResult=#ZIP_ERR_SIGNATURE
				Break
			EndIf
			
			FileSeek(iFileHdl, EndOfCentralDirectory_Offset)
			If Not WriteData(iFileHdl, @ZIP_EOCDR, SizeOf(ZIP_EndOfCentralDirectoryRecordV2))
				Debug "ERROR! Writing of corrected EndOfCentralDirectory failed!"
				iResult=#ZIP_ERR_WRITE_FILE
				Break
			EndIf
			
			If _ZIP_VerifySignature(iFileHdl, EndOfCentralDirectory_Offset, #ZIP_Signature_EOCDR)<#ZIP_OK ; verify EndOfCentralDirectory-Signature
				Debug "ERROR! Wrong signature detected! (EOCD)"
				iResult=#ZIP_ERR_SIGNATURE
				Break
			EndIf
			
		Until #True

	EndIf
	
	If iResult=#ZIP_OK And iProcessMode=#ZIP_Mode_RemoveFile
		; step 1 - we need additional data about the entry-to-be-removed in the zip
		
		Debug "################# Remove-File-Section ##########################"
		
		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
				Debug "ERROR! couldn't get data for to-be-removed entry >"+sFileToProcess+"<"
				Break
			EndIf
			
			; now we need write access to the file
			If iPreFileHdl=-1 ; only in case we opened the file ourselves
				CloseFile(iFileHdl)
				iFileHdl=OpenFile(#PB_Any, sZipFile, #PB_File_SharedRead)
				If iFileHdl=0
					Debug "ERROR! opening file >"+sZipfile+"< for write access"
					iResult=#ZIP_ERR_READ_FILE
					Break
				EndIf
			EndIf
			
			; ############ DELETE DIRECTORY ENTRY ##################### (this is the first step because this way round the 'CDH_Offset' is still valid)
			iResult=_ZIP_DropDataFromFileHdl(iFileHdl, DstFileEntry\CDH_Offset, DstFileEntry\CDH_Size)
			If iResult<>#ZIP_OK
				Debug "ERROR! Deleting CentralDirectoryHeader failed!"
				iResult=#ZIP_ERR_DELETE_DATA
				Break
			EndIf
			
			; ############ DELETE COMPRESSED DATA #####################
			iResult=_ZIP_DropDataFromFileHdl(iFileHdl, DstFileEntry\LFH_Offset, DstFileEntry\LFH_Size)
			If iResult<>#ZIP_OK
				Debug "ERROR! Deleting LocalFileData failed!"
				iResult=#ZIP_ERR_DELETE_DATA
				Break
			EndIf
			
			; ############ MODIFY DIRECTORY HEADER #####################
			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
			
			Debug "NEW EndOfCentralDirectory:"
			_ZIP_DEBUG_EndOfCentralDirectory
			
			If _ZIP_VerifySignature(iFileHdl, EndOfCentralDirectory_Offset, #ZIP_Signature_EOCDR)<#ZIP_OK ; verify EndOfCentralDirectory-Signature
				Debug "ERROR! Wrong signature detected! (EOCD)"
				iResult=#ZIP_ERR_SIGNATURE
				Break
			EndIf
			
			FileSeek(iFileHdl, EndOfCentralDirectory_Offset)
			If Not WriteData(iFileHdl, @ZIP_EOCDR, SizeOf(ZIP_EndOfCentralDirectoryRecordV2))
				Debug "ERROR! Writing of corrected EndOfCentralDirectory failed!"
				iResult=#ZIP_ERR_WRITE_FILE
				Break
			EndIf
			
			; ############ MODIFY OFFSET ENTRIES in CDH'S #####################
			qCurrentOffset=ZIP_EOCDR\CentralDirStartOffset
			iFileCnt=0
			Repeat
	
; 				Debug ">>>> ZIP_CDH"
				
				FileSeek(iFileHdl, qCurrentOffset)
				If Not ReadData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
					Debug "ERROR! Reading file CDH Remove >"+sZipfile+"<"
					iResult=#ZIP_ERR_READ_FILE
					Break
				EndIf	
				
				If ZIP_CDH\OffsetOfLocalHeader>DstFileEntry\LFH_Offset
; 					Debug "entry has offset after the file that has been removed - correct the offset"
					ZIP_CDH\OffsetOfLocalHeader-DstFileEntry\LFH_Size
					
					FileSeek(iFileHdl, qCurrentOffset)
					If Not WriteData(iFileHdl, @ZIP_CDH, SizeOf(ZIP_CentralDirectoryHeaderV2))
						Debug "ERROR! Writing corrected CentralDirectoryHeader failed! >"+sZipfile+"<"
						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 ZIP_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 ZIP_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 ZIP_AddFile(sZipfile.s, sFileToAdd.s, sZippedFileName.s)
	
	; History
	; 20160629 .. nalor .. now the UTF8 bit is set in case the filename requires utf8 support (windows zip engine relies on this BIT)
	
	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)
				Debug "ERROR! Couldn't add file to zip!"
				iResult=#ZIP_ERR_ADDELEMENT
				ClosePack(iZipHdl)
				Break
			EndIf
			ClosePack(iZipHdl)
		Else
			Debug "ERROR! Couldn't create zip file >"+sZipTempFile+"<"
			iResult=#ZIP_ERR_WRITE_FILE
			Break
		EndIf
		
		
		If StringUtf8Required(sZippedFileName)
			If Not ZIP_ModifyHeader(sZipTempFile, sZippedFileName, -1, #True)
				Debug "ERROR! Couldn't modify header (FileDateTime / UTF8) in temp zip file >"+sZipTempFile+"<"
				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
				Debug "Error getting EntryData for >"+sZipTempFile+"< File >"+sZippedFileName+"<"
				Break
			EndIf
			
			Debug "EntryData >"+SrcItemData\LFH_Offset+"< >"+SrcItemData\LFH_Size+"< - Filedetails >"+sZipTempFile+"< File >"+sZippedFileName+"<"
			iResult=_ZIP_Common(sZipfile, #ZIP_Mode_CopyEntry_File2File, sZipTempFile, 0, 0, @SrcItemData)
			If iResult<0
				Debug "Error copying data file2file for >"+sZipFile+"< >"+sZipTempFile+"<"
				Break
			EndIf
			
			If Not DeleteFile(sZipTempFile)
				Debug "ERROR! Couldn't delete temp file >"+sZipTempFile+"<"
			EndIf
		EndIf
		
	Until #True
	
	ProcedureReturn iResult
	
EndProcedure

Procedure ZIP_AddFileFromMem(sZipfile.s, *SrcMem, iSrcSize.i, sZippedFileName.s, iPreFileHdl.i=-1, lDateTime.l=-1)
	
	; History:
	; 20160306 .. nalor .. added parameter 'iPreFileHdl' to prevent repeated file open/close actions
	; 20160501 .. nalor .. added parameter 'lDateTime' and option to modify the filedate/time of the added file
	; 20160629 .. nalor .. now the UTF8 bit is set in case the filename requires utf8 support (windows zip engine relies on this BIT)
	
	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)
				Debug "ERROR! Couldn't add memory to zip! >"+sZipTempFile+"<"
				iResult=#ZIP_ERR_ADDELEMENT
				ClosePack(iZipHdl)
				Break
			EndIf
			ClosePack(iZipHdl)
		Else
			Debug "ERROR! Couldn't create zip file >"+sZipTempFile+"<"
			iResult=#ZIP_ERR_WRITE_FILE
			Break
		EndIf
		
		If Not ZIP_ModifyHeader(sZipTempFile, sZippedFileName, lDateTime, StringUtf8Required(sZippedFileName) )
			Debug "ERROR! Couldn't modify header (FileDateTime / UTF8) in temp zip file >"+sZipTempFile+"<"
			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
				Debug "Error getting EntryData for >"+sZipTempFile+"< File >"+sZippedFileName+"<"
				Break
			EndIf
			
			Debug "EntryData >"+SrcItemData\LFH_Offset+"< >"+SrcItemData\LFH_Size+"<"
			iResult=_ZIP_Common(sZipfile, #ZIP_Mode_CopyEntry_File2File, sZipTempFile, 0, 0, @SrcItemData, iPreFileHdl)
			If iResult<0
				Debug "Error copying data file2file for >"+sZipFile+"< >"+sZipTempFile+"<"
				Break
			EndIf
			
			If Not DeleteFile(sZipTempFile)
				Debug "ERROR! Couldn't delete temp file >"+sZipTempFile+"<"
			EndIf
		EndIf
		
	Until #True
	
	ProcedureReturn iResult
	
EndProcedure

Procedure.i ZIP_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())
		Debug "ERROR! CopyList failed!"
		iResult=#ZIP_ERR_UNSPEC_ERROR
	EndIf
	
	ProcedureReturn iResult
	
EndProcedure

EnableDebugger

EndModule



;- ## 3-EXAMPLE SECTION


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

;- main


Define iResult.i
Define sZipfile.s
Define sTmpFile.s
Define *SrcMem
Define NewList ZipList.ZIP::ZIP_EntryData()
Define iCnt.i
Define sRandom.s
Define iFileHdl.i
Define iStartime.i
Define iEndtime.i


sZipfile=GetTemporaryDirectory()+"ZipFile.zip"
sTmpFile=GetTemporaryDirectory()+"FileFromFilesystem.dat"

Debug "ZipFile >"+sZipfile+"<"
Debug "TmpFile >"+sTmpFile+"<"

*SrcMem=AllocateMemory(100000, #PB_Memory_NoClear)
Mem_Dump2File(*SrcMem, sTmpFile)

Debug ~"\n####################################"
Debug "Example 1 - add files from mem"

iResult=ZIP::ZIP_AddFileFromMem(sZipfile, *SrcMem, MemorySize(*SrcMem), "SubDirMem\FileFromMemWithSubdir.dat")
If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf

iResult=ZIP::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::ZIP_GetErrorText(iResult) : EndIf

iResult=ZIP::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::ZIP_GetErrorText(iResult) : EndIf

Debug ~"\n####################################"
Debug "Example 2 - add files from filesystem"

iResult=ZIP::ZIP_AddFile(sZipfile, sTmpFile, "SubDirFS\"+GetFilePart(sTmpFile))
If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf

iResult=ZIP::ZIP_AddFile(sZipfile, sTmpFile, "FileFromFilesystemWithoutSubdir.dat")
If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf

iResult=ZIP::ZIP_AddFile(sZipfile, sTmpFile, "FileFromFilesystemWithUTF8CharŪƝƗƇƠƉĖ.dat")
If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf


Debug ~"\n####################################"
Debug "Example 3 - show details of files in ZIP file"

iResult=ZIP::ZIP_Examine(sZipfile, ZipList())
If iResult<>ZIP::#ZIP_OK : Debug ZIP::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 ~"\n####################################"
Debug "Example 4 - Delete files from ZIP"

iResult=ZIP::ZIP_RemoveFile(sZipfile, "SubDirMem\FileFromMemWithSubdir.dat")
If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf

iResult=ZIP::ZIP_RemoveFile(sZipfile, "FileFromMemWithoutSubdir.dat")
If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf

iResult=ZIP::ZIP_RemoveFile(sZipfile, "FileFromMemWithUTF8CharŪƝƗƇƠƉĖ.dat")
If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf

Debug ~"\n####################################"
Debug "Example 5 - Modify date of existing entry"

iResult=ZIP::ZIP_ChangeFileDateTime(sZipfile, "SubDirFS\"+GetFilePart(sTmpFile), Date(2020,03,05,08,22,33))
If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf

iResult=ZIP::ZIP_ChangeFileDateTime(sZipfile, "FileFromFilesystemWithoutSubdir.dat", Date(2021,03,05,08,22,33))
If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf

iResult=ZIP::ZIP_ChangeFileDateTime(sZipfile, "FileFromFilesystemWithUTF8CharŪƝƗƇƠƉĖ.dat", Date(2022,03,05,08,22,33))
If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf


Debug ~"\n####################################"
Debug "show details of files in ZIP file again"
iResult=ZIP::ZIP_Examine(sZipfile, ZipList())
If iResult<>ZIP::#ZIP_OK : Debug ZIP::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 ~"\n####################################"
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)
; on my Transcend 128GB USB 3.1 Stick and my NAS it's about 2 times faster with PreFileHdl

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"
 		Debug "add File >"+iCnt+"< >"+sRandom+"<"
		
		iResult=ZIP::ZIP_AddFileFromMem(sZipfile, *SrcMem, 1000, sRandom, iFileHdl)
		If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf
	Next
	
	For iCnt=100 To 200 Step 1
		sRandom=RSet(Str(iCnt),3,"0")+"-mem.txt"
 		Debug "add File >"+iCnt+"< >"+sRandom+"<"
		
		iResult=ZIP::ZIP_RemoveFile(sZipfile, sRandom, #False, iFileHdl)
		If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf
	Next
	CloseFile(iFileHdl)
EndIf
iEndtime=ElapsedMilliseconds()
EnableDebugger
Debug "Duration >"+FormatTime(iEndtime-iStartime)+"<"

Debug ~"\n####################################"
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"
 		Debug "add File >"+iCnt+"< >"+sRandom+"<"
	
	iResult=ZIP::ZIP_AddFileFromMem(sZipfile, *SrcMem, 1000, sRandom)
	If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf
Next
For iCnt=300 To 400 Step 1
	sRandom=RSet(Str(iCnt),3,"0")+"-mem.txt"
 		Debug "add File >"+iCnt+"< >"+sRandom+"<"
	
	iResult=ZIP::ZIP_RemoveFile(sZipfile, sRandom)
	If iResult<>ZIP::#ZIP_OK : Debug ZIP::ZIP_GetErrorText(iResult) : EndIf
Next

iEndtime=ElapsedMilliseconds()
EnableDebugger
Debug "Duration >"+FormatTime(iEndtime-iStartime)+"<"

Debug ~"\n####################################"
Debug "show details of files in ZIP file again"
iResult=ZIP::ZIP_Examine(sZipfile, ZipList())
If iResult<>ZIP::#ZIP_OK : Debug ZIP::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 ~"\n####################################"
Debug "delete temp files"

DeleteFile(sZipfile)
DeleteFile(sTmpFile)

yrreti
Enthusiast
Enthusiast
Posts: 546
Joined: Tue Oct 31, 2006 4:34 am

Re: ZIP module - remove and add files from/to existing archi

Post by yrreti »

Wow nalor !
I will admit that I don't have as much time lately as I used too, to be able to try out your code
in projects. But just looking at your code, I can certainly appreciate all your 'HARD' work and research
that you did, and your knowledge in writing this code. It was very kind of you to share it with others.
Thank you very much.

yrreti
IdeasVacuum
Always Here
Always Here
Posts: 6425
Joined: Fri Oct 23, 2009 2:33 am
Location: Wales, UK
Contact:

Re: ZIP module - remove and add files from/to existing archi

Post by IdeasVacuum »

Excellent module nalor - no doubt you have a few more grey hairs now :shock:

Given that your module is better than the lib that Fred has used, I wonder if it could be incorporated into PB?
IdeasVacuum
If it sounds simple, you have not grasped the complexity.
nalor
Enthusiast
Enthusiast
Posts: 115
Joined: Thu Apr 02, 2009 9:48 pm

Re: ZIP module - remove and add files from/to existing archi

Post by nalor »

IdeasVacuum wrote: Given that your module is better than the lib that Fred has used, I wonder if it could be incorporated into PB?
My module still needs the library from Purebasic - when I noticed that it's not possible any longer to add new files to existing zip archives, I enhanced my remove-files-from-zip-module a little bit to also allow this - but I still use the included library for the compression (I didn't create a inflate/deflate procedure).

Adding a file to an existing zip archive is done in the following way:
# add the src-file to a new temporary zip file (with the internal pb functions)
# copy the compressed-data-block from the temp-file to the existing destination zip file and take care of the zip structure...

So it cannot be used as a replacement of the internal module, but only an extension.
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: ZIP module - remove and add files from/to existing archi

Post by davido »

@nalor,

Very nice .
Thank you for sharing. :D
DE AA EB
GoodNPlenty
Enthusiast
Enthusiast
Posts: 107
Joined: Wed May 13, 2009 8:38 am
Location: Arizona, USA

Re: ZIP module - remove and add files from/to existing archi

Post by GoodNPlenty »

Great Work, Thank You for Sharing :D
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5342
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: ZIP module - remove and add files from/to existing archi

Post by Kwai chang caine »

Very useful
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
User avatar
Thorsten1867
Addict
Addict
Posts: 1366
Joined: Wed Aug 24, 2005 4:02 pm
Location: Germany

Re: ZIP module - remove and add files from/to existing archi

Post by Thorsten1867 »

It is possible to add password support for zip archives?
I miss this for a long time.
Translated with http://www.DeepL.com/Translator

Download of PureBasic - Modules
Download of PureBasic - Programs

[Windows 11 x64] [PB V5.7x]
Post Reply