# ZIP files example 0.4.11
# more info: http://www.pkware.com/documents/casestudies/APPNOTE.TXT
#   note that with some archives like those created by Stuff-it on MacOSX is
#   not possible to use this script because they are wrongly built, practically
#   they set the comp_size and uncomp_size fields of the "Local file header" at
#   0 and they set them only in the relative "Central directory structure" which
#   means that it's necesary to read this one first for extracting the files
#   contained in the local header... senseless and stupid
# script for QuickBMS http://quickbms.aluigi.org

# put the password here, it supports both ZipCrypto and AES
set ZIP_PASSWORD string ""

quickbmsver "0.7.4"

get EXE_SIGN long
goto 0
if EXE_SIGN == 0x00905a4d
    get EXT extension
    if EXT == "exe" || EXT == "dll"
        findloc OFFSET string "PK\x03\x04"
        goto OFFSET
    endif
elif EXE_SIGN == 0x02014b50
    findloc OFFSET binary "\x50\x4b\x03\x04"
    goto OFFSET
endif

savepos OFFSET
set ZIP_SIGN short 0x0403
goto OFFSET
getdstring ZIP_CENTRAL_SEARCH 6 # PK_sign + sign + ver
goto OFFSET
get DUMMY short
get ZIP_SIGN short

math ALTERNATIVE_MODE = 0   # in reality this is the real correct mode to read the ZIP archives

