# Copyright (C) 2005, Giovanni Bajo
# Based on previous work under copyright (c) 2001, 2002 McMillan Enterprises, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

import win32api
import struct
import pywintypes

TEST=0

LOAD_LIBRARY_AS_DATAFILE = 2
RT_VERSION = 16

def getRaw(o):
    return str(buffer(o))


def decode(pathnm):
    h = win32api.LoadLibraryEx(pathnm, 0, LOAD_LIBRARY_AS_DATAFILE)
    nm = win32api.EnumResourceNames(h, RT_VERSION)[0]
    data = win32api.LoadResource(h, RT_VERSION, nm)
    vs = VSVersionInfo()
    j = vs.fromRaw(data)
    if TEST:
        print vs
        if data[:j] != vs.toRaw():
            print "AAAAAGGHHHH"
        glbls = {
            'VSVersionInfo': VSVersionInfo,
            'FixedFileInfo': FixedFileInfo,
            'StringFileInfo': StringFileInfo,
            'StringTable': StringTable,
            'StringStruct': StringStruct,
            'VarFileInfo': VarFileInfo,
            'VarStruct': VarStruct,
            }
        vs2 = eval(repr(vs), glbls)
        if vs.toRaw() != vs2.toRaw():
            print
            print 'reconstruction not the same!'
            print vs2
    win32api.FreeLibrary(h)
    return vs


class VSVersionInfo:
    """
    WORD  wLength;        // length of the VS_VERSION_INFO structure
    WORD  wValueLength;   // length of the Value member
    WORD  wType;          // 1 means text, 0 means binary
    WCHAR szKey[];        // Contains the Unicode string "VS_VERSION_INFO".
    WORD  Padding1[];
    VS_FIXEDFILEINFO Value;
    WORD  Padding2[];
    WORD  Children[];     // zero or more StringFileInfo or VarFileInfo
                          // structures (or both) that are children of the
                          // current version structure.
    """

    def __init__(self, ffi=None, kids=None):
        self.ffi = ffi
        self.kids = kids or []

    def fromRaw(self, data):
        i, (sublen, vallen, wType, nm) = parseCommon(data)
        #vallen is length of the ffi, typ is 0, nm is 'VS_VERSION_INFO'
        i = ((i + 3) / 4) * 4
        # now a VS_FIXEDFILEINFO
        self.ffi = FixedFileInfo()
        j = self.ffi.fromRaw(data, i)
        #print ffi
        if TEST and data[i:j] != self.ffi.toRaw():
            print "raw:", `data[i:j]`
            print "ffi:", `self.ffi.toRaw()`
        i = j
        while i < sublen:
            j = i
            i, (csublen, cvallen, ctyp, nm) = parseCommon(data, i)
            if str(nm).strip() == "StringFileInfo":
                sfi = StringFileInfo()
                k = sfi.fromRaw(csublen, cvallen, nm, data, i, j+csublen)
                if TEST and data[j:k] != sfi.toRaw():
                    rd = data[j:k]
                    sd = sfi.toRaw()
                    for x in range(0, len(rd), 16):
                        rds = rd[x:x+16]
                        sds = sd[x:x+16]
                        if rds != sds:
                            print "rd[%s:%s+16]: %r" % (x, x, rds)
                            print "sd[%s:%s+16]: %r" % (x, x, sds)
                            print
                    print ("raw: len %d, wLength %d"
                           % (len(rd), struct.unpack('h', rd[:2])[0]))
                    print ("sfi: len %d, wLength %d"
                           % (len(sd), struct.unpack('h', sd[:2])[0]))
                self.kids.append(sfi)
                i = k
            else:
                vfi = VarFileInfo()
                k = vfi.fromRaw(csublen, cvallen, nm, data, i, j+csublen)
                self.kids.append(vfi)
                if TEST and data[j:k] != vfi.toRaw():
                    print "raw:", `data[j:k]`
                    print "vfi:", `vfi.toRaw()`
                i = k
            i = j + csublen
            i = ((i + 3) / 4) * 4
        return i

    def toRaw(self):
        nm = pywintypes.Unicode('VS_VERSION_INFO')
        rawffi = self.ffi.toRaw()
        vallen = len(rawffi)
        typ = 0
        sublen = 6 + 2*len(nm) + 2
        pad = ''
        if sublen % 4:
            pad = '\000\000'
        sublen = sublen + len(pad) + vallen
        pad2 = ''
        if sublen % 4:
            pad2 = '\000\000'
        tmp = "".join([kid.toRaw() for kid in self.kids ])
        sublen = sublen + len(pad2) + len(tmp)
        return (struct.pack('hhh', sublen, vallen, typ)
                + getRaw(nm) + '\000\000' + pad + rawffi + pad2 + tmp)

    def __repr__(self, indent=''):
        indent = indent + '  '
        tmp = [kid.__repr__(indent+'  ')
               for kid in self.kids]
        tmp = ', \n'.join(tmp)
        return ("VSVersionInfo(\n%sffi=%s,\n%skids=[\n%s\n%s]\n)"
                % (indent, self.ffi.__repr__(indent), indent,
                   tmp, indent))


