Version 1 of zlib - inflate in tcl

Updated 2005-10-30 12:16:09

Philip Quaife 30 Oct 05, I have toyed with a tcl only installer some time ago, here is the code for inflating compressed files from a zip archive.

I lifted the code from AMSN, not sure of the copyright. Check with developer before distributing.

I changed the code to take a string and return decompressed string. I also have bracketed the expr and it runs in acceptable time. Enough speed that you could use it to uncompress the zlib dll from a SXE file and then use the dll to uncompress the rest of the archive.

NEM AMSN is distributed under the GPL. See also zlib.


 #
 # Lifted from AMSN (http://amsn.sf.net}
 #
 #

    proc zip/inflate { stream } {

        #status_log "reading from file $stream\n"
        set time [clock clicks]

        zip/reset 

 #--- Some sort of header starts the block not zlib block header

        set def ""
        binary scan [zip/read $stream 20] b* zlib

 if {0} {

        puts "read header"
        set CMF [string range $zlib 0 7]
        set FLG [string range $zlib 8 15]
        insLog DEBUG "CMF = $CMF\nFLG = $FLG\n" read

        if { [binary format b* $CMF] != "\x78" || [string range $FLG 5 5] != 0 } {
            insLog ERROR "Compression of the zlib data is in an unknown format\n" error
            return -1
        }

        binary scan [binary format b* [string range $zlib 0 15]] S FCHECK
        #status_log "FCHECK is such that CMF_FLG = $FCHECK\n" red
        if { [expr {$FCHECK % 31} ] != 0 } {
            #status_log "FCHECK is not a multiple of 31, corrupted data\n"
            return -1
        }

 #--- End of header
 } 

        set bfinal  "0"

# set idx 16

        set idx 0
        set len 160

        while { $bfinal != "1" } {


            set bfinal [string range $zlib $idx $idx]
            incr idx
            binary scan [binary format b* [string range $zlib $idx [expr {$idx + 1}]]] c btype
            set idx [expr $idx + 2]

            #status_log "Reading compressed block, with compression type $btype and final bloc = $bfinal\n"
            if { $btype == 0 } {

                if { [expr $idx % 8] != 0 } {
                    set idx [expr {$idx + 8 - ( $idx % 8)}]
                }

                binary scan [string range $zlib $idx [expr {$idx + 31}]] SS len nlen 
                set idx [expr {$idx + 32}]
                if { [string map { "0" "1" } $bnlen] != $blen } {
                    #status_log "Len and NLen does not match : [string range $zlib [expr {$idx -32}] [expr {$idx - 17}]] --- [string range $zlib [expr {$idx -16}] [expr {$idx - 1}]]\nValues are $len and $nlen\n" red
                    return -1
                } else {
                    binary scan [string range $zlib $idx [expr {$idx + 1}]] S len 
                }

                #status_log "Reading uncompressed block with length $len from index $idx to [expr $idx + 3 + $len]\n"
                set def "${def}[string range $zlib [expr {$idx + 4}] [expr {$idx + 3 + $len}]]"
                set idx [expr {$idx + 3 + $len}]

            } elseif { $btype == 3 } {
                #status_log "Got reserved word 11 for compression type : error\n" error
                return -1
            } else {
                if { $btype == 2 } {
                    #status_log "Got Huffman's dynamic compression block, processing\n"

                    #set time [clock clicks]

                    binary scan [binary format b* [string range $zlib $idx [expr {$idx + 4}]]] c hlit
                    set idx [expr {$idx + 5}]
                    set hlit [expr {$hlit + 257}]
                    binary scan [binary format b* [string range $zlib $idx [expr {$idx + 4}]]] c hdist
                    set idx [expr {$idx + 5}]
                    incr hdist
                    binary scan [binary format b* [string range $zlib $idx [expr {$idx + 3}]]] c hclen
                    set idx [expr {$idx + 4}]
                    set hclen [expr {$hclen + 4}]

                    #status_log "Got hlit = $hlit \nhdist = $hdist\nhclen = $hclen\n"


                    set codelengths [list 16 17 18 0 8 7 9 6 10 5 11 4 12 3 13 2 14 1 15]
                    for { set i 0 } { $i < [expr {$hclen * 3}] } { set i [expr {$i + 3}]} {
                        if { $idx > $len - 3 } {
                            binary scan [zip/read $stream 30] b* tmp
                            set zlib "[string range $zlib $idx end]$tmp"
                            set idx 0
                            set len [string length $zlib]
                        }
                        binary scan [binary format b* [string range $zlib $idx [expr {$idx + 2}]]] c clen([lindex $codelengths [expr {$i / 3}]])
                        set idx [expr {$idx + 3}]
                    }
                    #status_log "Read the codelengths, idx = $idx -- len = $len --- zlib = $zlib\ncodelengths = \n[array get clen]\n"

                    array set huffcodes [zip/createcodes [array get clen] 7 18]



                    #status_log "huffcodes = [array get huffcodes]\n"
                    #status_log "binary : [string range $zlib $idx  [expr $idx + 100]]\n\n"
                    set inc 0
                    set index 0
                    while { $index < $hlit } {
                        if { $idx >  $len - 7 } {
                            binary scan [zip/read $stream 30] b* tmp
                            set zlib "[string range $zlib $idx end]$tmp"
                            set idx 0
                            set len [string length $zlib]
                        }
                        set bin [string range $zlib $idx [expr {$idx + $inc}]]
                        if { [info exists huffcodes($bin)] } {
                            #                        #status_log "Found a length, for litteral value $index = $huffcodes($bin)\n"
                            set idx [expr {$idx + $inc + 1}]
                            if { $huffcodes($bin) < 16 } {
                                set litclen($index) $huffcodes($bin)
                                incr index
                            } elseif { $huffcodes($bin) == 16 } {
                                set tocopy $litclen([expr {$index - 1}])
                                binary scan [binary format b* [string range $zlib $idx [expr {$idx + 1}]]] c length
                                set length [expr {$length + 3}]
                                incr idx
                                incr idx

                                #                            #status_log "Copying value $tocopy into the next $length codes starting from $index\n"

                                for { set t 0 } { $t < $length } { incr t } {
                                    #                                #status_log "Literal length $index, copied value : $tocopy\n"
                                    set litclen($index) $tocopy
                                    incr index
                                }

                            } elseif { $huffcodes($bin) == 17 } {
                                binary scan [binary format b* [string range $zlib $idx [expr {$idx + 2}]]] c length
                                set length [expr {$length + 3}]
                                set idx [expr {$idx + 3}]
                                #                            #status_log "Copying value 0 into the next $length codes starting from $index\n"
                                for { set t 0 } { $t < $length } { incr t } {
                                    #                                #status_log "Literal length $index, copied value : 0\n"
                                    set litclen($index) 0
                                    incr index
                                }
                            } else {
                                binary scan [binary format b* [string range $zlib $idx [expr {$idx + 6}]]] c length
                                set length [expr {$length + 11}]
                                set idx [expr {$idx + 7}]
                                #                            #status_log "Copying value 0 into the next $length codes starting from $index\n"
                                for { set t 0 } { $t < $length } { incr t } {
                                    #                                #status_log "Literal length $index, copied value : 0\n"
                                    set litclen($index) 0
                                    incr index
                                }

                            }
                            set inc 0
                        } else {
                            incr inc
                            if { $inc > 7 } {
                                #status_log "Erreur.. l'increment a depasse 7.. \ndump :\nindex = $idx - increment = $inc, index = $index, $hlit\nmemoire = [string range $zlib [expr $idx ] [expr $idx + $inc ]]\n"

                                return -1
                            }

                        }




                    }

                    array set litval [zip/createcodes [array get litclen] 18 $hlit]


                    set inc 0
                    set index 0
                    while { $index < $hdist } {
                        if { $idx > $len - 7 } {
                            binary scan [zip/read $stream 30] b* tmp
                            set zlib "[string range $zlib $idx end]$tmp"
                            set idx 0
                            set len [string length $zlib]
                        }
                        set bin [string range $zlib $idx [expr {$idx + $inc}]]
                        if { [info exists huffcodes($bin)] } {
                            #                        #status_log "Found a length, for distance value $index = $huffcodes($bin)\n"
                            set idx [expr {$idx + $inc + 1}]
                            if { $huffcodes($bin) < 16 } {
                                set distclen($index) $huffcodes($bin)
                                incr index
                            } elseif { $huffcodes($bin) == 16 } {
                                set tocopy $distclen([expr {$index - 1}])
                                binary scan [binary format b* [string range $zlib $idx [expr {$idx + 1}]]] c length
                                set length [expr {$length + 3}]
                                incr idx 2

                                #                            #status_log "Copying value $tocopy into the next $length codes starting from $index\n"

                                for { set t 0 } { $t < $length } { incr t } {
                                    #                                #status_log "distance length $index, copied value : $tocopy\n"
                                    set distclen($index) $tocopy
                                    incr index
                                }

                            } elseif { $huffcodes($bin) == 17 } {
                                binary scan [binary format b* [string range $zlib $idx [expr {$idx + 2}]]] c length
                                set length [expr {$length + 3}]
                                set idx [expr {$idx + 3}]
                                #                            #status_log "Copying value 0 into the next $length codes starting from $index\n"
                                for { set t 0 } { $t < $length } { incr t } {
                                    #                                #status_log "distance length $index, copied value : 0\n"
                                    set distclen($index) 0
                                    incr index
                                }
                            } else {
                                binary scan [binary format b* [string range $zlib $idx [expr {$idx + 6}]]] c length
                                set length [expr {$length + 11}]
                                set idx [expr {$idx + 7}]
                                #                            #status_log "Copying value 0 into the next $length codes starting from $index\n"
                                for { set t 0 } { $t < $length } { incr t } {
                                    #                                #status_log "distance length $index, copied value : 0\n"
                                    set distclen($index) 0
                                    incr index
                                }

                            }
                            set inc 0
                        } else {
                            incr inc
                            if { $inc > 7 } {
                                #status_log "Erreur.. l'increment a depasse 7.. \ndump :\nindex = $idx - increment = $inc, index = $index, $hlit\nmemoire = [string range $zlib [expr $idx ] [expr $idx + $inc ]]\n"

                                return -1
                            }

                        }
                    }

                    array set distval [zip/createcodes [array get distclen] 18 $hdist]



                } else {
                    #status_log "Got Huffman's compressed block, processing\n"
                    array set litval [zip/createcodes [zip/fill_length lit] 18 287]
                    array set distval [zip/createcodes [zip/fill_length dist] 18 32]


                }

# status_log "Time for processing header: expr [clock clicks - $time]\n"

                ############################################################################################################

                set inc 0
                set index [string length $def]
                #set time [clock clicks]
                for { } { 1 } { } {
                    if { $idx > $len - 15 } {
                        binary scan [zip/read $stream 30] b* tmp
                        set zlib "[string range $zlib $idx end]$tmp"
                        set idx 0
                        set len [string length $zlib]
                    }
                    set bin [string range $zlib $idx [expr {$idx + $inc}]]
                    #status_log "time for string range : [time "string range $zlib $idx [expr {$idx + $inc}]"]\n"
                    #status_log "Time for infoexits : [time "info exists litval($bin)"] --- bin = $bin\n"
                    if { [info exists litval($bin)] } {
                        set out $litval($bin)
                                            #status_log "Found a length in index $index, for output = $out\n"
                        set idx [expr {$idx + $inc + 1}]
                        if { $out < 256 } {
                            set def "${def}[binary format c $out]"
                            incr index
                           #status_log "Time for literal value : [expr [clock clicks] - $time]\n"
                        } elseif { $out == 256 } {
                            #status_log "FOUND END OF BLOCK\n" red
                            break
                        } else {
                            #status_log "Need to move backward distance $out -- processing\n"
                            #set time [clock clicks]

                            if { $idx > $len - 5 } {
                                binary scan [zip/read $stream 30] b* tmp
                                set zlib "[string range $zlib $idx end]$tmp"
                                set idx 0
                                set len [string length $zlib]
                            }

                            if { $out < 265 } {
                                set plus 0
                                set length [expr {$out - 254}]
                            } elseif { $out == 285 } {
                                set plus 0
                                set length 258
                            } elseif { $out > 264 && $out < 269 } {
                                binary scan [binary format b* [string range $zlib $idx  $idx]] c plus
                                incr idx
                                set length [expr {(($out - 265) * 2) + $plus + 11}]
                            } elseif { $out > 268 && $out < 273} {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 1}]]] c plus
                                incr idx 2
                                set length [expr {(($out - 269) * 4) + $plus + 19}]
                            } elseif { $out > 272 && $out < 277 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 2}]]] c plus
                                set idx [expr {$idx + 3}]
                                set length [expr {(($out - 273) * 8) + $plus + 35}]
                            } elseif { $out > 276 && $out < 281 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 3}]]] c plus
                                set idx [expr {$idx + 4}]
                                set length [expr {(($out - 277) * 16) + $plus + 67}]
                            } else {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 4}]]] c plus
                                set idx [expr {$idx + 5}]
                                set length [expr {(($out - 281) * 32) + $plus + 131}]
                            }

                            #status_log "time for ifelses : [expr [clock clicks] - $time]"
                            #status_log "Found length $length with added $plus\n"

                            set out2 -1 
                            set inc2 0
                            while { $out2 == -1 } {
                                if { $idx > $len - 15 } {
                                    binary scan [zip/read $stream 30] b* tmp
                                    set zlib "[string range $zlib $idx end]$tmp"
                                    set idx 0
                                    set len [string length $zlib]
                                }
                                set bin [string range $zlib $idx [expr {$idx + $inc2}]]
                                if { [info exists distval($bin)] } {
                                    set out2 $distval($bin)
                                    #status_log "Found a distance code  $out2\n"
                                    set idx [expr {$idx + $inc2 + 1}]
                                } else {
                                    incr inc2
                                    if { $inc2 > 15 } {
                                        #status_log "Erreur.. l'increment a depasse 15.. \ndump :\nindex = $idx - increment = $inc2, index = $index, $hlit\nmemoire = [string range $zlib [expr $idx ] [expr $idx + $inc2 ]]\n"
                                        return -1
                                    }
                                }
                            }

                            if { $idx > $len - 13 } {
                                binary scan [zip/read $stream 30] b* tmp
                                set zlib "[string range $zlib $idx end]$tmp"
                                set idx 0
                                set len [string length $zlib]
                            }

                            if { $out2 < 4 } {
                                set plus 0
                                set distance [expr {$out2 + 1}]
                            } elseif { $out2 == 4 || $out2 == 5} {
                                binary scan [binary format b* [string range $zlib $idx  $idx]] c plus
                                set plus [expr $plus % 256]
                                incr idx
                                set distance [expr {(($out2 - 4) * 2) + $plus + 5}]
                            } elseif { $out2 == 6 || $out2 == 7} {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 1}]]] c plus
                                set plus [expr {$plus % 256}]
                                incr idx 2
                                set distance [expr {(($out2 - 6) * 4) + $plus + 9}]
                            } elseif { $out2 == 8 || $out2 == 9 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 2}]]] c plus
                                set plus [expr {$plus % 256}]
                                set idx [expr {$idx + 3}]
                                set distance [expr {(($out2 - 8) * 8) + $plus + 17}]
                            } elseif { $out2 == 10 || $out2 == 11} {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 3}]]] c plus
                                set plus [expr {$plus % 256}]
                                set idx [expr {$idx + 4}]
                                set distance [expr {(($out2 - 10) * 16) + $plus + 33}]
                            } elseif {$out2 == 12 || $out2 == 13 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 4}]]] c plus
                                set plus [expr {$plus % 256}]
                                set idx [expr {$idx + 5}]
                                set distance [expr {(($out2 - 12) * 32) + $plus + 65}]
                            } elseif {$out2 == 14 || $out2 == 15 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 5}]]] c plus
                                set plus [expr {$plus % 256}]
                                set idx [expr {$idx + 6}]
                                set distance [expr {(($out2 - 14) * 64) + $plus + 129}]
                            } elseif {$out2 == 16 || $out2 == 17 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 6}]]] c plus
                                set plus [expr {$plus % 256}]
                                set idx [expr {$idx + 7}]
                                set distance [expr {(($out2 - 16) * 128) + $plus + 257}]
                            } elseif {$out2 == 18 || $out2 == 19 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 7}]]] c plus
                                set plus [expr {$plus % 256}]
                                set idx [expr {$idx + 8}]
                                set distance [expr {(($out2 - 18) * 256) + $plus + 513}]
                            } elseif {$out2 == 20 || $out2 == 21 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 8}]]] s plus
                                set plus [expr {$plus % 65536}]
                                set idx [expr {$idx + 9}]
                                set distance [expr {(($out2 - 20) * 512) + $plus + 1025}]
                            } elseif {$out2 == 22 || $out2 == 23 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 9}]]] s plus
                                set plus [expr {$plus % 65536}]
                                set idx [expr {$idx + 10}]
                                set distance [expr {(($out2 - 22) * 1024) + $plus + 2049}]
                            } elseif {$out2 == 24 || $out2 == 25 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 10}]]] s plus
                                set plus [expr {$plus % 65536}]
                                set idx [expr {$idx + 11}]
                                set distance [expr {(($out2 - 24) * 2048) + $plus + 4097}]
                            } elseif {$out2 == 26 || $out2 == 27 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 11}]]] s plus
                                set plus [expr {$plus % 65536}]
                                set idx [expr {$idx + 12}]
                                set distance [expr {(($out2 - 26) * 4096) + $plus + 8193}]
                            } elseif {$out2 == 28 || $out2 == 29 } {
                                binary scan [binary format b* [string range $zlib $idx  [expr {$idx + 12}]]] s plus
                                set plus [expr {$plus % 65536}]
                                set idx [expr {$idx + 13}]
                                set distance [expr {(($out2 - 28) * 8192) + $plus + 16385}]
                            }

                            #                        #status_log "Found distance $distance with added $plus\n"

                            set tocopy [string range $def [expr {$index - $distance}] $index]
                            while { [string length $tocopy] <= $length } {
                                set tocopy "${tocopy}${tocopy}"
                            }

                            set tocopy [string range $tocopy 0 [expr {$length -1}]]
                            set def "${def}$tocopy"

                            set index [expr {$index + $length}]
                           #status_log "Time for distance : [expr [clock clicks] - $time]\n"
                        }
                        set inc 0

