Results 1 to 28 of 28

Thread: A module for reading CAB archives

  1. #1

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    A module for reading CAB archives

    I finally wrote a module for reading CAB archives today. With this module, you can read the list of files inside the CAB archives and which file takes up how many bytes. I did not add the date and time of the files, since I did not need it, but it is very easy to do it myself by slightly redesigning the module and the custom UDT array to get the file list data.

    This is a very simple implementation, I tried to make as few lines of code as possible so that there would be. In fact, unpacking a CAB archive is generally done with just one line of code. To do this, you need to access the undocumented, but very useful, ExtractFiles function from the library advpack.dll which is available in all Windows.

    Now you know how to get a list of files from the CAB archive and how to unpack the CAB archive with the program-code on VB6. However, the packaging feature is not implemented here, since this is a very complex code using the FCICreate function from the library cabinet.dll I was looking for a ready-made version of such code on the Internet, but I didn't find it for vb6, so I haven't written the packaging yet.

    Last update: 2025-02-08, posted version 2.1

    New module code
    Attached Images Attached Images  
    Attached Files Attached Files
    Last edited by HackerVlad; Feb 8th, 2025 at 05:23 PM.

  2. #2

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    Yes, and I almost forgot the code of the module itself here:

    Code:
    Option Explicit
    '////////////////////////////////////////////
    '// A module for reading CAB archives      //
    '// Copyright (c) 11.11.2024 by HackerVlad //
    '// e-mail: vladislavpeshkov@yandex.ru     //
    '// Version 1.0                            //
    '////////////////////////////////////////////
    
    Private Declare Function SetupIterateCabinet Lib "setupapi" Alias "SetupIterateCabinetA" (ByVal CabinetFile As String, ByVal Reserved As Long, ByVal MsgHandler As Long, ByVal Context As Long) As Long
    Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenA" (ByVal lpString As Long) As Long
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, ByVal Source As Long, ByVal Length As Long)
    
    Private Const MAXPATH = 260
    Private Const NO_ERROR = 0
    '
    ' Notification messages, handled in the callback
    ' procedure. This class doesn't handle them all.
    '
    Private Const SPFILENOTIFY_FILEINCABINET = &H11
    Private Const SPFILENOTIFY_NEEDNEWCABINET = &H12
    Private Const SPFILENOTIFY_FILEEXTRACTED = &H13
    
    Private Const sicList = 568
    Private Const sicCount = 569
    
    Dim mstrFileToExtract As String
    Dim mstrOutputPath As String
    Dim mstrOutputFile As String
    Dim mlngCount As Long
    Dim mlngcnt As Long
    Dim arrListFilesCab() As CabInfo
    
    Public Type CabInfo
        cabFileName As String
        cabFileSize As Long
    End Type
    
    Private Type FileInCabinetInfo
        NameInCabinet As Long
        FileSize      As Long
        Win32Error    As Long
        DosDate       As Integer
        DosTime       As Integer
        DosAttribs    As Integer
        FullTargetName(0 To MAXPATH - 1) As Byte
    End Type
    
    Private Enum FILEOP
        FILEOP_ABORT = 0
        FILEOP_DOIT = 1
        FILEOP_SKIP = 2
    End Enum
    
    Private Function CabinetCallback(ByVal Context As Long, ByVal Notification As Long, ByRef Param1 As FileInCabinetInfo, ByVal Param2 As Long) As Long
        Select Case Notification
            Case SPFILENOTIFY_NEEDNEWCABINET
                CabinetCallback = NO_ERROR
                
            Case SPFILENOTIFY_FILEINCABINET
                Select Case Context
                    Case sicCount
                        mlngCount = mlngCount + 1
                        CabinetCallback = FILEOP_SKIP
                        
                   Case sicList
                        ' Add a position to the UDT array
                        ReDim Preserve arrListFilesCab(mlngcnt)
                        arrListFilesCab(mlngcnt).cabFileName = fStringFromPointer(Param1.NameInCabinet)
                        arrListFilesCab(mlngcnt).cabFileSize = Param1.FileSize
                        mlngcnt = mlngcnt + 1
                        
                        CabinetCallback = FILEOP_SKIP ' Go through the list of files further
                End Select
        End Select
    End Function
    
    Private Function fStringFromPointer(ByVal ptr As Long) As String
        Dim lngLen   As Long
        Dim strBuffer As String
        '
        ' Given a string pointer, copy the value
        ' of the string into a new, safe location.
        '
        lngLen = lstrlen(ptr)
        strBuffer = Space$(lngLen)
        
        CopyMemory ByVal strBuffer, ptr, lngLen
        fStringFromPointer = strBuffer
    End Function
    
    ' Get a list of files inside the CAB archive
    Public Function GetFilesListInCab(ByVal FileName As String, arrCabInfo() As CabInfo) As Long
        mlngcnt = 0
        
        If SetupIterateCabinet(FileName, 0, AddressOf CabinetCallback, sicList) Then
            arrCabInfo = arrListFilesCab
            GetFilesListInCab = mlngcnt
            
            Erase arrListFilesCab
        End If
    End Function
    
    ' Find out the count of files inside the CAB archive
    Public Function GetFilesCountInCab(ByVal FileName As String) As Long
        mlngCount = 0
        
        If SetupIterateCabinet(FileName, 0, AddressOf CabinetCallback, sicCount) Then
            GetFilesCountInCab = mlngCount
        End If
    End Function

  3. #3

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    Today, testuser2 has slightly modified this module to support Unicode and 64-bit platforms. However, in order for this code to work in VB6, you will have to delete all the words "PtrSafe".

    Code:
    Option Explicit
    '////////////////////////////////////////////
    '// ?????? ??? ?????? CAB-???????          //
    '// Copyright (c) 11.11.2024 by HackerVlad //
    '// e-mail: vladislavpeshkov@yandex.ru     //
    '// Testuser2 ??????? ????????? x64        //
    '// ?????? 1.1                             //
    '////////////////////////////////////////////
    Private Declare PtrSafe Function SetupIterateCabinet Lib "setupapi" Alias "SetupIterateCabinetW" ( _
                          ByVal CabinetFile As LongPtr, _
                          ByVal Reserved As Long, _
                          ByVal MsgHandler As LongPtr, _
                          ByVal Context As LongPtr) As Long
    'WINSETUPAPI BOOL SetupIterateCabinetW(
    '  [in] PCWSTR              CabinetFile,
    '  [in] DWORD               Reserved,
    '  [in] PSP_FILE_CALLBACK_W MsgHandler,
    '  [in] PVOID               Context
    ');
    Private Declare PtrSafe Function lstrlenW Lib "kernel32" (ByVal lpString As LongPtr) As Long
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
     
    Private Const MAXPATH = 260
    Private Const NO_ERROR = 0
    '
    ' Notification messages, handled in the callback
    ' procedure. This class doesn't handle them all.
    '
    Private Const SPFILENOTIFY_FILEINCABINET = &H11
    Private Const SPFILENOTIFY_NEEDNEWCABINET = &H12
    Private Const SPFILENOTIFY_FILEEXTRACTED = &H13
     
    Private Const sicList = 568
    Private Const sicCount = 569
    Private Type CabInfo
        cabFileName As String
        cabFileSize As Long
    End Type
    Private Type FileInCabinetInfo          '_FILE_IN_CABINET_INFO_W
        NameInCabinet As LongPtr                  '  PCWSTR
        FileSize      As Long                     '  DWORD
        Win32Error    As Long                     '  DWORD
        DosDate       As Integer                  '  WORD
        DosTime       As Integer                  '  WORD
        DosAttribs    As Integer                  '  WORD
        FullTargetName(MAXPATH - 1) As Byte       '  WCHAR
    End Type
    Private Enum FILEOP
        FILEOP_ABORT = 0
        FILEOP_DOIT = 1
        FILEOP_SKIP = 2
    End Enum
    Private mstrFileToExtract As String
    Private mstrOutputPath As String
    Private mstrOutputFile As String
    Private mlngCount As Long
    Private arrListFilesCab() As CabInfo
    'UINT PspFileCallbackW(PVOID Context, UINT Notification, UINT_PTR Param1, UINT_PTR Param2)
    Private Function CabinetCallback(ByVal Context As LongPtr, ByVal Notification As Long, ByRef Param1 As FileInCabinetInfo, ByVal Param2 As LongPtr) As Long
        Select Case Notification
        Case SPFILENOTIFY_NEEDNEWCABINET
            CabinetCallback = NO_ERROR
            
        Case SPFILENOTIFY_FILEINCABINET
            Select Case Context
            Case sicCount
                mlngCount = mlngCount + 1
                CabinetCallback = FILEOP_SKIP
                    
            Case sicList
                ' ???????? ??????? ? ?????? UDT
                ReDim Preserve arrListFilesCab(mlngCount)
                arrListFilesCab(mlngCount).cabFileName = fStringFromPointer(Param1.NameInCabinet)
                arrListFilesCab(mlngCount).cabFileSize = Param1.FileSize
                mlngCount = mlngCount + 1
                
                CabinetCallback = FILEOP_SKIP ' ?????????? ?????? ?????? ??????
            End Select
        End Select
    End Function
     
    Private Function fStringFromPointer(ByVal ptr As LongPtr) As String
        Dim lngLen As Long
        
        ' Given a string pointer, copy the value of the string into a new, safe location.
        lngLen = lstrlenW(ptr)
        fStringFromPointer = Space$(lngLen)
        
        CopyMemory ByVal StrPtr(fStringFromPointer), ByVal ptr, lngLen * 2
    End Function
     
    ' ???????? ?????? ?????? ?????? ?????? CAB
    Public Function GetFilesListInCab(ByRef FileName As String, arrCabInfo() As CabInfo) As Long
        mlngCount = 0
        
        If SetupIterateCabinet(StrPtr(FileName), 0, AddressOf CabinetCallback, sicList) Then
            arrCabInfo = arrListFilesCab
            GetFilesListInCab = mlngCount
            
            Erase arrListFilesCab
        End If
    End Function
     
    ' ?????? ?????????? ?????? ?????? ?????? CAB
    Public Function GetFilesCountInCab(ByRef FileName As String) As Long
        mlngCount = 0
        
        If SetupIterateCabinet(StrPtr(FileName), 0&, AddressOf CabinetCallback, sicCount) Then
            GetFilesCountInCab = mlngCount
        End If
    End Function

  4. #4
    Fanatic Member
    Join Date
    Jun 2016
    Location
    España
    Posts
    572

    Re: A module for reading CAB archives

    avast antivirus detect

  5. #5

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    So what!? And where do you see the virus here?

  6. #6
    Fanatic Member
    Join Date
    Jun 2016
    Location
    España
    Posts
    572

    Re: A module for reading CAB archives

    I don't see it, but antiviruses do.
    People aren't going to use something like that.

  7. #7

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    What nonsense is this!? You can see the full source code in front of your eyes, which is 100% free of any virus! Is your antivirus blatantly lying to you and do you believe it? This is a false detect.

  8. #8
    PowerPoster
    Join Date
    Jul 2010
    Location
    NYC
    Posts
    6,712

    Re: A module for reading CAB archives

    Quote Originally Posted by yokesee View Post
    I don't see it, but antiviruses do.
    People aren't going to use something like that.
    You do realize it's impossible to produce an unsigned VB6 app that doesn't trigger false positives in at least 2-3 of the engines on VirusTotal, right?

  9. #9
    PowerPoster yereverluvinuncleber's Avatar
    Join Date
    Feb 2014
    Location
    Norfolk UK (inbred)
    Posts
    2,880

    Re: A module for reading CAB archives

    Quote Originally Posted by yokesee View Post
    I don't see it, but antiviruses do.
    People aren't going to use something like that.
    Of course they are going to use it. Anyone who has a basic understanding of how A/V tools flag up unknown binaries (or even unknown source code) as false positives and who has the source in front of them (and an understanding of the code is doing) is going to be completely confident.

    IF you don't know this stuff then you MUST investigate deeper yourself.
    https://github.com/yereverluvinunclebert

    Skillset: VMS,DOS,Windows Sysadmin from 1985, fault-tolerance, VaxCluster, Alpha,Sparc. DCL,QB,VBDOS- VB6,.NET, PHP,NODE.JS, Graphic Design, Project Manager, CMS, Quad Electronics. classic cars & m'bikes. Artist in water & oils. Historian.

    By the power invested in me, all the threads I start are battle free zones - no arguing about the benefits of VB6 over .NET here please. Happiness must reign.

  10. #10
    Fanatic Member BenJones's Avatar
    Join Date
    Mar 2010
    Location
    Wales UK
    Posts
    812

    Re: A module for reading CAB archives

    Wow cool code works perfect. you just need a way to make the cabs now. can make an achiver then be cool. I remmber using a program to create and read cabs years ago called Cab2000 i think but was a trial. thanks for this code.

  11. #11

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    Quote Originally Posted by BenJones View Post
    Wow cool code works perfect. you just need a way to make the cabs now. can make an achiver then be cool. I remmber using a program to create and read cabs years ago called Cab2000 i think but was a trial. thanks for this code.
    Yes, you can write your own CAB archiver, but there is very complex code for packing files, so I haven't written it yet. There is simply no such code anywhere on the Internet for Visual Basic 6 and not even for VB.Net . Therefore, you will have to rewrite from other languages (Delphi, C++).

  12. #12
    Fanatic Member BenJones's Avatar
    Join Date
    Mar 2010
    Location
    Wales UK
    Posts
    812

    Re: A module for reading CAB archives

    Quote Originally Posted by HackerVlad View Post
    Yes, you can write your own CAB archiver, but there is very complex code for packing files, so I haven't written it yet. There is simply no such code anywhere on the Internet for Visual Basic 6 and not even for VB.Net . Therefore, you will have to rewrite from other languages (Delphi, C++).
    True but I did find makecab in windows after reading your code I was thinking of makeing a shell for it, and maybe using your code for reading the cabs if that is ok, I did see some cab stuff in delphi cos I thought I have a quick look around the net I used to use delphi I may have a look at that to. anyway keep up the good work.

  13. #13

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    Quote Originally Posted by BenJones View Post
    True but I did find makecab in windows after reading your code I was thinking of makeing a shell for it, and maybe using your code for reading the cabs if that is ok, I did see some cab stuff in delphi cos I thought I have a quick look around the net I used to use delphi I may have a look at that to. anyway keep up the good work.
    Yes, I'm already working on rewriting the code from Delphi. But I face difficulties in understanding the data types of variables. There is a lot to know (ULONG, UINT, Integer, USHORT, AnsiChar and so on)...
    Last edited by HackerVlad; Nov 12th, 2024 at 05:30 PM.

  14. #14

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    If I had a useful table where each data type of Delphi variables would be indicated, how many bytes it takes up in memory. It would be easier for me.

  15. #15
    Fanatic Member BenJones's Avatar
    Join Date
    Mar 2010
    Location
    Wales UK
    Posts
    812

    Re: A module for reading CAB archives

    Quote Originally Posted by HackerVlad View Post
    If I had a useful table where each data type of Delphi variables would be indicated, how many bytes it takes up in memory. It would be easier for me.
    Found this duno if it helps.
    http://mc-computing.com/languages/DataTypes.htm

    You should also get the size of the types using sizeof or Length if I remmber

  16. #16
    Fanatic Member BenJones's Avatar
    Join Date
    Mar 2010
    Location
    Wales UK
    Posts
    812

    Re: A module for reading CAB archives

    this looks usefull i was thinking of maybe writeing a shell for it but it you can convert that delphi one that be good to.
    https://learn.microsoft.com/en-us/wi...mmands/makecab

  17. #17

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    Quote Originally Posted by BenJones View Post
    this looks usefull i was thinking of maybe writeing a shell for it but it you can convert that delphi one that be good to.
    https://learn.microsoft.com/en-us/wi...mmands/makecab
    Yes, I'm already working on rewriting the Delphi code. So you'd better just wait for me to do the CAB packaging in VB6 myself. However, it should be understood that the packaging functions are very complex and use the CDecl convention.

  18. #18
    Member
    Join Date
    Apr 2014
    Posts
    40

    Re: A module for reading CAB archives

    I had to manage some cabs a few years ago... I started by wasting my time with some c++ code that was too strong for me found on codeproject,
    https://www.codeproject.com/Articles...and-Extraction
    then I found my happiness in c# on stackoverflow that I converted quite easily to vb.net (my final goal in vb.net)
    https://stackoverflow.com/questions/...file-in-memory
    (see post by Mr. Simon Mourier)
    it must be quite easly to adapt to vb6 for the experts here

    I should be able to find my code from that time, but it's vb.net, sorry for that

    NB My goal was quite specific, to read into memory (without extracting the entire cab into a directory) the beginning of a file (whose name was known) in a funny *.xsn extension cab... and that quickly...

  19. #19

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    As I promised, I wrote the packaging of CAB files in VB6 code, here's a look at my work: https://www.vbforums.com/showthread....=1#post5668210
    Last edited by HackerVlad; Feb 3rd, 2025 at 10:28 AM.

  20. #20

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    Quote Originally Posted by BenJones View Post
    Wow cool code works perfect. you just need a way to make the cabs now. can make an achiver then be cool. I remmber using a program to create and read cabs years ago called Cab2000 i think but was a trial. thanks for this code.
    Now we also have the code for packing files (creating cabinets). I've mastered this very complex code after all. In TwinBasic, this code will work without any dependencies, But in order for this code to work in VB6, you will have to download an add-in (a plugin for the VB6 environment) for CDecl functions to work.

    You can download the add-on here: https://github.com/thetrik/VBCDeclFix
    Topic on add-on VBCDeclFix: https://www.vbforums.com/showthread....ons-in-VB6-IDE

    The most important thing is that now we have a code for packing and unpacking cabinets. Now you can safely write a program similar to Cab2000 yourself. You can call it Cab2025 or even Cab3000

  21. #21
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,239

    Re: A module for reading CAB archives

    You don't really need that Add-in for VB6, "DispCallFunc" can call Cdecl functions if you want to be free of any external dependencies!

  22. #22

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    Quote Originally Posted by VanGoghGaming View Post
    You don't really need that Add-in for VB6, "DispCallFunc" can call Cdecl functions if you want to be free of any external dependencies!
    Thanks for telling me, I've thought about it too, but it's difficult for me. I do not know how to do this. Write me an example of such code.
    Even if you can call the API, then what about the custom CDecl functions? You will notice the many callback functions in my module - they are all CDecl.

    Although the dependence on this Add-in for vb6 does not bother me much, since now all people are switching to Twin Basic, and there are no dependencies there already.
    Last edited by HackerVlad; Nov 22nd, 2024 at 09:35 AM.

  23. #23
    PowerPoster VanGoghGaming's Avatar
    Join Date
    Jan 2020
    Location
    Eve Online - Mining, Missions & Market Trading!
    Posts
    2,239

    Re: A module for reading CAB archives

    Yeah, if the callback functions are also CDecl then you need the Add-in because it knows how to clear the stack after the function call.

  24. #24

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    BenJones, by the way, I have not found a program called "Cab2000" anywhere on the Internet.

  25. #25

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    Today I wrote version 2.0 of the module for reading and unpacking CAB files. What's new in this version?

    • Support for unicode file names and paths, for cabinet names themselves, and for destination folder paths for unpacking files (but not for files inside cabinets)
    • The ability to view the date and time of files inside the cabinets
    • Extracting file attributes inside cabinets
    • The ability to extract multiple files from a list (only some selected files) from cabinets
    • Fully compatible with 64-bit systems


    The first version of the module did not have a built-in mechanism for extracting files. The first version of my project used the undocumented ExtractFiles function, but unfortunately this function does not support unicode file names for cabinet files and for decompression paths, even if you use the ExtractFilesW function. Therefore, I decided to implement the unpacking mechanism with the SetupIterateCabinetW function. So now, to unpack the CAB files, you will need to use your own ExtractFilesInCab function.

    How to unpack a CAB file with a program code on VB6 (How Do I Unzip a CAB file)

    Code:
    Option Explicit
    '///////////////////////////////////////////////////
    '// Module for reading and unpacking CAB archives //
    '// Copyright (c) 2025-02-02 by HackerVlad        //
    '// e-mail: vladislavpeshkov@ya.ru                //
    '// Version 2.0 (32 and 64-bit compatible)        //
    '///////////////////////////////////////////////////
    
    ' API declarations ...
    #If VBA7 = 0 Then
    Private Declare Function SetupIterateCabinet Lib "setupapi" Alias "SetupIterateCabinetW" (ByVal CabinetFile As Long, ByVal Reserved As Long, ByVal MsgHandler As Long, ByVal Context As Long) As Long
    Private Declare Function lstrlen Lib "kernel32" Alias "lstrlenW" (ByVal lpString As Long) As Long
    Private Declare Function lstrcpy Lib "kernel32" Alias "lstrcpyW" (ByVal lpString1 As Long, ByVal lpString2 As Long) As Long
    Private Declare Function GetFileAttributesEx Lib "kernel32" Alias "GetFileAttributesExW" (ByVal lpFileName As Long, ByVal fInfoLevelId As Long, ByRef lpFileInformation As Any) As Long
    Private Declare Function SHCreateDirectory Lib "shell32" (ByVal hwnd As Long, ByVal pszPath As Long) As Long
    Private Declare Function PathRemoveFileSpecW Lib "shlwapi" (ByVal pszPath As Long) As Long
    
    Private Enum LongPtr
        [_]
    End Enum
    #Else
    Private Declare PtrSafe Function SetupIterateCabinet Lib "setupapi" Alias "SetupIterateCabinetW" (ByVal CabinetFile As LongPtr, ByVal Reserved As Long, ByVal MsgHandler As LongPtr, ByVal Context As Long) As Long
    Private Declare PtrSafe Function lstrlen Lib "kernel32" Alias "lstrlenW" (ByVal lpString As LongPtr) As Long
    Private Declare PtrSafe Function lstrcpy Lib "kernel32" Alias "lstrcpyW" (ByVal lpString1 As LongPtr, ByVal lpString2 As LongPtr) As Long
    Private Declare PtrSafe Function GetFileAttributesEx Lib "kernel32" Alias "GetFileAttributesExW" (ByVal lpFileName As LongPtr, ByVal fInfoLevelId As Long, ByRef lpFileInformation As Any) As Long
    Private Declare PtrSafe Function SHCreateDirectory Lib "shell32" (ByVal hwnd As Long, ByVal pszPath As LongPtr) As Long
    Private Declare PtrSafe Function PathRemoveFileSpecW Lib "shlwapi" (ByVal pszPath As LongPtr) As Long
    #End If
    
    ' Constants ...
    Private Const MAXPATH = 260
    Private Const FILEOP_DOIT = 1
    Private Const FILEOP_SKIP = 2
    Private Const SPFILENOTIFY_FILEINCABINET = &H11
    Private Const GetFileExInfoStandard As Long = 0
    
    ' Own fictional constants ...
    Private Const sicList = 568
    Private Const sicCount = 569
    Private Const sicExtract = 570
    
    ' Variables for temporary data storage ...
    Dim strFileToExtract As String, strOutputPath As String
    Dim lngCount As Long, CountFilesSuccessfullyExtracted As Long
    Dim arrListFilesCab() As CabInfo
    Dim arrFilesToExtract() As String
    Dim IsFilesList As Boolean
    
    ' Structures ...
    Public Type CabInfo
        cabFileName As String
        cabFileSize As Long
        cabDosDate As Integer
        cabDosTime As Integer
        cabDosAttribs As Integer
    End Type
    
    Private Type FileInCabinetInfoW
        NameInCabinet As LongPtr
        FileSize      As Long
        Win32Error    As Long
        DosDate       As Integer
        DosTime       As Integer
        DosAttribs    As Integer
        FullTargetName(MAXPATH - 1) As Integer ' WCHAR  FullTargetName[MAX_PATH];
    End Type
    
    Private Type WIN32_FILE_ATTRIBUTE_DATA
        dwFileAttributes    As Long
        ftCreationTime      As Currency
        ftLastAccessTime    As Currency
        ftLastWriteTime     As Currency
        nFileSizeHigh       As Long
        nFileSizeLow        As Long
    End Type
    
    ' Does the directory exist (IsDir)
    Public Function cabIsDir(ByVal path As String) As Boolean
        Dim tAttr As WIN32_FILE_ATTRIBUTE_DATA
        
        If GetFileAttributesEx(StrPtr(path), GetFileExInfoStandard, tAttr) <> 0 Then
            If (tAttr.dwFileAttributes And vbDirectory) <> 0 Then
                cabIsDir = True
            End If
        End If
    End Function
    
    ' Convert the full file path to the folder path (always returns "\" at the end)
    Public Function cabExtractFilePath(ByVal FileName As String) As String
        Dim lNullPos As Long
        Dim pszPath As String
        
        pszPath = FileName
        PathRemoveFileSpecW StrPtr(pszPath)
        
        lNullPos = InStr(1, pszPath, vbNullChar)
        If lNullPos Then
            pszPath = Left$(pszPath, lNullPos - 1)
            If Right$(pszPath, 1) <> "\" Then pszPath = pszPath & "\"
            cabExtractFilePath = pszPath
        Else
            cabExtractFilePath = FileName
        End If
    End Function
    
    Private Function CabinetCallback(ByVal Context As Long, ByVal Notification As Long, ByRef Param1 As FileInCabinetInfoW, ByVal Param2 As Long) As Long
        If Notification = SPFILENOTIFY_FILEINCABINET Then
            Dim FileNameInCab As String
            
            Select Case Context
                Case sicCount
                    lngCount = lngCount + 1
                    CabinetCallback = FILEOP_SKIP ' Skip the file and go through the list of files next
                    
                Case sicList
                    FileNameInCab = Space$(MAXPATH)
                    lstrcpy StrPtr(FileNameInCab), Param1.NameInCabinet
                    FileNameInCab = Left$(FileNameInCab, lstrlen(StrPtr(FileNameInCab)))
                    
                    ' Add a position to the UDT array
                    ReDim Preserve arrListFilesCab(lngCount)
                    arrListFilesCab(lngCount).cabFileName = FileNameInCab
                    arrListFilesCab(lngCount).cabFileSize = Param1.FileSize
                    arrListFilesCab(lngCount).cabDosDate = Param1.DosDate
                    arrListFilesCab(lngCount).cabDosTime = Param1.DosTime
                    arrListFilesCab(lngCount).cabDosAttribs = Param1.DosAttribs
                    
                    lngCount = lngCount + 1
                    CabinetCallback = FILEOP_SKIP ' Skip the file and go through the list of files next
                    
                Case sicExtract
                    Dim FileName As String, FilePath As String
                    Dim i As Long
                    
                    FileNameInCab = Space$(MAXPATH)
                    lstrcpy StrPtr(FileNameInCab), Param1.NameInCabinet
                    FileNameInCab = Left$(FileNameInCab, lstrlen(StrPtr(FileNameInCab)))
                    
                    FileName = strOutputPath & "\" & FileNameInCab
                    lstrcpy VarPtr(Param1.FullTargetName(0)), StrPtr(FileName) ' Filling in the structure value with the name of the extracted file
                    
                    If IsFilesList = False Then
                        If strFileToExtract = FileNameInCab Or Len(strFileToExtract) = 0 Then
                            FilePath = cabExtractFilePath(FileName)
                            If cabIsDir(FilePath) = False Then SHCreateDirectory 0, StrPtr(FilePath)
                            
                            CabinetCallback = FILEOP_DOIT ' Extract the file
                            CountFilesSuccessfullyExtracted = CountFilesSuccessfullyExtracted + 1
                        Else
                            CabinetCallback = FILEOP_SKIP ' Skip the file and go through the list of files next
                        End If
                    Else
                        Dim ExtractCurrentFile As Boolean
                        
                        For i = 0 To UBound(arrFilesToExtract)
                            If arrFilesToExtract(i) = FileNameInCab Then
                                ExtractCurrentFile = True
                                Exit For
                            End If
                        Next
                        
                        If ExtractCurrentFile = True Then
                            FilePath = cabExtractFilePath(FileName)
                            If cabIsDir(FilePath) = False Then SHCreateDirectory 0, StrPtr(FilePath)
                            
                            CabinetCallback = FILEOP_DOIT ' Extract the file
                            CountFilesSuccessfullyExtracted = CountFilesSuccessfullyExtracted + 1
                        Else
                            CabinetCallback = FILEOP_SKIP ' Skip the file and go through the list of files next
                        End If
                    End If
            End Select
        End If
    End Function
    
    ' Get a list of files inside the CAB archive
    Public Function GetFilesListInCab(ByVal cabFileName As String, arrCabInfo() As CabInfo) As Long
        lngCount = 0
        
        If SetupIterateCabinet(StrPtr(cabFileName), 0, AddressOf CabinetCallback, sicList) Then
            arrCabInfo = arrListFilesCab
            GetFilesListInCab = lngCount
            
            Erase arrListFilesCab
        End If
    End Function
    
    ' Get count of files inside the CAB archive
    Public Function GetFilesCountInCab(ByVal cabFileName As String) As Long
        lngCount = 0
        
        If SetupIterateCabinet(StrPtr(cabFileName), 0, AddressOf CabinetCallback, sicCount) Then
            GetFilesCountInCab = lngCount
        End If
    End Function
    
    ' Extract files from the CAB archive
    ' FileList - an extracted file from an archive (string) if there is one file, or an array of strings if there are several files to extract
    ' If the FileList parameter is omitted, all files from the archive will be extracted
    Public Function ExtractFilesInCab(ByVal cabFileName As String, ByVal ExpandDir As String, Optional FileList As Variant) As Boolean
        If Len(cabFileName) > 0 And Len(ExpandDir) > 0 Then
            If Right$(ExpandDir, 1) = "\" Then
                strOutputPath = Mid$(ExpandDir, 1, Len(ExpandDir) - 1)
            Else
                strOutputPath = ExpandDir
            End If
            
            If cabIsDir(strOutputPath) = False Then
                If SHCreateDirectory(0, StrPtr(strOutputPath)) <> 0 Then Exit Function
            End If
            
            If VarType(FileList) = vbString Then
                strFileToExtract = FileList
            ElseIf VarType(FileList) = vbArray + vbString Then ' If it is an array of strings
                strFileToExtract = ""
                arrFilesToExtract = FileList
                IsFilesList = True
            Else
                strFileToExtract = ""
            End If
            
            CountFilesSuccessfullyExtracted = 0
            
            If SetupIterateCabinet(StrPtr(cabFileName), 0, AddressOf CabinetCallback, sicExtract) Then
                If IsFilesList = False Then
                    If CountFilesSuccessfullyExtracted = 1 Or Len(strFileToExtract) = 0 Then ExtractFilesInCab = True
                Else
                    If CountFilesSuccessfullyExtracted = UBound(arrFilesToExtract) + 1 Then ExtractFilesInCab = True
                End If
            End If
            
            If IsFilesList = True Then
                IsFilesList = False
                Erase arrFilesToExtract
            End If
        End If
    End Function
    Download an example

  26. #26

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    Quote Originally Posted by VanGoghGaming View Post
    Yeah, if the callback functions are also CDecl then you need the Add-in because it knows how to clear the stack after the function call.
    By the way, I found a class module today that implements the ability to connect all CDecl functions, even if they are callback functions, without depending on the add-on.

    Today, I just happened to stumble across the CAB packaging and unpacking code on the Internet. Right here: https://www.jsware.net/jsware/vbcode.html#cab1

    Before that, I couldn't find such a code on the Internet for some reason, and it came to me to write everything myself. And then suddenly I found it. But the most important thing is that this project has a complete class module for working with CDecl. To do this, you can download the project "vbcab2.zip (20 KB)" from that site and there will be this class module.
    There's a "cCDECL.cls" file inside. I really liked it, I've been looking for a way to implement CDecl without any dependencies for a long time.

    The project there is very interesting, of course, it's a pity I didn't meet him earlier, but I still have a better project. Since I have implemented the highest compression ratio in LZX:21. But they don't. And my project does not create temporary files for me either.

  27. #27
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,690

    Re: A module for reading CAB archives

    Btw, go to https://github.com/Planet-Source-Code and you can search for cCDECL.cls in the org only.

    It's a class by Paul Caton but the latest version can be found in some of LaVolpe's submissions.

    cheers,
    </wqw>

  28. #28

    Thread Starter
    Fanatic Member HackerVlad's Avatar
    Join Date
    Nov 2023
    Posts
    562

    Re: A module for reading CAB archives

    New update! I have released a new version 2.1. I only recently noticed that the SetupIterateCabinet function does not set attributes when unpacking files for some reason. I had to manually set the attributes for each file extracted from the archive myself, so I had to write a new version. For some reason, Microsoft did not take care of this, without any idea why they thought that the SetupIterateCabinet function should not set file attributes... Thank them for at least setting the date and time for the files!

    Now all extracted files from the CAB archive have the correct attributes set!

    New module code:

    Code:
    Option Explicit
    '///////////////////////////////////////////////////
    '// Module for reading and unpacking CAB archives //
    '// Copyright (c) 2025-02-08 by HackerVlad        //
    '// e-mail: vladislavpeshkov@ya.ru                //
    '// Version 2.1 (32 and 64-bit compatible)        //
    '///////////////////////////////////////////////////
    
    ' API declarations ...
    #If VBA7 = 0 Then
    Private Declare Function SetupIterateCabinet Lib "setupapi" Alias "SetupIterateCabinetW" (ByVal CabinetFile As Long, ByVal Reserved As Long, ByVal MsgHandler As Long, ByVal Context As Long) As Long
    Private Declare Function GetFileAttributesEx Lib "kernel32" Alias "GetFileAttributesExW" (ByVal lpFileName As Long, ByVal fInfoLevelId As Long, ByRef lpFileInformation As Any) As Long
    Private Declare Function SHCreateDirectory Lib "shell32" (ByVal hwnd As Long, ByVal pszPath As Long) As Long
    Private Declare Function PathRemoveFileSpec Lib "shlwapi" Alias "PathRemoveFileSpecW" (ByVal pszPath As Long) As Long
    Private Declare Function SysReAllocString Lib "oleaut32" (ByVal pBSTR As Long, ByVal psz As Long) As Long
    Private Declare Sub SysFreeString Lib "oleaut32" (ByVal bstr As Long)
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare Function SetFileAttributes Lib "kernel32" Alias "SetFileAttributesW" (ByVal lpFileName As Long, ByVal dwFileAttributes As Long) As Long
    
    Private Enum LongPtr
        [_]
    End Enum
    #Else
    Private Declare PtrSafe Function SetupIterateCabinet Lib "setupapi" Alias "SetupIterateCabinetW" (ByVal CabinetFile As LongPtr, ByVal Reserved As Long, ByVal MsgHandler As LongPtr, ByVal Context As Long) As Long
    Private Declare PtrSafe Function GetFileAttributesEx Lib "kernel32" Alias "GetFileAttributesExW" (ByVal lpFileName As LongPtr, ByVal fInfoLevelId As Long, ByRef lpFileInformation As Any) As Long
    Private Declare PtrSafe Function SHCreateDirectory Lib "shell32" (ByVal hwnd As Long, ByVal pszPath As LongPtr) As Long
    Private Declare PtrSafe Function PathRemoveFileSpec Lib "shlwapi" Alias "PathRemoveFileSpecW" (ByVal pszPath As LongPtr) As Long
    Private Declare PtrSafe Function SysReAllocString Lib "oleaut32" (ByVal pBSTR As LongPtr, ByVal psz As LongPtr) As Long
    Private Declare PtrSafe Sub SysFreeString Lib "oleaut32" (ByVal bstr As LongPtr)
    Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)
    Private Declare PtrSafe Function SetFileAttributes Lib "kernel32" Alias "SetFileAttributesW" (ByVal lpFileName As LongPtr, ByVal dwFileAttributes As Long) As Long
    #End If
    
    ' Constants ...
    Private Const MAXPATH = 260
    Private Const FILEOP_DOIT = 1
    Private Const FILEOP_SKIP = 2
    Private Const SPFILENOTIFY_FILEINCABINET = &H11
    Private Const SPFILENOTIFY_FILEEXTRACTED = &H13
    Private Const GetFileExInfoStandard As Long = 0
    
    ' Own fictional constants ...
    Private Const sicList = 568
    Private Const sicCount = 569
    Private Const sicExtract = 570
    
    ' Variables for temporary data storage ...
    Dim CountFilesSuccessfullyExtracted As Long, SetAttribsFile As Long, lngCount As Long
    Dim strFileToExtract As String, strOutputPath As String
    Dim arrFilesToExtract() As String
    Dim arrListFilesCab() As CabInfo
    Dim IsFilesList As Boolean
    
    ' Structures ...
    Public Type CabInfo
        cabFileName As String
        cabFileSize As Long
        cabDosDate As Integer
        cabDosTime As Integer
        cabDosAttribs As Integer
    End Type
    
    Private Type FileInCabinetInfoW
        NameInCabinet As LongPtr
        FileSize      As Long
        Win32Error    As Long
        DosDate       As Integer
        DosTime       As Integer
        DosAttribs    As Integer
        FullTargetName(MAXPATH - 1) As Integer ' WCHAR  FullTargetName[MAX_PATH];
    End Type
    
    Private Type FILEPATHS
        Target     As LongPtr
        Source     As LongPtr
        Win32Error As Integer
        Flags      As Long
    End Type
    
    Private Type WIN32_FILE_ATTRIBUTE_DATA
        dwFileAttributes    As Long
        ftCreationTime      As Currency
        ftLastAccessTime    As Currency
        ftLastWriteTime     As Currency
        nFileSizeHigh       As Long
        nFileSizeLow        As Long
    End Type
    
    ' Does the directory exist (IsDir)
    Public Function cabIsDir(ByVal path As String) As Boolean
        Dim tAttr As WIN32_FILE_ATTRIBUTE_DATA
        
        If GetFileAttributesEx(StrPtr(path), GetFileExInfoStandard, tAttr) <> 0 Then
            If (tAttr.dwFileAttributes And vbDirectory) <> 0 Then
                cabIsDir = True
            End If
        End If
    End Function
    
    ' Convert the full file path to the folder path (always returns "\" at the end)
    Public Function cabExtractFilePath(ByVal FileName As String) As String
        Dim lNullPos As Long
        Dim pszPath As String
        
        pszPath = FileName
        PathRemoveFileSpec StrPtr(pszPath)
        
        lNullPos = InStr(1, pszPath, vbNullChar)
        If lNullPos Then
            pszPath = Left$(pszPath, lNullPos - 1)
            If Right$(pszPath, 1) <> "\" Then pszPath = pszPath & "\"
            cabExtractFilePath = pszPath
        Else
            cabExtractFilePath = FileName
        End If
    End Function
    
    Private Function CabinetCallback(ByVal Context As Long, ByVal Notification As Long, Param1 As FileInCabinetInfoW, ByVal Param2 As Long) As Long
        If Notification = SPFILENOTIFY_FILEINCABINET Then
            Dim FileNameInCab As String
            
            Select Case Context
                Case sicCount
                    lngCount = lngCount + 1
                    CabinetCallback = FILEOP_SKIP ' Skip the file and go through the list of files next
                    
                Case sicList
                    SysReAllocString VarPtr(FileNameInCab), Param1.NameInCabinet
                    
                    ' Add a position to the UDT array
                    ReDim Preserve arrListFilesCab(lngCount)
                    arrListFilesCab(lngCount).cabFileName = FileNameInCab
                    arrListFilesCab(lngCount).cabFileSize = Param1.FileSize
                    arrListFilesCab(lngCount).cabDosDate = Param1.DosDate
                    arrListFilesCab(lngCount).cabDosTime = Param1.DosTime
                    arrListFilesCab(lngCount).cabDosAttribs = Param1.DosAttribs
                    
                    lngCount = lngCount + 1
                    CabinetCallback = FILEOP_SKIP ' Skip the file and go through the list of files next
                    SysFreeString StrPtr(FileNameInCab)
                    
                Case sicExtract
                    Dim FileName As String, FilePath As String
                    Dim i As Long
                    
                    SysReAllocString VarPtr(FileNameInCab), Param1.NameInCabinet
                    FileName = strOutputPath & "\" & FileNameInCab
                    
                    ' We fill in the value of the structure with the filename of the extracted file (+1 character at the end of the line is vbNullChar)
                    CopyMemory Param1.FullTargetName(0), ByVal StrPtr(FileName), LenB(FileName) + 1
                    
                    If IsFilesList = False Then
                        If strFileToExtract = FileNameInCab Or Len(strFileToExtract) = 0 Then
                            FilePath = cabExtractFilePath(FileName)
                            If cabIsDir(FilePath) = False Then SHCreateDirectory 0, StrPtr(FilePath)
                            
                            SetAttribsFile = Param1.DosAttribs ' Remember the attributes
                            CabinetCallback = FILEOP_DOIT ' Extract the file
                            CountFilesSuccessfullyExtracted = CountFilesSuccessfullyExtracted + 1
                        Else
                            CabinetCallback = FILEOP_SKIP ' Skip the file and go through the list of files next
                        End If
                    Else
                        Dim ExtractCurrentFile As Boolean
                        
                        For i = 0 To UBound(arrFilesToExtract)
                            If arrFilesToExtract(i) = FileNameInCab Then
                                ExtractCurrentFile = True
                                Exit For
                            End If
                        Next
                        
                        If ExtractCurrentFile = True Then
                            FilePath = cabExtractFilePath(FileName)
                            If cabIsDir(FilePath) = False Then SHCreateDirectory 0, StrPtr(FilePath)
                            
                            SetAttribsFile = Param1.DosAttribs ' Remember the attributes
                            CabinetCallback = FILEOP_DOIT ' Extract the file
                            CountFilesSuccessfullyExtracted = CountFilesSuccessfullyExtracted + 1
                        Else
                            CabinetCallback = FILEOP_SKIP ' Skip the file and go through the list of files next
                        End If
                    End If
                    
                    SysFreeString StrPtr(FileNameInCab)
            End Select
        ElseIf Notification = SPFILENOTIFY_FILEEXTRACTED Then
            Dim fp As FILEPATHS
            
            LSet fp = Param1
            
            If fp.Win32Error = 0 Then ' If the file has been successfully extracted from the archive
                ' Manually set the attributes of the extracted file yourself,
                ' as Microsoft kindly refused to do this in the SetupIterateCabinet function
                SetFileAttributes fp.Target, SetAttribsFile
            End If
        End If
    End Function
    
    ' Get a list of files inside the CAB archive
    Public Function GetFilesListInCab(ByVal cabFileName As String, arrCabInfo() As CabInfo) As Long
        lngCount = 0
        
        If SetupIterateCabinet(StrPtr(cabFileName), 0, AddressOf CabinetCallback, sicList) Then
            arrCabInfo = arrListFilesCab
            GetFilesListInCab = lngCount
            
            Erase arrListFilesCab
        End If
    End Function
    
    ' Get count of files inside the CAB archive
    Public Function GetFilesCountInCab(ByVal cabFileName As String) As Long
        lngCount = 0
        
        If SetupIterateCabinet(StrPtr(cabFileName), 0, AddressOf CabinetCallback, sicCount) Then
            GetFilesCountInCab = lngCount
        End If
    End Function
    
    ' Extract files from the CAB archive
    ' FileList - an extracted file from an archive (string) if there is one file, or an array of strings if there are several files to extract
    ' If the FileList parameter is omitted, all files from the archive will be extracted
    Public Function ExtractFilesInCab(ByVal cabFileName As String, ByVal ExpandDir As String, Optional FileList As Variant) As Boolean
        If Len(cabFileName) > 0 And Len(ExpandDir) > 0 Then
            If Right$(ExpandDir, 1) = "\" Then
                strOutputPath = Mid$(ExpandDir, 1, Len(ExpandDir) - 1)
            Else
                strOutputPath = ExpandDir
            End If
            
            If cabIsDir(strOutputPath) = False Then
                If SHCreateDirectory(0, StrPtr(strOutputPath)) <> 0 Then Exit Function
            End If
            
            If VarType(FileList) = vbString Then
                strFileToExtract = FileList
            ElseIf VarType(FileList) = vbArray + vbString Then ' If it is an array of strings
                strFileToExtract = ""
                arrFilesToExtract = FileList
                IsFilesList = True
            Else
                strFileToExtract = ""
            End If
            
            CountFilesSuccessfullyExtracted = 0
            
            If SetupIterateCabinet(StrPtr(cabFileName), 0, AddressOf CabinetCallback, sicExtract) Then
                If IsFilesList = False Then
                    If CountFilesSuccessfullyExtracted = 1 Or Len(strFileToExtract) = 0 Then ExtractFilesInCab = True
                Else
                    If CountFilesSuccessfullyExtracted = UBound(arrFilesToExtract) + 1 Then ExtractFilesInCab = True
                End If
            End If
            
            If IsFilesList = True Then
                IsFilesList = False
                Erase arrFilesToExtract
            End If
        End If
    End Function
    Download an example

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width