Updated 2012-04-13 02:15:09 by aspect

AF 2004-08-04 This is a series of procs for reading information from the resource tree compiled into windows executable files. The resource tree holds such information as the menus, icons, cursors, version information, and accelerator keys for the program.
proc FindWindowsHeader {file sig} {
    set fh [open $file r]
    fconfigure $fh -encoding binary -eofchar {} -translation lf
    if {[read $fh 2] != "MZ"} {close $fh; error "not an DOS executable"}
    seek $fh 60 start
    seek $fh [getword $fh] start
    if {[read $fh 2] != $sig} {close $fh; error "windows header not found"}
    seek $fh -2 current
    return $fh
}

proc EnumNEResources {file} {
    set fh [FindWindowsHeader $file NE]
    seek $fh 36 current
    seek $fh [expr {[getword $fh] - 36}] current
    set base [tell $fh]

    set ret {}
    while {[set type [getNEResName $fh $base [getushort $fh]]] != 0} {
        set num [getushort $fh]
        seek $fh [expr {$num * 12 + 4}] current
        lappend ret $type $num
    }
    return $ret
}

proc getNEResource {file restype {resid {}}} {
    set fh [FindWindowsHeader $file NE]
    seek $fh 36 current
    seek $fh [expr {[getword $fh] - 38}] current
    set base [tell $fh]

    set shift [expr {int(pow(2, [getushort $fh]))}]
    while {[set type [getNEResName $fh $base [getushort $fh]]] != 0} {
        set num [getushort $fh]
        if {$type != $restype} {
            seek $fh [expr {$num * 12 + 4}] current
            continue
        }
        seek $fh 4 current
        set ret {}
        for {set i 0} {$i < $num} {incr i} {
            set offset [expr {[getushort $fh] * $shift}]
            set len [getushort $fh]
            seek $fh 2 current
            lappend ret[getNEResName $fh $base [getushort $fh]] $offset $len
            seek $fh 4 current
        }
        close $fh
        return $ret
    }
    return {}
}


proc getNEResName {fh base data} {
    if {$data == 0} {
        return 0
    } elseif {[getbit $data 0] == 0} {
        set cur [tell $fh]
        seek $fh [expr {$data + $base}] start
        set len [getchar $fh]
        set name [read $fh $len]
        seek $fh $cur start
        return $name
    } else {
        return [expr {$data & 0x7fff}]
    }
}

proc EnumPEResources {file} {
    set fh [FindWindowsHeader $file PE]
    binary scan [read $fh 24] x6sx12s sections headersize
    seek $fh $headersize current
    for {set i 0} {$i < $sections} {incr i} {
        binary scan [read $fh 40] a8x4ix4i type baserva base
        if {[string match .rsrc* $type]} {break}
    }
    if {![string match .rsrc* $type]} {close $fh; return {}}
    seek $fh [expr {$base + 12}] start

    set res {}
    set entries [expr {[getushort $fh] + [getushort $fh]}]
    for {set i 0} {$i < $entries} {incr i} {
        lappend res [getPEResName $fh $base [getulong $fh]]
        set offset [expr {[getulong $fh] & 0x7fffffff}]
        set cur [tell $fh]
        seek $fh [expr {$base + $offset + 12}] start
        lappend res [expr {[getushort $fh] + [getushort $fh]}]
        seek $fh $cur start
    }
    close $fh
    return $res
}

proc getPEResource {file restype {resid {}}} {
    set fh [FindWindowsHeader $file PE]
    binary scan [read $fh 24] x6sx12s sections headersize
    seek $fh $headersize current
    for {set i 0} {$i < $sections} {incr i} {
        binary scan [read $fh 40] a8x4ix4i type baserva base
        if {[string match .rsrc* $type]} {break}
    }
    if {![string match .rsrc* $type]} {close $fh; return {}}
    seek $fh [expr {$base + 12}] start

    set entries [expr {[getushort $fh] + [getushort $fh]}]
    for {set i 0} {$i < $entries} {incr i} {
        set name [getPEResName $fh $base [getulong $fh]]
        set offset [expr {[getulong $fh] & 0x7fffffff}]
        if {$name != $restype} {continue}

        set cur [tell $fh]
        seek $fh [expr {$base + $offset + 12}] start
        
        set entries2 [expr {[getushort $fh] + [getushort $fh]}]
        for {set i2 0} {$i2 < $entries2} {incr i2} {
            
            set name [getulong $fh]
            set offset [expr {[getulong $fh] & 0x7fffffff}]
            set name [getPEResName $fh $base $name]
            if {$resid != "" && $name != $resid} {continue}
            
            set cur2 [tell $fh]
            seek $fh [expr {$offset + $base + 12}] start
          
            set entries3 [expr {[getushort $fh] + [getushort $fh]}]
            for {set i3 0} {$i3 < $entries3} {incr i3} {
                binary scan [read $fh 4] H2H2 n1 n2
                set offset [expr {[getulong $fh] & 0x7fffffff}]
                set cur3 [tell $fh]
                        
                seek $fh [expr {$offset + $base}] start
                set rva [getulong $fh]
                set size [getulong $fh]
                lappend ret $name [expr {$rva - $baserva + $base}] $size
                        
                seek $fh $cur3 start
            }
            seek $fh $cur2 start
        }
        close $fh
        return $ret
    }
    return {}
}

proc getPEResName {fh start data} {
    if {($data & 0x80000000) != 0} {
        set cur [tell $fh]
        seek $fh [expr {($data & 0x7fffffff) + $start}] start
        set len [getushort $fh]
        set name [read $fh [expr {$len * 2}]]
        seek $fh $cur start
        return [encoding convertfrom unicode $name]
    } else {
        return $data
    }
}

proc getdword {fh} {
    binary scan [read $fh 4] i tmp
    return $tmp
}

proc getulong {fh} {
    binary scan [read $fh 4] i tmp
    return [format %u $tmp]
}

proc getushort {fh} {
    binary scan [read $fh 2] s tmp
    return [expr {$tmp & 0x0000FFFF}]
}

proc getword {fh} {
    binary scan [read $fh 2] s tmp
    return $tmp
}

proc getchar {fh} {
    binary scan [read $fh 1] c tmp
    return $tmp
}

Usage:
 EnumNEResources <file>
 getNEResource <file> <res type> ?res id?
 EnumPEResources <file>
 getPEResource <file> <res type> ?res id?

Enum* returns a list of resource types found and the number of resources for that type.

get* returns a list of the resource id, its offset, and its size for the resources of the specified type

The NE and PE methods corespond to 16bit and 32bit files. DLL, OCX, ICL, and other files are also executable format and will work with these procedures.

Resource types may have a string or integer id. The integer types are defined as follows:
    #define    RT_CURSOR           1
    #define    RT_BITMAP           2
    #define    RT_ICON             3
    #define    RT_MENU             4
    #define    RT_DIALOG           5
    #define    RT_STRING           6
    #define    RT_FONTDIR          7
    #define    RT_FONT             8
    #define    RT_ACCELERATORS     9
    #define    RT_RCDATA           10
    #define    RT_MESSAGETABLE     11
    #define    RT_GROUP_CURSOR     12
    #define    RT_GROUP_ICON       14
    #define    RT_VERSION          16

For more information see: [1]

Also see TWAPI's resource module [2].