def parseCommon(data, start=0):
    i = start + 6
    (wLength, wValueLength, wType) = struct.unpack('3h', data[start:i])
    #print "wLength, wValueLength, wType, i:", wLength, wValueLength, wType, i
    i, szKey = parseUString(data, i, i+wLength)
    #i = ((i + 3) / 4) * 4
    #print `data[start+6:start+wLength]`
    return i, (wLength, wValueLength, wType, szKey)

def parseUString(data, start, limit):
    i = start
    while i < limit:
        if data[i:i+2] == '\000\000':
            break
        i += 2
    szKey = pywintypes.UnicodeFromRaw(data[start:i])
    i += 2
    #print "szKey:", repr(szKey), "(consumed", i-start, "bytes - to", i, ")"
    return i, szKey


class FixedFileInfo:
    """
    DWORD dwSignature;        //Contains the value 0xFEEFO4BD
    DWORD dwStrucVersion;     // binary version number of this structure.
                              // The high-order word of this member contains
                              // the major version number, and the low-order
                              // word contains the minor version number.
    DWORD dwFileVersionMS;    // most significant 32 bits of the file's binary
                              // version number
    DWORD dwFileVersionLS;    //
    DWORD dwProductVersionMS; // most significant 32 bits of the binary version
                              // number of the product with which this file was
                              // distributed
    DWORD dwProductVersionLS; //
    DWORD dwFileFlagsMask;    // bitmask that specifies the valid bits in
                              // dwFileFlags. A bit is valid only if it was
                              // defined when the file was created.
    DWORD dwFileFlags;        // VS_FF_DEBUG, VS_FF_PATCHED etc.
    DWORD dwFileOS;           // VOS_NT, VOS_WINDOWS32 etc.
    DWORD dwFileType;         // VFT_APP etc.
    DWORD dwFileSubtype;      // 0 unless VFT_DRV or VFT_FONT or VFT_VXD
    DWORD dwFileDateMS;
    DWORD dwFileDateLS;
    """
    def __init__(self, filevers=(0, 0, 0, 0), prodvers=(0, 0, 0, 0),
                 mask=0x3f, flags=0x0, OS=0x40004, fileType=0x1,
                 subtype=0x0, date=(0, 0)):
        self.sig = 0xfeef04bdL
        self.strucVersion = 0x10000
        self.fileVersionMS = (filevers[0] << 16) | (filevers[1] & 0xffff)
        self.fileVersionLS = (filevers[2] << 16) | (filevers[3] & 0xffff)
        self.productVersionMS = (prodvers[0] << 16) | (prodvers[1] & 0xffff)
        self.productVersionLS = (prodvers[2] << 16) | (prodvers[3] & 0xffff)
        self.fileFlagsMask = mask
        self.fileFlags = flags
        self.fileOS = OS
        self.fileType = fileType
        self.fileSubtype = subtype
        self.fileDateMS = date[0]
        self.fileDateLS = date[1]

    def fromRaw(self, data, i):
        (self.sig,
         self.strucVersion,
         self.fileVersionMS,
         self.fileVersionLS,
         self.productVersionMS,
         self.productVersionLS,
         self.fileFlagsMask,
         self.fileFlags,
         self.fileOS,
         self.fileType,
         self.fileSubtype,
         self.fileDateMS,
         self.fileDateLS) = struct.unpack('13l', data[i:i+52])
        return i+52

    def toRaw(self):
        return struct.pack('L12l', self.sig,
                             self.strucVersion,
                             self.fileVersionMS,
                             self.fileVersionLS,
                             self.productVersionMS,
                             self.productVersionLS,
                             self.fileFlagsMask,
                             self.fileFlags,
                             self.fileOS,
                             self.fileType,
                             self.fileSubtype,
                             self.fileDateMS,
                             self.fileDateLS)

    def __repr__(self, indent=''):
        fv = (self.fileVersionMS >> 16, self.fileVersionMS & 0xffff,
              self.fileVersionLS >> 16, self.fileVersionLS & 0xFFFF)
        pv = (self.productVersionMS >> 16, self.productVersionMS & 0xffff,
              self.productVersionLS >> 16, self.productVersionLS & 0xFFFF)
        fd = (self.fileDateMS, self.fileDateLS)
        tmp = ["FixedFileInfo(",
               "filevers=%s," % fv,
               "prodvers=%s," % pv,
               "mask=%s," % hex(self.fileFlagsMask),
               "flags=%s," % hex(self.fileFlags),
               "OS=%s," % hex(self.fileOS),
               "fileType=%s," % hex(self.fileType),
               "subtype=%s," % hex(self.fileSubtype),
               "date=%s" % fd,
               ")"
              ]
        return ('\n'+indent+'  ').join(tmp)