# set time clock clicks

                    } else {
                        incr inc

                    }
                }

            }

        }


        #status_log "Finished reading and uncompressing zlib blocks of data\n" blue
        #status_log "Time for zlib: [expr [clock clicks] - $time]\n"

        return $def

    }


    proc zip/createcodes { oclen maxbits maxcode } {

        array set clen $oclen 

        #    set clen [list 3 3 3 3 3 2 4 4]

        foreach c [array names clen] {
            if {[info exists bl_count($clen($c))] } {
                incr bl_count($clen($c))
            } else {
                set bl_count($clen($c)) 1
            }
        }

        set code 0

        set bl_count(0) 0;
        #status_log "bl_cout = [array get bl_count]\n"
        for { set bits 1 } { $bits <= $maxbits } {incr bits} {
            if { ![info exists bl_count([expr {$bits - 1}])] } {
                set bl_count([expr {$bits - 1}]) 0
            }
            set code [expr {($code + $bl_count([expr $bits - 1])) << 1}];
            set next_code($bits) $code;
        }

        #status_log "code = $code\nnext_code = [array get next_code]\n"

        for {set n  0} { $n <= $maxcode} {incr n} {
            if { [info exists clen($n)]} {
                set len $clen($n) 
            } else {
                set len 0
            }
            if { $len != 0} {
                binary scan [binary format s $next_code($len)] b$len bin
                #            #status_log "$len = $next_code($len) = $bin = [zip/invert $bin]\n"
                set bin [zip/invert $bin]
                set codes($bin) $n
                incr next_code($len)
            }
        }


        return [array get codes]

    }


    proc zip/invert { bin } {

        set out ""

        for { set i [expr {[string length $bin] - 1}] } { $i >= 0 } { incr i -1} {
            set out "$out[string index $bin $i]"
        }

        return $out
    }

    proc zip/fill_length { type } {
        set out ""
        switch $type { 
            "lit" {
                for { set i 0 } { $i <= 287 } { incr i } {
                    if { $i <= 143 } {
                        set out "$out $i 8"
                    } elseif { $i <= 255 } {
                        set out "$out $i 9"
                    } elseif { $i <= 279 } {
                        set out "$out $i 7"
                    } else {
                        set out "$out $i 8"
                    }
                }
            }
            "dist" {
                for { set i 0 } { $i <= 31} { incr i } {
                    set out "$out $i 5"
                }
            }
        }

        return $out
    }


 variable zip/tell 0


 proc zip/reset {} {variable zip/tell ; set zip/tell 0}


 proc zip/read {stream cnt} {
        variable zip/tell
        set ret [string range $stream ${zip/tell} [expr {${zip/tell} + $cnt -1}]]

        if {${zip/tell} > [string length $stream]} {
                insLog ERROR zip/read past end of file at ${zip/tell} ([string length $stream])
                #error "EOF"
        }
        incr zip/tell $cnt
        set ret
 }


 package provide tclzlib 0.1


Category Tutorial