Reading ZIP and DOCX files and unpacking image files in memory
I wrote a program that scans the contents of ZIP, DOCX, XLSX, and EXE files, reads the entire file structure inside archives and without temporary files, unpacks and displays images in memory.
It even works with all nested subfolders. I improved the code from The trick.
Requirement: olelib.tlb or oleexp.tlb library.
This illustrative example shows how easy it is to unpack any ZIP file and read its contents, as well as SFX EXE files and DOCX files. Works in WinXP+
Form code:
Code:
Option Explicit
Private Declare Function SHParseDisplayName Lib "shell32" (ByVal pszName As Long, ByVal IBindCtx As Long, ByRef ppidl As Long, sfgaoIn As Long, sfgaoOut As Long) As Long
Private Declare Function ILFree Lib "shell32" (ByVal pidlFree As Long) As Long
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Const ZipFldrCLSID = "{E88DCCE0-B7B3-11d1-A9F0-00AA0060FA31}"
Private Const IID_IShellExtInit = "{000214E8-0000-0000-C000-000000000046}"
Private Sub ReadingStructureZIP(ByVal FileName As String)
Dim clsid As UUID
Dim iidSh As UUID
Dim shExt As IShellExtInit
Dim pf As IPersistFolder2
Dim pidl As Long
CLSIDFromString ZipFldrCLSID, clsid
CLSIDFromString IID_IShellExtInit, iidSh
If CoCreateInstance(clsid, Nothing, CLSCTX_INPROC_SERVER, iidSh, shExt) <> S_OK Then Exit Sub
Set pf = shExt
SHParseDisplayName StrPtr(FileName), 0, pidl, 0, 0
pf.Initialize pidl
ILFree pidl
IStorageRead pf
End Sub
Private Sub IStorageRead(IStorageInZIP As IStorage, Optional PathFolder As String)
Dim enm As IEnumSTATSTG
Dim itm As STATSTG
Dim nam As String
Dim cb As Long
Set enm = IStorageInZIP.EnumElements
enm.Reset
Do While enm.Next(1, itm) = S_OK
nam = SysAllocString(itm.pwcsName)
CoTaskMemFree itm.pwcsName
If itm.Type = STGTY_STREAM Then
List1.AddItem IIf(PathFolder <> "", PathFolder & "\" & nam, nam) & IIf(itm.cbSize, " " & itm.cbSize * 10000@ & " bytes", "")
ElseIf itm.Type = STGTY_STORAGE Then
IStorageRead IStorageInZIP.OpenStorage(nam, 0, STGM_READ, 0, 0), IIf(PathFolder <> "", PathFolder & "\" & nam, nam)
End If
Loop
End Sub
Private Sub LoadFileFromZIP(ByVal FileName As String, ByVal FileNameInZIP As String)
Dim clsid As UUID
Dim iidSh As UUID
Dim shExt As IShellExtInit
Dim pf As IPersistFolder2
Dim pidl As Long
CLSIDFromString ZipFldrCLSID, clsid
CLSIDFromString IID_IShellExtInit, iidSh
If CoCreateInstance(clsid, Nothing, CLSCTX_INPROC_SERVER, iidSh, shExt) <> S_OK Then Exit Sub
Set pf = shExt
SHParseDisplayName StrPtr(FileName), 0, pidl, 0, 0
pf.Initialize pidl
ILFree pidl
LoadPictureFileFromZIP pf, FileNameInZIP
End Sub
Private Sub LoadPictureFileFromZIP(IStorageInZIP As IStorage, ByVal FileNameInZIP As String, Optional PathFolder As String)
Dim enm As IEnumSTATSTG
Dim itm As STATSTG
Dim nam As String
Dim cb As Long
Dim FileName As String
Dim stm As IStream
Set enm = IStorageInZIP.EnumElements
enm.Reset
Do While enm.Next(1, itm) = S_OK
nam = SysAllocString(itm.pwcsName)
CoTaskMemFree itm.pwcsName
FileName = IIf(PathFolder <> "", PathFolder & "\" & nam, nam)
If itm.Type = STGTY_STREAM Then
If FileName = FileNameInZIP Then
Set stm = IStorageInZIP.OpenStream(nam, 0, STGM_READ, 0)
If itm.cbSize Then Picture1.Picture = LoadPictureFromStream(stm)
Set stm = Nothing
End If
ElseIf itm.Type = STGTY_STORAGE Then
LoadPictureFileFromZIP IStorageInZIP.OpenStorage(nam, 0, STGM_READ, 0, 0), FileNameInZIP, FileName
End If
Loop
End Sub
Private Sub Form_Load()
Text1.Text = App.Path & "\test.docx"
ReadingStructureZIP Text1.Text
If List1.ListCount > 0 Then
List1.Selected(0) = True
Label1.Caption = "Count files: " & List1.ListCount
End If
End Sub
Private Sub List1_Click()
Dim tick As Long
tick = GetTickCount
LoadFileFromZIP Text1.Text, Mid$(List1.Text, 1, InStr(1, List1.Text, " ") - 1)
Label2.Caption = "ms: " & (GetTickCount - tick)
End Sub
Private Sub Text1_KeyPress(KeyAscii As Integer)
If KeyAscii = 13 Then
KeyAscii = 0
List1.Clear
ReadingStructureZIP Text1.Text
If List1.ListCount > 0 Then
List1.Selected(0) = True
Label1.Caption = "Count files: " & List1.ListCount
List1.SetFocus
End If
End If
End Sub
Module code (loading images from memory):
Code:
Option Explicit
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Private Type PicBmp
size As Long
Type As Long
hBmp As Long
hPal As Long
Reserved As Long
End Type
Private Type GdiplusStartupInput
GdiplusVersion As Long
DebugEventCallback As Long
SuppressBackgroundThread As Long
SuppressExternalCodecs As Long
End Type
Private Declare Function GdipCreateBitmapFromStream Lib "gdiplus" (ByVal Stream As IUnknown, ByRef hBitmap As Long) As Long
Private Declare Function GdipCreateHBITMAPFromBitmap Lib "gdiplus" (ByVal BITMAP As Long, hbmReturn As Long, ByVal background As Long) As Long
Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (PicDesc As PicBmp, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
Private Declare Function GdiplusStartup Lib "gdiplus" (Token As Long, inputbuf As GdiplusStartupInput, Optional ByVal outputbuf As Long = 0) As Long
Private Declare Function GdiplusShutdown Lib "gdiplus" (ByVal Token As Long) As Long
Private Declare Function GdipDisposeImage Lib "gdiplus" (ByVal Image As Long) As Long
Public Function LoadPictureFromStream(FileStream As IUnknown, Optional BackColor As Long = vbWhite) As StdPicture
Dim Pic As PicBmp
Dim IPic As IPicture
Dim IID_IDispatch As GUID
Dim SI As GdiplusStartupInput
Dim Token As Long
Dim bmp As Long
Dim hBmp As Long
SI.GdiplusVersion = 1
If GdiplusStartup(Token, SI) Then Exit Function
If GdipCreateBitmapFromStream(FileStream, bmp) = 0 Then
If GdipCreateHBITMAPFromBitmap(bmp, hBmp, BackColor) = 0 Then
GdipDisposeImage (bmp)
End If
GdiplusShutdown Token
Else
GdiplusShutdown Token
End If
With IID_IDispatch
.Data1 = &H20400
.Data4(0) = &HC0
.Data4(7) = &H46
End With
With Pic
.size = Len(Pic) ' Length of structure.
.Type = vbPicTypeBitmap ' Type of Picture (bitmap).
.hBmp = hBmp ' Handle to bitmap.
.hPal = 0
End With
OleCreatePictureIndirect Pic, IID_IDispatch, 1, IPic
Set LoadPictureFromStream = IPic
End Function
Last edited by HackerVlad; Feb 24th, 2025 at 07:13 PM.
Re: Reading ZIP and DOCX files and unpacking image files in memory
In order for the project to work in TwinBasic, simply check the box in the plug-in libraries at the "[IMPORTED] OLE Guid and interface definitions for 32-bit and 64-bit" OLEGuids position.
Re: Reading ZIP and DOCX files and unpacking image files in memory
It turns out that the code for unpacking files from zipfldr.dll creates temporary files... Therefore, the information in the program titled out to be incorrect... But I didn't know that before...
Re: Reading ZIP and DOCX files and unpacking image files in memory
Here is cZipArchive reimplementation of the same:
Code:
Option Explicit
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Declare Function SHCreateMemStream Lib "shlwapi" Alias "#12" (pInit As Any, ByVal cbInit As Long) As stdole.IUnknown
Private Sub ReadingStructureZIP(ByVal FileName As String)
Dim lIdx As Long
With New cZipArchive
If Not .OpenArchive(FileName) Then
Exit Sub
End If
For lIdx = 0 To .FileCount - 1
List1.AddItem .FileInfo(lIdx, zipIdxFileName) & vbTab & .FileInfo(lIdx, zipIdxSize) & " bytes"
Next
End With
End Sub
Private Sub LoadFileFromZIP(ByVal FileName As String, ByVal FileNameInZIP As String)
Dim baExtracted() As Byte
With New cZipArchive
If Not .OpenArchive(FileName) Then
Exit Sub
End If
If Not .Extract(baExtracted, FileNameInZIP) Then
Exit Sub
End If
End With
Set Picture1.Picture = LoadPictureFromStream(SHCreateMemStream(baExtracted(0), UBound(baExtracted) + 1))
End Sub
Private Sub Form_Load()
Text1.Text = App.Path & "\test.docx"
ReadingStructureZIP Text1.Text
If List1.ListCount > 0 Then
List1.Selected(0) = True
Label1.Caption = "Count files: " & List1.ListCount
End If
End Sub
Private Sub List1_Click()
Dim tick As Long
tick = GetTickCount
LoadFileFromZIP Text1.Text, Mid$(List1.Text, 1, InStr(1, List1.Text, vbTab) - 1)
Label2.Caption = "ms: " & (GetTickCount - tick)
End Sub
Private Sub Text1_KeyPress(KeyAscii As Integer)
If KeyAscii = 13 Then
KeyAscii = 0
List1.Clear
ReadingStructureZIP Text1.Text
If List1.ListCount > 0 Then
List1.Selected(0) = True
Label1.Caption = "Count files: " & List1.ListCount
List1.SetFocus
End If
End If
End Sub
Re: Reading ZIP and DOCX files and unpacking image files in memory
I didn't use your class for only one reason - I didn't like the size of the output EXE file at 126 KB, which is a lot, and therefore I didn't use your class. However, in this example of yours, the EXE size is only 28 KB, how did you manage it?
Re: Reading ZIP and DOCX files and unpacking image files in memory
Dear wqweto,
Probably a 28 KB file, it was my file. Your compiled code cannot take up less than 100 KB. And I dealt with it thoroughly today. 100 KB is very annoying. This is a lot, especially when you want to write a small SFX module on VB.
I understood why the EXE takes up so much space in your case. You saved the subroutine in Base64 inside the class itself. Moreover, you didn't even pack it with anything. I think we can reduce the size of the EXE if we at least pack this subroutine with at least the simplest data packing algorithm.
Of course, the fact that Vb6 itself, when it creates an EXE, stores strings only in Unicode, which doubles the size of the EXE when they contain large strings, adds to the troubles. It's not profitable, of course.
That's why I started working with other algorithms, immediately putting your class aside. 100 KB is a lot, of course.
1. Well, maybe for someone to use at home, for people who are sure that these are their home files, without viruses.
2. The SFX self-extracting archive technology is useful to you if you want to write your own installer.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Originally Posted by HackerVlad
Dear wqweto,
Probably a 28 KB file, it was my file. Your compiled code cannot take up less than 100 KB. And I dealt with it thoroughly today. 100 KB is very annoying. This is a lot, especially when you want to write a small SFX module on VB.
I understood why the EXE takes up so much space in your case. You saved the subroutine in Base64 inside the class itself. Moreover, you didn't even pack it with anything. I think we can reduce the size of the EXE if we at least pack this subroutine with at least the simplest data packing algorithm.
Of course, the fact that Vb6 itself, when it creates an EXE, stores strings only in Unicode, which doubles the size of the EXE when they contain large strings, adds to the troubles. It's not profitable, of course.
That's why I started working with other algorithms, immediately putting your class aside. 100 KB is a lot, of course.
Thanks for your attention
Btw, in latest commit 183c16b ASM thunk size in output binary is reduced by 28KB est. by comparing before and after size of compiled vbzip.exe.
Have not tested exactly how much in KBs an amalgamation of the cZipArchive for *decompression only* does weight in final binary.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Originally Posted by wqweto
Btw, in latest commit 183c16b ASM thunk size in output binary is reduced by 28KB est. by comparing before and after size of compiled vbzip.exe.
Have not tested exactly how much in KBs an amalgamation of the cZipArchive for *decompression only* does weight in final binary.
cheers,
</wqw>
This is a good technology to reduce the size of the EXE.
I conducted an experiment, saved this subroutine buffer to a file on disk, resulting in a 7624 byte file, and decided to load this buffer directly from the file, while reducing the size of the output EXE by as much as 40 KB!
But in fact, it can be further improved if this buffer is compressed, then it will no longer be 7 KB, but 5 KB or 4 Kb in total.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Originally Posted by wqweto
Have not tested exactly how much in KBs an amalgamation of the cZipArchive for *decompression only* does weight in final binary.
I managed to cut it down to 46 KB (P-code) 80 KB Native-code. This is a record. However, the buffer will be loaded from a separate file (you can shove it into resources).
With very great difficulty, with a lot of cutting out everything superfluous, it is possible to achieve an EXE size of about 50 KB (P-code).
Last edited by HackerVlad; Feb 27th, 2025 at 06:43 AM.
Re: Reading ZIP and DOCX files and unpacking image files in memory
> But in fact, it can be further improved if this buffer is compressed, then it will no longer be 7 KB, but 5 KB or 4 Kb in total.
Btw, currently it's not 7KB in the final exe as VB6 does not allow storing binary data in literals (unlike string literals). Using code-as-data technique it (probably) currently occupies about 9-10KB in final exe in form of push 0x12345678 instructions i.e. 1 byte opcode overhead on each 4 bytes of data + some additional overhead for the call instruction after each 32 push-es.
Compression to be useful has to be implemented in less than 2-3KB of code, provided that using compression thunk can be reduces to 4-5KB so it must be some OS built-in compressor then.
Re: Reading ZIP and DOCX files and unpacking image files in memory
> Yes, of course, but then it will only work starting from XP (I haven't checked, but suddenly your class works on systems up to XP)
It was using CryptStringToBinary API call to decode base64 thunk which is supported on XP and above only though currently I'm still not sure (have not tested) if latest build works on any OS before XP.
Re: Reading ZIP and DOCX files and unpacking image files in memory
So, if you use at least the simplest buffer packing functions RtlCompressBuffer/RtlDecompressBuffer (or even better CreateDeltaB/ApplyDeltaB), then you can imagine how many more kilobytes you can win.
Re: Reading ZIP and DOCX files and unpacking image files in memory
msdelta.dll is first used in Vista+ to decompress Windows Updates while there is something called The Microsoft Compression Client Pack 1.0 for Microsoft Windows XP which ships it on XP
On the other hand 7624 - 4733 = 2891 which frankly I'm not sure is worth the effort to research compression for less than 3KB footprint. Better to spend time testing it on Win2000/NT4 and fix incompatibilities if any.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Originally Posted by wqweto
msdelta.dll is first used in Vista+ to decompress Windows Updates while there is something called The Microsoft Compression Client Pack 1.0 for Microsoft Windows XP which ships it on XP
On the other hand 7624 - 4733 = 2891 which frankly I'm not sure is worth the effort to research compression for less than 3KB footprint. Better to spend time testing it on Win2000/NT4 and fix incompatibilities if any.
cheers,
</wqw>
No, you're wrong, the documentation is lying. This feature works in XP. Considering that you are from Bulgaria, you should understand a little Russian. Read it here: https://www.manhunter.ru/assembler/1...a_funkciy.html
Moreover, I personally checked it works in XP, and other people there write in the comments that it also works for them in XP.
Don't you understand that the gain is not 2891 bytes, but much more, considering that the description in the code of this jumper buffer will be almost halved. The payoff will be decent, I'm sure it's more than 10 KB.
Re: Reading ZIP and DOCX files and unpacking image files in memory
I just checked that it doesn't work in WinME. I've already dealt with WinME compatibility in my CAB reader recently. The most famous problem is that all W-functions will fail, you need to check for each such failure and redirect to A-functions. It's still a bit of a hassle, of course
Of course, the very first thing that won't work is the CreateFileW function, since only the CreateFileA function will work in WinME.
Re: Reading ZIP and DOCX files and unpacking image files in memory
> Don't you understand that the gain is not 2891 bytes, but much more, considering that the description in the code of this jumper buffer will be almost halved. The payoff will be decent, I'm sure it's more than 10 KB.
Not sure what halving you are talking about when 2891 is not half of 7624 but feel free to test it yourself and gain all those payoffs in your self-extractor.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Originally Posted by wqweto
> Don't you understand that the gain is not 2891 bytes, but much more, considering that the description in the code of this jumper buffer will be almost halved. The payoff will be decent, I'm sure it's more than 10 KB.
Not sure what halving you are talking about when 2891 is not half of 7624 but feel free to test it yourself and gain all those payoffs in your self-extractor.
cheers,
</wqw>
You've come up with a good idea to store a buffer in the code. But if you use Currency variables for concatenation instead of Long, then you can still win!
Re: Reading ZIP and DOCX files and unpacking image files in memory
> You've come up with a good idea to store a buffer in the code. But if you use Currency variables for concatenation instead of Long, then you can still win!
Re: Reading ZIP and DOCX files and unpacking image files in memory
Not much, of course, but you can win. I'm aiming for maximum gain.
But most importantly, I suggest you consider the idea of packing and unpacking the buffer for your new version to save about 10 KB more.
Re: Reading ZIP and DOCX files and unpacking image files in memory
> Not much, of course, but you can win.
Unfortunately this is not the case here. Calling pvAppendBuffer 1234567890.1234@ with currency literals generates two consecutive push instructions for each Currency (each 8 bytes) like this:
This means you gain nothing because 2 opcodes are used for 8 bytes of data so just using two Longs will generate the same assembly i.e. 2 opcodes per 8 bytes of data.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Maybe you can compress the byte code by first using delta encoding on the byte array and then use RunLength Encoding.
Both algorithms can be quite small and save some space on the internal data blob
Re: Reading ZIP and DOCX files and unpacking image files in memory
maybe pack your exe will be best.
Shell tools can make your program smaller. For example, libxldll has 8MB, but after adding the shell, it only has 2MB
Re: Reading ZIP and DOCX files and unpacking image files in memory
Dear wqweto,
What is archive ZIP unpacking in Windows? Unpacking is the creation of files, setting the date and time and file ATTRIBUTES. All algorithms in the Windows operating system work there. But for some reason your class doesn't set file attributes, which deeply shocked me...
Re: Reading ZIP and DOCX files and unpacking image files in memory
It sets archive attribute ) But attribs are not universal i.e. you cannot ZIP on Linux and restore the same attribs on extract on Windows.
I’m undecided on whether attempting restoring attribs is right thing to do on extract. Have to research behavior of popular compression utilities for consensus.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Originally Posted by wqweto
It sets archive attribute ) But attribs are not universal i.e. you cannot ZIP on Linux and restore the same attribs on extract on Windows.
I’m undecided on whether attempting restoring attribs is right thing to do on extract. Have to research behavior of popular compression utilities for consensus.
I don't understand why you need to research something if extracting files with a simple Windows Explorer sets attributes - your class doesn't set attributes. As a result, Windows users will be offended that you didn't take care of setting file attributes when extracting from ZIP.
Re: Reading ZIP and DOCX files and unpacking image files in memory
I assure you, all normal programs for extracting files from ZIP set attributes to the extracted files, but for some reason you did not want to do this.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Just a small note, Microsoft says ILFree shouldn't be used, use CoTaskMemFree instead.
Note When using Windows 2000 or later, use CoTaskMemFree rather than ILFree. ITEMIDLIST structures are always allocated with the Component Object Model (COM) task allocator on those platforms.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Originally Posted by HackerVlad
I don't understand why you need to research something if extracting files with a simple Windows Explorer sets attributes - your class doesn't set attributes. As a result, Windows users will be offended that you didn't take care of setting file attributes when extracting from ZIP.
I don't understand why keep being offended by source code provided by others.
The source provided by wqweto is as it is, if you need more functionality then currently provided then add the functionality yourself.
You can also ask wqweto whether he wants to update his source with your improved functionality.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Originally Posted by HackerVlad
I assure you, all normal programs for extracting files from ZIP set attributes to the extracted files, but for some reason you did not want to do this.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Yes, I understand that wqweto does not owe anyone anything and its software is free and such as it is. Excuse me. I'm really sorry if I offended anyone.
Re: Reading ZIP and DOCX files and unpacking image files in memory
Dear Vladimir Vissoultchev,
Thank you so much for adding this wonderful feature of setting file attributes to your class. I noticed it, even though you didn't say anything! I'm sure this is the right decision. I'm sorry again if it seemed like I was picking on you. I just wanted the best.
I'm currently working on my own mini-class for zip reading. I will have an auto-detection of file encoding, so that if the zip file was compressed in Mac OS, where there is no mark in the file that the encoding is UTF8, then I will still detect it automatically. But the auto-detection works for me only starting from Win7.