##StringFileInfo {
##};

class StringFileInfo:
    """
    WORD        wLength;      // length of the version resource
    WORD        wValueLength; // length of the Value member in the current
                              // VS_VERSION_INFO structure
    WORD        wType;        // 1 means text, 0 means binary
    WCHAR       szKey[];      // Contains the Unicode string 'StringFileInfo'.
    WORD        Padding[];
    StringTable Children[];   // list of zero or more String structures
    """
    def __init__(self, kids=None):
        self.name = "StringFileInfo"
        self.kids = kids or []

    def fromRaw(self, sublen, vallen, name, data, i, limit):
        self.name = name
        while i < limit:
            st = StringTable()
            j = st.fromRaw(data, i, limit)
            if TEST and data[i:j] != st.toRaw():
                rd = data[i:j]
                sd = st.toRaw()
                for x in range(0, len(rd), 16):
                    rds = rd[x:x+16]
                    sds = sd[x:x+16]
                    if rds != sds:
                        print "rd[%s:%s+16]: %r" % (x, x, rds)
                        print "sd[%s:%s+16]: %r" % (x, x, sds)
                        print
                print ("raw: len %d, wLength %d"
                       % (len(rd), struct.unpack('h', rd[:2])[0]))
                print (" st: len %d, wLength %d"
                       % (len(sd), struct.unpack('h', sd[:2])[0]))
            self.kids.append(st)
            i = j
        return i

    def toRaw(self):
        if type(self.name) is STRINGTYPE:
            self.name = pywintypes.Unicode(self.name)
        vallen = 0
        typ = 1
        sublen = 6 + 2*len(self.name) + 2
        pad = ''
        if sublen % 4:
            pad = '\000\000'
        tmp = ''.join([kid.toRaw() for kid in self.kids])
        sublen = sublen + len(pad) + len(tmp)
        if tmp[-2:] == '\000\000':
            sublen = sublen - 2
        return (struct.pack('hhh', sublen, vallen, typ)
                + getRaw(self.name) + '\000\000' + pad + tmp)

    def __repr__(self, indent=''):
        newindent = indent + '  '
        tmp = [kid.__repr__(newindent)
               for kid in self.kids]
        tmp = ', \n'.join(tmp)
        return ("%sStringFileInfo(\n%s[\n%s\n%s])"
                % (indent, newindent, tmp, newindent))