math FIRST_FILE = 1
goto OFFSET
get zip_filesize asize
for offset = offset < zip_filesize
    #idstring "PK\x03\x04"
    get PK_sign short       # so it works also with modified ZIP files!
    get sign short
    if sign == ZIP_SIGN     # Local file header
        get ver             short
        get flag            short
        get method          short
        get modtime         short
        get moddate         short
        get zip_crc         long
        get comp_size       long
        get uncomp_size     long
        get name_len        short
        get extra_len       short
        getdstring name     name_len
        getdstring extra    extra_len
        savepos offset

        if FIRST_FILE != 0
            math FIRST_FILE = 0

            if flag & 8
            if zip_crc == 0
            if comp_size == 0
            #if uncomp_size == 0    # needs to be commented out
                goto -0x16
                get PK_sign short
                idstring "\x05\x06"
                get disk_num        short
                get disk_start      short
                get central_entries short
                get central_entries short
                get central_size    long
                get central_offset  long
                get comm_len        short   # let's think it's zero
                getdstring comment  comm_len

                math ALTERNATIVE_MODE   = 1
                math ALTERNATIVE_OFFSET = central_offset
                math ALTERNATIVE_comp_size = 0
                math ALTERNATIVE_uncomp_size = 0
                math ALTERNATIVE_zip_crc = 0

                set NAME string "/"     # skip this file
                math uncomp_size = 0    # skip this file
            #endif
            endif
            endif
            endif
        endif

        if ALTERNATIVE_MODE != 0
            math comp_size   = ALTERNATIVE_comp_size
            math uncomp_size = ALTERNATIVE_uncomp_size
            math zip_crc     = ALTERNATIVE_zip_crc
        endif

        # zip64
        if extra_len >= 20
            getvarchr extra_id extra 0 short
            if extra_id == 0x0001
            if comp_size == 0xffffffff
                getvarchr uncomp_size 4 longlong
                getvarchr comp_size 12 longlong
            endif
            endif
        endif

        # possible lame tricks used by games
        if comp_size & 0x80000000 # < 0
        if comp_size u> zip_filesize
            math comp_size ^= 0xffffffff
            math uncomp_size ^= 0xffffffff
        endif
        endif
        if name_len & 0x80000000 # < 0
            math name_len ^= 0xffffffff
        endif
        if extra_len & 0x80000000 # < 0
            math extra_len ^= 0xffffffff
        endif

        if flag & 1
            if ZIP_PASSWORD == ""
                print "the file is encrypted, you must set ZIP_PASSWORD in the script!"
                #cleanexit
            endif
            math method_backup = method
            if method == 99
                getvarchr AES_EXTRA1 extra 0 short  # Extra field header ID (0x9901)
                getvarchr AES_EXTRA2 extra 2 short  # Data size (currently 7, but subject to possible increase in the future)
                getvarchr AES_EXTRA3 extra 4 short  # Integer version number specific to the zip vendor
                getvarchr AES_EXTRA4 extra 6 short  # 2-character vendor ID
                getvarchr AES_EXTRA5 extra 8 byte   # Integer mode value indicating AES encryption strength
                getvarchr AES_EXTRA6 extra 9 short  # The actual compression method used to compress the file
                math method = AES_EXTRA6
                if AES_EXTRA5 == 0x01
                    math AES_KEY_SIZE = 8
                    set AES_ALGO string "ZIP_AES128"
                elif AES_EXTRA5 == 0x02
                    math AES_KEY_SIZE = 12
                    set AES_ALGO string "ZIP_AES192"
                elif AES_EXTRA5 == 0x03
                    math AES_KEY_SIZE = 16
                    set AES_ALGO string "ZIP_AES256"
                else
                    print "Error: invalid AES_EXTRA5 %AES_EXTRA5%"
                    cleanexit
                endif
                getdstring AES_SALT AES_KEY_SIZE
                xmath offset    "offset    + (AES_KEY_SIZE + 2)"
                xmath comp_size "comp_size - (AES_KEY_SIZE + 2 + 10)"
                strlen ZIP_PASSWORD_LEN ZIP_PASSWORD
                encryption AES_ALGO ZIP_PASSWORD AES_SALT 0 ZIP_PASSWORD_LEN    # long story short, set the password length or use "Set binary"
            else
                encryption zipcrypto ZIP_PASSWORD 1
            endif
        endif

        if method == 0
            Log name offset comp_size   # was uncomp_size before AES
        else
            if method == 8
                ComType deflate
            elif method == 1
                ComType shrink
            elif method == 2
                ComType reduce1
            elif method == 3
                ComType reduce2
            elif method == 4
                ComType reduce3
            elif method == 5
                ComType reduce4
            elif method == 6
                ComType pkware      # ???
            elif method == 9
                ComType deflate64
            elif method == 10
                ComType pkware      # ???
            elif method == 12
                ComType bzip2
            elif method == 13
                ComType XMemDecompress
            elif method == 14
                ComType lzmaefs
            elif method == 15
                ComType oodle
            elif method == 18
                ComType terse       # ???
            elif method == 21
                ComType XMemDecompress
            elif method == 28
                ComType lz4f
            elif method == 34
                ComType brotli
            elif method == 64
                ComType darksector
            elif method == 95
                comtype LZMA2_EFS0
                getdstring XZ_MAGIC 6
                get XZ_FLAGS0 byte
                get XZ_FLAGS byte
                get XZ_CRC32 long
                xmath DUMMY "1 << ((((XZ_FLAGS & 0xf) - 1) / 3) + 2)"
                getdstring DUMMY DUMMY
                savepos tmp
                xmath comp_size "comp_size - (tmp - offset)"
                math offset = tmp
            elif method == 96       # compressed jpeg
                ComType copy
                math uncomp_size = comp_size
                string name + ".jpg"
            elif method == 97       # wavpack
                ComType copy
                math uncomp_size = comp_size
                string name + ".wv"
            elif method == 98
                ComType ppmd
            elif method == 99
                ComType lzfse
            else
                print "unsupported compression method %method%"
                cleanexit
            endif
            CLog name offset comp_size uncomp_size
        endif

        if flag & 1
            encryption "" ""
            if method_backup == 99
                math offset += 10
            endif
        endif

        math offset += comp_size
        goto offset

        if ALTERNATIVE_MODE != 0
            goto ALTERNATIVE_OFFSET
        endif

    elif sign == 0x0806     # Archive extra data record
        get extra_len       long
        getdstring extra    extra_len

    elif sign == 0x0201     # Central directory structure
        get ver_made        short
        get ver_need        short
        get flag            short
        get method          short
        get modtime         short
        get moddate         short
        get zip_crc         long
        get comp_size       long
        get uncomp_size     long
        get name_len        short
        get extra_len       short
        get comm_len        short
        get disknum         short
        get int_attr        short
        get ext_attr        long
        get rel_offset      long
        getdstring name     name_len
        getdstring extra    extra_len
        getdstring comment  comm_len

        if ALTERNATIVE_MODE != 0
            math ALTERNATIVE_comp_size = comp_size
            math ALTERNATIVE_uncomp_size = uncomp_size
            math ALTERNATIVE_zip_crc = zip_crc
            savepos ALTERNATIVE_OFFSET
            goto rel_offset
        endif

    elif sign == 0x0505     # Digital Signature
        get sign_len        long
        getdstring sign     sign_len

    elif sign == 0x0606     # Zip64 end of central directory record
        get dir_record      longlong
        get ver_made        short
        get ver_need        short
        get num_disk        long
        get num_disk2       long
        get tot_entries     longlong
        get tot_entries2    longlong
        get central_size    longlong
        get central_offset  longlong
        print "Error: zip64 extensible data sector not implemented, contact me"
        cleanexit

    elif sign == 0x0706     # Zip64 end of central directory locator
        get start_central   long
        get end_central     longlong
        get disks           long

    elif sign == 0x0605     # End of central directory record
        get disk_num        short
        get disk_start      short
        get central_entries short
        get central_entries short
        get central_size    long
        get central_offset  long
        get comm_len        short
        getdstring comment  comm_len

    elif sign == 0x0807     # Data Descriptor
        get zip_crc         long
        get comp_size       long
        get uncomp_size     long

    elif sign == 0x3030     # disk spanning
        # nothing?

    else
        # A ZIP archive should be read from the end and not sequentially because in some rare cases
        # we may have some "gaps" between the various directories, this is a basic way to guess
        # the beginning of the next directory

        # ZIP_CENTRAL_SEARCH contains zeroes that will not be considered, not a problem
        print "...search ZIP signature..."
        findloc NEW_OFFSET binary ZIP_CENTRAL_SEARCH ""
        if NEW_OFFSET == ""
            xmath COVERAGE "offset / (zip_filesize / 100)"   # "(offset*100)/zip_filesize" may go in overflow on 32bit
            set COVERAGE_OK string "not fully covered, lot of data remaining"
            if COVERAGE >= 90
                set COVERAGE_OK string "fully covered, probably no remaining data"
            endif
            print "\nError: unknown ZIP signature %sign|x% at offset %offset|x%\n       if the other files have been extracted correctly it's all ok,\n       maybe this is just the end of file:\n\n       OFFSET   %offset|x%\n       ZIP SIZE %zip_filesize|x%\n       COVERAGE %COVERAGE% / 100   (%COVERAGE_OK%)"
            cleanexit
        endif
        goto NEW_OFFSET
    endif

    savepos offset
next