1# -*- coding: utf-8 -*- 2#----------------------------------------------------------------------------- 3# Copyright (c) 2013-2019, PyInstaller Development Team. 4# 5# Distributed under the terms of the GNU General Public License with exception 6# for distributing bootloader. 7# 8# The full license is in the file COPYING.txt, distributed with this software. 9#----------------------------------------------------------------------------- 10 11 12import codecs 13import struct 14 15from ...compat import is_py3, text_read_mode, win32api 16 17# ::TODO:: #1920 revert to using pypi version 18import pefile 19 20 21# TODO implement read/write version information with pefile library. 22# PE version info doc: http://msdn.microsoft.com/en-us/library/ms646981.aspx 23def pefile_read_version(filename): 24 """ 25 Return structure like: 26 27 { 28 # Translation independent information. 29 # VS_FIXEDFILEINFO - Contains version information about a file. This information is language and code page independent. 30 u'FileVersion': (1, 2, 3, 4), 31 u'ProductVersion': (9, 10, 11, 12), 32 33 # PE files might contain several translations of version information. 34 # VS_VERSIONINFO - Depicts the organization of data in a file-version resource. It is the root structure that contains all other file-version information structures. 35 u'translations': { 36 'lang_id1' : { 37 u'Comments': u'日本語, Unicode 対応.', 38 u'CompanyName': u'your company.', 39 u'FileDescription': u'your file desc.', 40 u'FileVersion': u'1, 2, 3, 4', 41 u'InternalName': u'your internal name.', 42 u'LegalCopyright': u'your legal copyright.', 43 u'LegalTrademarks': u'your legal trademarks.', 44 u'OriginalFilename': u'your original filename.', 45 u'PrivateBuild': u'5, 6, 7, 8', 46 u'ProductName': u'your product name', 47 u'ProductVersion': u'9, 10, 11, 12', 48 u'SpecialBuild': u'13, 14, 15, 16', 49 }, 50 51 'lang_id2' : { 52 ... 53 } 54 } 55 } 56 57 Version info can contain multiple languages. 58 """ 59 # TODO 60 vers = { 61 'FileVersion': (0, 0, 0, 0), 62 'ProductVersion': (0, 0, 0, 0), 63 'translations': { 64 'lang_id1': { 65 'Comments': '', 66 'CompanyName': '', 67 'FileDescription': '', 68 'FileVersion': '', 69 'InternalName': '', 70 'LegalCopyright': '', 71 'LegalTrademarks': '', 72 'OriginalFilename': '', 73 'PrivateBuild': '', 74 'ProductName': '', 75 'ProductVersion': '', 76 'SpecialBuild': '', 77 } 78 } 79 } 80 pe = pefile.PE(filename) 81 #ffi = pe.VS_FIXEDFILEINFO 82 #vers['FileVersion'] = (ffi.FileVersionMS >> 16, ffi.FileVersionMS & 0xFFFF, ffi.FileVersionLS >> 16, ffi.FileVersionLS & 0xFFFF) 83 #vers['ProductVersion'] = (ffi.ProductVersionMS >> 16, ffi.ProductVersionMS & 0xFFFF, ffi.ProductVersionLS >> 16, ffi.ProductVersionLS & 0xFFFF) 84 #print(pe.VS_FIXEDFILEINFO.FileVersionMS) 85 # TODO Only first available language is used for now. 86 #vers = pe.FileInfo[0].StringTable[0].entries 87 from pprint import pprint 88 pprint(pe.VS_FIXEDFILEINFO) 89 print(dir(pe.VS_FIXEDFILEINFO)) 90 print(repr(pe.VS_FIXEDFILEINFO)) 91 print(pe.dump_info()) 92 pe.close() 93 return vers 94 95 96 97# Ensures no code from the executable is executed. 98LOAD_LIBRARY_AS_DATAFILE = 2 99 100 101if is_py3: 102 def getRaw(text): 103 """ 104 Encodes text as UTF-16LE (Microsoft 'Unicode') for use in structs. 105 `bytes` is not allowed on Python 3. 106 """ 107 return text.encode('UTF-16LE') 108else: 109 def getRaw(text): 110 """ 111 Encodes text as UTF-16LE (Microsoft 'Unicode') for use in structs. 112 `unicode` is encoded to UTF-16LE, and `str` is first decoded from 113 `mbcs` before being re-encoded. 114 """ 115 if isinstance(text, str): 116 text = text.decode('mbcs', errors='replace') 117 118 return text.encode('UTF-16LE') 119 120 121def decode(pathnm): 122 h = win32api.LoadLibraryEx(pathnm, 0, LOAD_LIBRARY_AS_DATAFILE) 123 res = win32api.EnumResourceNames(h, pefile.RESOURCE_TYPE['RT_VERSION']) 124 if not len(res): 125 return None 126 data = win32api.LoadResource(h, pefile.RESOURCE_TYPE['RT_VERSION'], 127 res[0]) 128 vs = VSVersionInfo() 129 j = vs.fromRaw(data) 130 win32api.FreeLibrary(h) 131 return vs 132 133 134def nextDWord(offset): 135 """ Align `offset` to the next 4-byte boundary """ 136 return ((offset + 3) >> 2) << 2 137 138if is_py3: 139 def _py3_str_compat(cls): 140 """ 141 On Python 3, the special method __str__ is equivalent to the Python 2 142 method __unicode__. 143 """ 144 cls.__str__ = cls.__unicode__ 145 return cls 146else: 147 def _py3_str_compat(cls): 148 return cls 149 150@_py3_str_compat 151class VSVersionInfo: 152 """ 153 WORD wLength; // length of the VS_VERSION_INFO structure 154 WORD wValueLength; // length of the Value member 155 WORD wType; // 1 means text, 0 means binary 156 WCHAR szKey[]; // Contains the Unicode string "VS_VERSION_INFO". 157 WORD Padding1[]; 158 VS_FIXEDFILEINFO Value; 159 WORD Padding2[]; 160 WORD Children[]; // zero or more StringFileInfo or VarFileInfo 161 // structures (or both) that are children of the 162 // current version structure. 163 """ 164 165 def __init__(self, ffi=None, kids=None): 166 self.ffi = ffi 167 self.kids = kids or [] 168 169 def fromRaw(self, data): 170 i, (sublen, vallen, wType, nm) = parseCommon(data) 171 #vallen is length of the ffi, typ is 0, nm is 'VS_VERSION_INFO'. 172 i = nextDWord(i) 173 # Now a VS_FIXEDFILEINFO 174 self.ffi = FixedFileInfo() 175 j = self.ffi.fromRaw(data, i) 176 i = j 177 while i < sublen: 178 j = i 179 i, (csublen, cvallen, ctyp, nm) = parseCommon(data, i) 180 if nm.strip() == u'StringFileInfo': 181 sfi = StringFileInfo() 182 k = sfi.fromRaw(csublen, cvallen, nm, data, i, j+csublen) 183 self.kids.append(sfi) 184 i = k 185 else: 186 vfi = VarFileInfo() 187 k = vfi.fromRaw(csublen, cvallen, nm, data, i, j+csublen) 188 self.kids.append(vfi) 189 i = k 190 i = j + csublen 191 i = nextDWord(i) 192 return i 193 194 def toRaw(self): 195 raw_name = getRaw(u'VS_VERSION_INFO') 196 rawffi = self.ffi.toRaw() 197 vallen = len(rawffi) 198 typ = 0 199 sublen = 6 + len(raw_name) + 2 200 pad = b'' 201 if sublen % 4: 202 pad = b'\000\000' 203 sublen = sublen + len(pad) + vallen 204 pad2 = b'' 205 if sublen % 4: 206 pad2 = b'\000\000' 207 tmp = b''.join([kid.toRaw() for kid in self.kids ]) 208 sublen = sublen + len(pad2) + len(tmp) 209 return (struct.pack('hhh', sublen, vallen, typ) 210 + raw_name + b'\000\000' + pad + rawffi + pad2 + tmp) 211 212 def __unicode__(self, indent=u''): 213 indent = indent + u' ' 214 tmp = [kid.__unicode__(indent+u' ') 215 for kid in self.kids] 216 tmp = u', \n'.join(tmp) 217 return (u"""# UTF-8 218# 219# For more details about fixed file info 'ffi' see: 220# http://msdn.microsoft.com/en-us/library/ms646997.aspx 221VSVersionInfo( 222%sffi=%s, 223%skids=[ 224%s 225%s] 226) 227""" % (indent, self.ffi.__unicode__(indent), indent, tmp, indent)) 228 229 230def parseCommon(data, start=0): 231 i = start + 6 232 (wLength, wValueLength, wType) = struct.unpack('3h', data[start:i]) 233 i, text = parseUString(data, i, i+wLength) 234 return i, (wLength, wValueLength, wType, text) 235 236def parseUString(data, start, limit): 237 i = start 238 while i < limit: 239 if data[i:i+2] == b'\000\000': 240 break 241 i += 2 242 text = data[start:i].decode('UTF-16LE') 243 i += 2 244 return i, text 245 246@_py3_str_compat 247class FixedFileInfo: 248 """ 249 DWORD dwSignature; //Contains the value 0xFEEFO4BD 250 DWORD dwStrucVersion; // binary version number of this structure. 251 // The high-order word of this member contains 252 // the major version number, and the low-order 253 // word contains the minor version number. 254 DWORD dwFileVersionMS; // most significant 32 bits of the file's binary 255 // version number 256 DWORD dwFileVersionLS; // 257 DWORD dwProductVersionMS; // most significant 32 bits of the binary version 258 // number of the product with which this file was 259 // distributed 260 DWORD dwProductVersionLS; // 261 DWORD dwFileFlagsMask; // bitmask that specifies the valid bits in 262 // dwFileFlags. A bit is valid only if it was 263 // defined when the file was created. 264 DWORD dwFileFlags; // VS_FF_DEBUG, VS_FF_PATCHED etc. 265 DWORD dwFileOS; // VOS_NT, VOS_WINDOWS32 etc. 266 DWORD dwFileType; // VFT_APP etc. 267 DWORD dwFileSubtype; // 0 unless VFT_DRV or VFT_FONT or VFT_VXD 268 DWORD dwFileDateMS; 269 DWORD dwFileDateLS; 270 """ 271 def __init__(self, filevers=(0, 0, 0, 0), prodvers=(0, 0, 0, 0), 272 mask=0x3f, flags=0x0, OS=0x40004, fileType=0x1, 273 subtype=0x0, date=(0, 0)): 274 self.sig = 0xfeef04bd 275 self.strucVersion = 0x10000 276 self.fileVersionMS = (filevers[0] << 16) | (filevers[1] & 0xffff) 277 self.fileVersionLS = (filevers[2] << 16) | (filevers[3] & 0xffff) 278 self.productVersionMS = (prodvers[0] << 16) | (prodvers[1] & 0xffff) 279 self.productVersionLS = (prodvers[2] << 16) | (prodvers[3] & 0xffff) 280 self.fileFlagsMask = mask 281 self.fileFlags = flags 282 self.fileOS = OS 283 self.fileType = fileType 284 self.fileSubtype = subtype 285 self.fileDateMS = date[0] 286 self.fileDateLS = date[1] 287 288 def fromRaw(self, data, i): 289 (self.sig, 290 self.strucVersion, 291 self.fileVersionMS, 292 self.fileVersionLS, 293 self.productVersionMS, 294 self.productVersionLS, 295 self.fileFlagsMask, 296 self.fileFlags, 297 self.fileOS, 298 self.fileType, 299 self.fileSubtype, 300 self.fileDateMS, 301 self.fileDateLS) = struct.unpack('13l', data[i:i+52]) 302 return i+52 303 304 def toRaw(self): 305 return struct.pack('L12l', self.sig, 306 self.strucVersion, 307 self.fileVersionMS, 308 self.fileVersionLS, 309 self.productVersionMS, 310 self.productVersionLS, 311 self.fileFlagsMask, 312 self.fileFlags, 313 self.fileOS, 314 self.fileType, 315 self.fileSubtype, 316 self.fileDateMS, 317 self.fileDateLS) 318 319 def __unicode__(self, indent=u''): 320 fv = (self.fileVersionMS >> 16, self.fileVersionMS & 0xffff, 321 self.fileVersionLS >> 16, self.fileVersionLS & 0xFFFF) 322 pv = (self.productVersionMS >> 16, self.productVersionMS & 0xffff, 323 self.productVersionLS >> 16, self.productVersionLS & 0xFFFF) 324 fd = (self.fileDateMS, self.fileDateLS) 325 tmp = [u'FixedFileInfo(', 326 u'# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)', 327 u'# Set not needed items to zero 0.', 328 u'filevers=%s,' % (fv,), 329 u'prodvers=%s,' % (pv,), 330 u"# Contains a bitmask that specifies the valid bits 'flags'r", 331 u'mask=%s,' % hex(self.fileFlagsMask), 332 u'# Contains a bitmask that specifies the Boolean attributes of the file.', 333 u'flags=%s,' % hex(self.fileFlags), 334 u'# The operating system for which this file was designed.', 335 u'# 0x4 - NT and there is no need to change it.', 336 u'OS=%s,' % hex(self.fileOS), 337 u'# The general type of file.', 338 u'# 0x1 - the file is an application.', 339 u'fileType=%s,' % hex(self.fileType), 340 u'# The function of the file.', 341 u'# 0x0 - the function is not defined for this fileType', 342 u'subtype=%s,' % hex(self.fileSubtype), 343 u'# Creation date and time stamp.', 344 u'date=%s' % (fd,), 345 u')' 346 ] 347 return (u'\n'+indent+u' ').join(tmp) 348 349 350@_py3_str_compat 351class StringFileInfo(object): 352 """ 353 WORD wLength; // length of the version resource 354 WORD wValueLength; // length of the Value member in the current 355 // VS_VERSION_INFO structure 356 WORD wType; // 1 means text, 0 means binary 357 WCHAR szKey[]; // Contains the Unicode string 'StringFileInfo'. 358 WORD Padding[]; 359 StringTable Children[]; // list of zero or more String structures 360 """ 361 def __init__(self, kids=None): 362 self.name = u'StringFileInfo' 363 self.kids = kids or [] 364 365 def fromRaw(self, sublen, vallen, name, data, i, limit): 366 self.name = name 367 while i < limit: 368 st = StringTable() 369 j = st.fromRaw(data, i, limit) 370 self.kids.append(st) 371 i = j 372 return i 373 374 def toRaw(self): 375 raw_name = getRaw(self.name) 376 vallen = 0 377 typ = 1 378 sublen = 6 + len(raw_name) + 2 379 pad = b'' 380 if sublen % 4: 381 pad = b'\000\000' 382 tmp = b''.join([kid.toRaw() for kid in self.kids]) 383 sublen = sublen + len(pad) + len(tmp) 384 return (struct.pack('hhh', sublen, vallen, typ) 385 + raw_name + b'\000\000' + pad + tmp) 386 387 def __unicode__(self, indent=u''): 388 newindent = indent + u' ' 389 tmp = [kid.__unicode__(newindent) 390 for kid in self.kids] 391 tmp = u', \n'.join(tmp) 392 return (u'%sStringFileInfo(\n%s[\n%s\n%s])' 393 % (indent, newindent, tmp, newindent)) 394 395 396@_py3_str_compat 397class StringTable: 398 """ 399 WORD wLength; 400 WORD wValueLength; 401 WORD wType; 402 WCHAR szKey[]; 403 String Children[]; // list of zero or more String structures. 404 """ 405 def __init__(self, name=None, kids=None): 406 self.name = name or u'' 407 self.kids = kids or [] 408 409 def fromRaw(self, data, i, limit): 410 i, (cpsublen, cpwValueLength, cpwType, self.name) = parseCodePage(data, i, limit) # should be code page junk 411 i = nextDWord(i) 412 while i < limit: 413 ss = StringStruct() 414 j = ss.fromRaw(data, i, limit) 415 i = j 416 self.kids.append(ss) 417 i = nextDWord(i) 418 return i 419 420 def toRaw(self): 421 raw_name = getRaw(self.name) 422 vallen = 0 423 typ = 1 424 sublen = 6 + len(raw_name) + 2 425 tmp = [] 426 for kid in self.kids: 427 raw = kid.toRaw() 428 if len(raw) % 4: 429 raw = raw + b'\000\000' 430 tmp.append(raw) 431 tmp = b''.join(tmp) 432 sublen += len(tmp) 433 return (struct.pack('hhh', sublen, vallen, typ) 434 + raw_name + b'\000\000' + tmp) 435 436 def __unicode__(self, indent=u''): 437 newindent = indent + u' ' 438 tmp = (u',\n%s' % newindent).join(u'%s' % (kid,) for kid in self.kids) 439 return (u"%sStringTable(\n%su'%s',\n%s[%s])" 440 % (indent, newindent, self.name, newindent, tmp)) 441 442 443@_py3_str_compat 444class StringStruct: 445 """ 446 WORD wLength; 447 WORD wValueLength; 448 WORD wType; 449 WCHAR szKey[]; 450 WORD Padding[]; 451 String Value[]; 452 """ 453 def __init__(self, name=None, val=None): 454 self.name = name or u'' 455 self.val = val or u'' 456 457 def fromRaw(self, data, i, limit): 458 i, (sublen, vallen, typ, self.name) = parseCommon(data, i) 459 limit = i + sublen 460 i = nextDWord(i) 461 i, self.val = parseUString(data, i, limit) 462 return i 463 464 def toRaw(self): 465 raw_name = getRaw(self.name) 466 raw_val = getRaw(self.val) 467 # TODO document the size of vallen and sublen. 468 vallen = len(raw_val) + 2 469 typ = 1 470 sublen = 6 + len(raw_name) + 2 471 pad = b'' 472 if sublen % 4: 473 pad = b'\000\000' 474 sublen = sublen + len(pad) + vallen 475 abcd = (struct.pack('hhh', sublen, vallen, typ) 476 + raw_name + b'\000\000' + pad 477 + raw_val + b'\000\000') 478 return abcd 479 480 def __unicode__(self, indent=''): 481 return u"StringStruct(u'%s', u'%s')" % (self.name, self.val) 482 483 484def parseCodePage(data, i, limit): 485 i, (sublen, wValueLength, wType, nm) = parseCommon(data, i) 486 return i, (sublen, wValueLength, wType, nm) 487 488 489@_py3_str_compat 490class VarFileInfo: 491 """ 492 WORD wLength; // length of the version resource 493 WORD wValueLength; // length of the Value member in the current 494 // VS_VERSION_INFO structure 495 WORD wType; // 1 means text, 0 means binary 496 WCHAR szKey[]; // Contains the Unicode string 'VarFileInfo'. 497 WORD Padding[]; 498 Var Children[]; // list of zero or more Var structures 499 """ 500 def __init__(self, kids=None): 501 self.kids = kids or [] 502 503 def fromRaw(self, sublen, vallen, name, data, i, limit): 504 self.sublen = sublen 505 self.vallen = vallen 506 self.name = name 507 i = nextDWord(i) 508 while i < limit: 509 vs = VarStruct() 510 j = vs.fromRaw(data, i, limit) 511 self.kids.append(vs) 512 i = j 513 return i 514 515 def toRaw(self): 516 self.vallen = 0 517 self.wType = 1 518 self.name = u'VarFileInfo' 519 raw_name = getRaw(self.name) 520 sublen = 6 + len(raw_name) + 2 521 pad = b'' 522 if sublen % 4: 523 pad = b'\000\000' 524 tmp = b''.join([kid.toRaw() for kid in self.kids]) 525 self.sublen = sublen + len(pad) + len(tmp) 526 return (struct.pack('hhh', self.sublen, self.vallen, self.wType) 527 + raw_name + b'\000\000' + pad + tmp) 528 529 def __unicode__(self, indent=''): 530 return "%sVarFileInfo([%s])" % (indent, ', '.join(u'%s' % (kid,) for kid in self.kids)) 531 532 533@_py3_str_compat 534class VarStruct: 535 """ 536 WORD wLength; // length of the version resource 537 WORD wValueLength; // length of the Value member in the current 538 // VS_VERSION_INFO structure 539 WORD wType; // 1 means text, 0 means binary 540 WCHAR szKey[]; // Contains the Unicode string 'Translation' 541 // or a user-defined key string value 542 WORD Padding[]; // 543 WORD Value[]; // list of one or more values that are language 544 // and code-page identifiers 545 """ 546 def __init__(self, name=None, kids=None): 547 self.name = name or u'' 548 self.kids = kids or [] 549 550 def fromRaw(self, data, i, limit): 551 i, (self.sublen, self.wValueLength, self.wType, self.name) = parseCommon(data, i) 552 i = nextDWord(i) 553 for j in range(0, self.wValueLength, 2): 554 kid = struct.unpack('h', data[i:i+2])[0] 555 self.kids.append(kid) 556 i += 2 557 return i 558 559 def toRaw(self): 560 self.wValueLength = len(self.kids) * 2 561 self.wType = 0 562 raw_name = getRaw(self.name) 563 sublen = 6 + len(raw_name) + 2 564 pad = b'' 565 if sublen % 4: 566 pad = b'\000\000' 567 self.sublen = sublen + len(pad) + self.wValueLength 568 tmp = b''.join([struct.pack('h', kid) for kid in self.kids]) 569 return (struct.pack('hhh', self.sublen, self.wValueLength, self.wType) 570 + raw_name + b'\000\000' + pad + tmp) 571 572 def __unicode__(self, indent=u''): 573 return u"VarStruct(u'%s', %r)" % (self.name, self.kids) 574 575 576def SetVersion(exenm, versionfile): 577 if isinstance(versionfile, VSVersionInfo): 578 vs = versionfile 579 else: 580 with codecs.open(versionfile, text_read_mode, 'utf-8') as fp: 581 txt = fp.read() 582 vs = eval(txt) 583 584 # Remember overlay 585 pe = pefile.PE(exenm, fast_load=True) 586 overlay_before = pe.get_overlay() 587 pe.close() 588 589 hdst = win32api.BeginUpdateResource(exenm, 0) 590 win32api.UpdateResource(hdst, pefile.RESOURCE_TYPE['RT_VERSION'], 1, vs.toRaw()) 591 win32api.EndUpdateResource (hdst, 0) 592 593 if overlay_before: 594 # Check if the overlay is still present 595 pe = pefile.PE(exenm, fast_load=True) 596 overlay_after = pe.get_overlay() 597 pe.close() 598 599 # If the update removed the overlay data, re-append it 600 if not overlay_after: 601 with open(exenm, 'ab') as exef: 602 exef.write(overlay_before) 603