class StringTable:
    """
    WORD   wLength;
    WORD   wValueLength;
    WORD   wType;
    WCHAR  szKey[];
    String Children[];    // list of zero or more String structures.
    """
    def __init__(self, name=None, kids=None):
        self.name = name or ''
        self.kids = kids or []

    def fromRaw(self, data, i, limit):
        #print "Parsing StringTable"
        i, (cpsublen, cpwValueLength, cpwType, self.name) = parseCodePage(data, i, limit) # should be code page junk
        #i = ((i + 3) / 4) * 4
        while i < limit:
            ss = StringStruct()
            j = ss.fromRaw(data, i, limit)
            if TEST and data[i:j] != ss.toRaw():
                print "raw:", `data[i:j]`
                print " ss:", `ss.toRaw()`
            i = j
            self.kids.append(ss)
            i = ((i + 3) / 4) * 4
        return i

    def toRaw(self):
        if type(self.name) is STRINGTYPE:
            self.name = pywintypes.Unicode(self.name)
        vallen = 0
        typ = 1
        sublen = 6 + 2*len(self.name) + 2
        tmp = []
        for kid in self.kids:
            raw = kid.toRaw()
            if len(raw) % 4:
                raw = raw + '\000\000'
            tmp.append(raw)
        tmp = ''.join(tmp)
        sublen += len(tmp)
        if tmp[-2:] == '\000\000':
            sublen -= 2
        return (struct.pack('hhh', sublen, vallen, typ)
                + getRaw(self.name) + '\000\000' + tmp)

    def __repr__(self, indent=''):
        newindent = indent + '  '
        tmp = map(repr, self.kids)
        tmp = (',\n%s' % newindent).join(tmp)
        return ("%sStringTable(\n%s%r,\n%s[%s])"
                % (indent, newindent, self.name, newindent, tmp))


class StringStruct:
    """
    WORD   wLength;
    WORD   wValueLength;
    WORD   wType;
    WCHAR  szKey[];
    WORD   Padding[];
    String Value[];
    """
    def __init__(self, name=None, val=None):
        self.name = name or ''
        self.val = val or ''

    def fromRaw(self, data, i, limit):
        i, (sublen, vallen, typ, self.name) = parseCommon(data, i)
        limit = i + sublen
        i = ((i + 3) / 4) * 4
        i, self.val = parseUString(data, i, limit)
        return i

    def toRaw(self):
        if type(self.name) is STRINGTYPE:
            self.name = pywintypes.Unicode(self.name)
        if type(self.val) is STRINGTYPE:
            self.val = pywintypes.Unicode(self.val)
        vallen = len(self.val) + 1
        typ = 1
        sublen = 6 + 2*len(self.name) + 2
        pad = ''
        if sublen % 4:
            pad = '\000\000'
        sublen = sublen + len(pad) + 2*vallen
        return (struct.pack('hhh', sublen, vallen, typ)
                + getRaw(self.name) + '\000\000' + pad
                + getRaw(self.val) + '\000\000')

    def __repr__(self, indent=''):
        return "StringStruct(%r, %r)" % (self.name, self.val)


def parseCodePage(data, i, limit):
    #print "Parsing CodePage"
    i, (sublen, wValueLength, wType, nm) = parseCommon(data, i)
    #i = ((i + 3) / 4) * 4
    return i, (sublen, wValueLength, wType, nm)


class VarFileInfo:
    """
    WORD  wLength;        // length of the version resource
    WORD  wValueLength;   // length of the Value member in the current
                          // VS_VERSION_INFO structure
    WORD  wType;          // 1 means text, 0 means binary
    WCHAR szKey[];        // Contains the Unicode string 'VarFileInfo'.
    WORD  Padding[];
    Var   Children[];     // list of zero or more Var structures
    """
    def __init__(self, kids=None):
        self.kids = kids or []

    def fromRaw(self, sublen, vallen, name, data, i, limit):
        self.sublen = sublen
        self.vallen = vallen
        self.name = name
        i = ((i + 3) / 4) * 4
        while i < limit:
            vs = VarStruct()
            j = vs.fromRaw(data, i, limit)
            self.kids.append(vs)
            if TEST and data[i:j] != vs.toRaw():
                print "raw:", `data[i:j]`
                print "cmp:", `vs.toRaw()`
            i = j
        return i

    def toRaw(self):
        self.vallen = 0
        self.wType = 1
        self.name = pywintypes.Unicode('VarFileInfo')
        sublen = 6 + 2*len(self.name) + 2
        pad = ''
        if sublen % 4:
            pad = '\000\000'
        tmp = ''.join([kid.toRaw() for kid in self.kids])
        self.sublen = sublen + len(pad) + len(tmp)
        return (struct.pack('hhh', self.sublen, self.vallen, self.wType)
                + getRaw(self.name) + '\000\000' + pad + tmp)

    def __repr__(self, indent=''):
        tmp = map(repr, self.kids)
        return "%sVarFileInfo([%s])" % (indent, ', '.join(tmp))


STRINGTYPE = type('')

class VarStruct:
    """
    WORD  wLength;        // length of the version resource
    WORD  wValueLength;   // length of the Value member in the current
                          // VS_VERSION_INFO structure
    WORD  wType;          // 1 means text, 0 means binary
    WCHAR szKey[];        // Contains the Unicode string 'Translation'
                          // or a user-defined key string value
    WORD  Padding[];      //
    WORD  Value[];        // list of one or more values that are language
                          // and code-page identifiers
    """
    def __init__(self, name=None, kids=None):
        self.name = name or ''
        self.kids = kids or []

    def fromRaw(self, data, i, limit):
        i, (self.sublen, self.wValueLength, self.wType, self.name) = parseCommon(data, i)
        i = ((i + 3) / 4) * 4
        for j in range(self.wValueLength/2):
            kid = struct.unpack('h', data[i:i+2])[0]
            self.kids.append(kid)
            i += 2
        return i

    def toRaw(self):
        self.wValueLength = len(self.kids) * 2
        self.wType = 0
        if type(self.name) is STRINGTYPE:
            self.name = pywintypes.Unicode(self.name)
        sublen = 6 + 2*len(self.name) + 2
        pad = ''
        if sublen % 4:
            pad = '\000\000'
        self.sublen = sublen + len(pad) + self.wValueLength
        tmp = ''.join([struct.pack('h', kid) for kid in self.kids])
        return (struct.pack('hhh', self.sublen, self.wValueLength, self.wType)
                + getRaw(self.name) + '\000\000' + pad + tmp)

    def __repr__(self, indent=''):
        return "VarStruct(%r, %r)" % (self.name, self.kids)


def SetVersion(exenm, versionfile):
    txt = open(versionfile, 'rU').read()
    vs = eval(txt)
    hdst = win32api.BeginUpdateResource(exenm, 0)
    win32api.UpdateResource(hdst, RT_VERSION, 1, vs.toRaw())
    win32api.EndUpdateResource (hdst, 0)


if __name__ == '__main__':
    import sys
    TEST = 1
    if len(sys.argv) < 2:
        decode('c:/Program Files/Netscape/Communicator/Program/netscape.exe')
    else:
        print "Examining", sys.argv[1]
        decode(sys.argv[1])
