Remove hardcoded libpython binaries and add debug step
All checks were successful
build / build-linux (push) Successful in 16s
All checks were successful
build / build-linux (push) Successful in 16s
This commit is contained in:
1
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/__init__.py
Executable file
1
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
__author__ = 'martin'
|
||||
251
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/icon.py
Executable file
251
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/icon.py
Executable file
@@ -0,0 +1,251 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2013-2023, PyInstaller Development Team.
|
||||
#
|
||||
# Distributed under the terms of the GNU General Public License (version 2
|
||||
# or later) with exception for distributing the bootloader.
|
||||
#
|
||||
# The full license is in the file COPYING.txt, distributed with this software.
|
||||
#
|
||||
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
|
||||
#-----------------------------------------------------------------------------
|
||||
"""
|
||||
The code in this module supports the --icon parameter on Windows.
|
||||
(For --icon support under macOS, see building/osx.py.)
|
||||
|
||||
The only entry point, called from api.py, is CopyIcons(), below. All the elaborate structure of classes that follows
|
||||
is used to support the operation of CopyIcons_FromIco(). None of these classes and globals are referenced outside
|
||||
this module.
|
||||
"""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import struct
|
||||
|
||||
import PyInstaller.log as logging
|
||||
from PyInstaller import config
|
||||
from PyInstaller.compat import pywintypes, win32api
|
||||
from PyInstaller.building.icon import normalize_icon_type
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RT_ICON = 3
|
||||
RT_GROUP_ICON = 14
|
||||
LOAD_LIBRARY_AS_DATAFILE = 2
|
||||
|
||||
|
||||
class Structure:
|
||||
def __init__(self):
|
||||
size = self._sizeInBytes = struct.calcsize(self._format_)
|
||||
self._fields_ = list(struct.unpack(self._format_, b'\000' * size))
|
||||
indexes = self._indexes_ = {}
|
||||
for i, nm in enumerate(self._names_):
|
||||
indexes[nm] = i
|
||||
|
||||
def dump(self):
|
||||
logger.info("DUMP of %s", self)
|
||||
for name in self._names_:
|
||||
if not name.startswith('_'):
|
||||
logger.info("%20s = %s", name, getattr(self, name))
|
||||
logger.info("")
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in self._names_:
|
||||
index = self._indexes_[name]
|
||||
return self._fields_[index]
|
||||
try:
|
||||
return self.__dict__[name]
|
||||
except KeyError as e:
|
||||
raise AttributeError(name) from e
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in self._names_:
|
||||
index = self._indexes_[name]
|
||||
self._fields_[index] = value
|
||||
else:
|
||||
self.__dict__[name] = value
|
||||
|
||||
def tostring(self):
|
||||
return struct.pack(self._format_, *self._fields_)
|
||||
|
||||
def fromfile(self, file):
|
||||
data = file.read(self._sizeInBytes)
|
||||
self._fields_ = list(struct.unpack(self._format_, data))
|
||||
|
||||
|
||||
class ICONDIRHEADER(Structure):
|
||||
_names_ = "idReserved", "idType", "idCount"
|
||||
_format_ = "hhh"
|
||||
|
||||
|
||||
class ICONDIRENTRY(Structure):
|
||||
_names_ = ("bWidth", "bHeight", "bColorCount", "bReserved", "wPlanes", "wBitCount", "dwBytesInRes", "dwImageOffset")
|
||||
_format_ = "bbbbhhii"
|
||||
|
||||
|
||||
class GRPICONDIR(Structure):
|
||||
_names_ = "idReserved", "idType", "idCount"
|
||||
_format_ = "hhh"
|
||||
|
||||
|
||||
class GRPICONDIRENTRY(Structure):
|
||||
_names_ = ("bWidth", "bHeight", "bColorCount", "bReserved", "wPlanes", "wBitCount", "dwBytesInRes", "nID")
|
||||
_format_ = "bbbbhhih"
|
||||
|
||||
|
||||
# An IconFile instance is created for each .ico file given.
|
||||
class IconFile:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
try:
|
||||
# The path is from the user parameter, don't trust it.
|
||||
file = open(self.path, "rb")
|
||||
except OSError:
|
||||
# The icon file can't be opened for some reason. Stop the
|
||||
# program with an informative message.
|
||||
raise SystemExit(f'ERROR: Unable to open icon file {self.path}!')
|
||||
with file:
|
||||
self.entries = []
|
||||
self.images = []
|
||||
header = self.header = ICONDIRHEADER()
|
||||
header.fromfile(file)
|
||||
for i in range(header.idCount):
|
||||
entry = ICONDIRENTRY()
|
||||
entry.fromfile(file)
|
||||
self.entries.append(entry)
|
||||
for e in self.entries:
|
||||
file.seek(e.dwImageOffset, 0)
|
||||
self.images.append(file.read(e.dwBytesInRes))
|
||||
|
||||
def grp_icon_dir(self):
|
||||
return self.header.tostring()
|
||||
|
||||
def grp_icondir_entries(self, id=1):
|
||||
data = b''
|
||||
for entry in self.entries:
|
||||
e = GRPICONDIRENTRY()
|
||||
for n in e._names_[:-1]:
|
||||
setattr(e, n, getattr(entry, n))
|
||||
e.nID = id
|
||||
id = id + 1
|
||||
data = data + e.tostring()
|
||||
return data
|
||||
|
||||
|
||||
def CopyIcons_FromIco(dstpath, srcpath, id=1):
|
||||
"""
|
||||
Use the Win API UpdateResource facility to apply the icon resource(s) to the .exe file.
|
||||
|
||||
:param str dstpath: absolute path of the .exe file being built.
|
||||
:param str srcpath: list of 1 or more .ico file paths
|
||||
"""
|
||||
icons = map(IconFile, srcpath)
|
||||
logger.debug("Copying icons from %s", srcpath)
|
||||
|
||||
hdst = win32api.BeginUpdateResource(dstpath, 0)
|
||||
|
||||
iconid = 1
|
||||
# Each step in the following enumerate() will instantiate an IconFile object, as a result of deferred execution
|
||||
# of the map() above.
|
||||
for i, f in enumerate(icons):
|
||||
data = f.grp_icon_dir()
|
||||
data = data + f.grp_icondir_entries(iconid)
|
||||
win32api.UpdateResource(hdst, RT_GROUP_ICON, i + 1, data)
|
||||
logger.debug("Writing RT_GROUP_ICON %d resource with %d bytes", i + 1, len(data))
|
||||
for data in f.images:
|
||||
win32api.UpdateResource(hdst, RT_ICON, iconid, data)
|
||||
logger.debug("Writing RT_ICON %d resource with %d bytes", iconid, len(data))
|
||||
iconid = iconid + 1
|
||||
|
||||
win32api.EndUpdateResource(hdst, 0)
|
||||
|
||||
|
||||
def CopyIcons(dstpath, srcpath):
|
||||
"""
|
||||
Called from building/api.py to handle icons. If the input was by --icon on the command line, srcpath is a single
|
||||
string. However, it is possible to modify the spec file adding icon=['foo.ico','bar.ico'] to the EXE() statement.
|
||||
In that case, srcpath is a list of strings.
|
||||
|
||||
The string format is either path-to-.ico or path-to-.exe,n for n an integer resource index in the .exe. In either
|
||||
case, the path can be relative or absolute.
|
||||
"""
|
||||
|
||||
if isinstance(srcpath, (str, os.PathLike)):
|
||||
# Just a single string, make it a one-element list.
|
||||
srcpath = [srcpath]
|
||||
# Convert possible PathLike elements to strings to allow the splitter function to work.
|
||||
srcpath = [str(path) for path in srcpath]
|
||||
|
||||
def splitter(s):
|
||||
"""
|
||||
Convert "pathname" to tuple ("pathname", None)
|
||||
Convert "pathname,n" to tuple ("pathname", n)
|
||||
"""
|
||||
try:
|
||||
srcpath, index = s.split(',')
|
||||
return srcpath.strip(), int(index)
|
||||
except ValueError:
|
||||
return s, None
|
||||
|
||||
# split all the items in the list into tuples as above.
|
||||
srcpath = list(map(splitter, srcpath))
|
||||
|
||||
if len(srcpath) > 1:
|
||||
# More than one icon source given. We currently handle multiple icons by calling CopyIcons_FromIco(), which only
|
||||
# allows .ico, but will convert to that format if needed.
|
||||
#
|
||||
# Note that a ",index" on a .ico is just ignored in the single or multiple case.
|
||||
srcs = []
|
||||
for s in srcpath:
|
||||
srcs.append(normalize_icon_type(s[0], ("ico",), "ico", config.CONF["workpath"]))
|
||||
return CopyIcons_FromIco(dstpath, srcs)
|
||||
|
||||
# Just one source given.
|
||||
srcpath, index = srcpath[0]
|
||||
|
||||
# Makes sure the icon exists and attempts to convert to the proper format if applicable
|
||||
srcpath = normalize_icon_type(srcpath, ("exe", "ico"), "ico", config.CONF["workpath"])
|
||||
|
||||
srcext = os.path.splitext(srcpath)[1]
|
||||
|
||||
# Handle the simple case of foo.ico, ignoring any index.
|
||||
if srcext.lower() == '.ico':
|
||||
return CopyIcons_FromIco(dstpath, [srcpath])
|
||||
|
||||
# Single source is not .ico, presumably it is .exe (and if not, some error will occur).
|
||||
if index is not None:
|
||||
logger.debug("Copying icon from %s, %d", srcpath, index)
|
||||
else:
|
||||
logger.debug("Copying icons from %s", srcpath)
|
||||
|
||||
try:
|
||||
# Attempt to load the .ico or .exe containing the icon into memory using the same mechanism as if it were a DLL.
|
||||
# If this fails for any reason (for example if the file does not exist or is not a .ico/.exe) then LoadLibraryEx
|
||||
# returns a null handle and win32api raises a unique exception with a win error code and a string.
|
||||
hsrc = win32api.LoadLibraryEx(srcpath, 0, LOAD_LIBRARY_AS_DATAFILE)
|
||||
except pywintypes.error as W32E:
|
||||
# We could continue with no icon (i.e., just return), but it seems best to terminate the build with a message.
|
||||
raise SystemExit(
|
||||
"ERROR: Unable to load icon file {}\n {} (Error code {})".format(srcpath, W32E.strerror, W32E.winerror)
|
||||
)
|
||||
hdst = win32api.BeginUpdateResource(dstpath, 0)
|
||||
if index is None:
|
||||
grpname = win32api.EnumResourceNames(hsrc, RT_GROUP_ICON)[0]
|
||||
elif index >= 0:
|
||||
grpname = win32api.EnumResourceNames(hsrc, RT_GROUP_ICON)[index]
|
||||
else:
|
||||
grpname = -index
|
||||
data = win32api.LoadResource(hsrc, RT_GROUP_ICON, grpname)
|
||||
win32api.UpdateResource(hdst, RT_GROUP_ICON, grpname, data)
|
||||
for iconname in win32api.EnumResourceNames(hsrc, RT_ICON):
|
||||
data = win32api.LoadResource(hsrc, RT_ICON, iconname)
|
||||
win32api.UpdateResource(hdst, RT_ICON, iconname, data)
|
||||
win32api.FreeLibrary(hsrc)
|
||||
win32api.EndUpdateResource(hdst, 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
dstpath = sys.argv[1]
|
||||
srcpath = sys.argv[2:]
|
||||
CopyIcons(dstpath, srcpath)
|
||||
604
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/versioninfo.py
Executable file
604
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/versioninfo.py
Executable file
@@ -0,0 +1,604 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2013-2023, PyInstaller Development Team.
|
||||
#
|
||||
# Distributed under the terms of the GNU General Public License (version 2
|
||||
# or later) with exception for distributing the bootloader.
|
||||
#
|
||||
# The full license is in the file COPYING.txt, distributed with this software.
|
||||
#
|
||||
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
import struct
|
||||
|
||||
import pefile
|
||||
|
||||
from PyInstaller.compat import win32api
|
||||
|
||||
|
||||
def pefile_check_control_flow_guard(filename):
|
||||
"""
|
||||
Checks if the specified PE file has CFG (Control Flow Guard) enabled.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename : str
|
||||
Path to the PE file to inspect.
|
||||
|
||||
Returns
|
||||
----------
|
||||
bool
|
||||
True if file is a PE file with CFG enabled. False if CFG is not enabled or if file could not be processed using
|
||||
the pefile library.
|
||||
"""
|
||||
try:
|
||||
pe = pefile.PE(filename, fast_load=True)
|
||||
# https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
|
||||
# IMAGE_DLLCHARACTERISTICS_GUARD_CF = 0x4000
|
||||
return bool(pe.OPTIONAL_HEADER.DllCharacteristics & 0x4000)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
# Ensures no code from the executable is executed.
|
||||
LOAD_LIBRARY_AS_DATAFILE = 2
|
||||
|
||||
|
||||
def getRaw(text):
|
||||
"""
|
||||
Encodes text as UTF-16LE (Microsoft 'Unicode') for use in structs.
|
||||
"""
|
||||
return text.encode('UTF-16LE')
|
||||
|
||||
|
||||
def read_version_info_from_executable(exe_filename):
|
||||
"""
|
||||
Read the version information structure from the given executable's resources, and return it as an instance of
|
||||
`VSVersionInfo` structure.
|
||||
"""
|
||||
h = win32api.LoadLibraryEx(exe_filename, 0, LOAD_LIBRARY_AS_DATAFILE)
|
||||
res = win32api.EnumResourceNames(h, pefile.RESOURCE_TYPE['RT_VERSION'])
|
||||
if not len(res):
|
||||
return None
|
||||
data = win32api.LoadResource(h, pefile.RESOURCE_TYPE['RT_VERSION'], res[0])
|
||||
info = VSVersionInfo()
|
||||
info.fromRaw(data)
|
||||
win32api.FreeLibrary(h)
|
||||
return info
|
||||
|
||||
|
||||
def nextDWord(offset):
|
||||
"""
|
||||
Align `offset` to the next 4-byte boundary.
|
||||
"""
|
||||
return ((offset + 3) >> 2) << 2
|
||||
|
||||
|
||||
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 = nextDWord(i)
|
||||
# Now a VS_FIXEDFILEINFO
|
||||
self.ffi = FixedFileInfo()
|
||||
j = self.ffi.fromRaw(data, i)
|
||||
i = j
|
||||
while i < sublen:
|
||||
j = i
|
||||
i, (csublen, cvallen, ctyp, nm) = parseCommon(data, i)
|
||||
if nm.strip() == 'StringFileInfo':
|
||||
sfi = StringFileInfo()
|
||||
k = sfi.fromRaw(csublen, cvallen, nm, data, i, j + csublen)
|
||||
self.kids.append(sfi)
|
||||
i = k
|
||||
else:
|
||||
vfi = VarFileInfo()
|
||||
k = vfi.fromRaw(csublen, cvallen, nm, data, i, j + csublen)
|
||||
self.kids.append(vfi)
|
||||
i = k
|
||||
i = j + csublen
|
||||
i = nextDWord(i)
|
||||
return i
|
||||
|
||||
def toRaw(self):
|
||||
raw_name = getRaw('VS_VERSION_INFO')
|
||||
rawffi = self.ffi.toRaw()
|
||||
vallen = len(rawffi)
|
||||
typ = 0
|
||||
sublen = 6 + len(raw_name) + 2
|
||||
pad = b''
|
||||
if sublen % 4:
|
||||
pad = b'\000\000'
|
||||
sublen = sublen + len(pad) + vallen
|
||||
pad2 = b''
|
||||
if sublen % 4:
|
||||
pad2 = b'\000\000'
|
||||
tmp = b''.join([kid.toRaw() for kid in self.kids])
|
||||
sublen = sublen + len(pad2) + len(tmp)
|
||||
return struct.pack('HHH', sublen, vallen, typ) + raw_name + b'\000\000' + pad + rawffi + pad2 + tmp
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.toRaw() == other
|
||||
|
||||
def __str__(self, indent=''):
|
||||
indent = indent + ' '
|
||||
tmp = [kid.__str__(indent + ' ') for kid in self.kids]
|
||||
tmp = ', \n'.join(tmp)
|
||||
return '\n'.join([
|
||||
"# UTF-8",
|
||||
"#",
|
||||
"# For more details about fixed file info 'ffi' see:",
|
||||
"# http://msdn.microsoft.com/en-us/library/ms646997.aspx",
|
||||
"VSVersionInfo(",
|
||||
indent + f"ffi={self.ffi.__str__(indent)},",
|
||||
indent + "kids=[",
|
||||
tmp,
|
||||
indent + "]",
|
||||
")",
|
||||
])
|
||||
|
||||
def __repr__(self):
|
||||
return "versioninfo.VSVersionInfo(ffi=%r, kids=%r)" % (self.ffi, self.kids)
|
||||
|
||||
|
||||
def parseCommon(data, start=0):
|
||||
i = start + 6
|
||||
(wLength, wValueLength, wType) = struct.unpack('3H', data[start:i])
|
||||
i, text = parseUString(data, i, i + wLength)
|
||||
return i, (wLength, wValueLength, wType, text)
|
||||
|
||||
|
||||
def parseUString(data, start, limit):
|
||||
i = start
|
||||
while i < limit:
|
||||
if data[i:i + 2] == b'\000\000':
|
||||
break
|
||||
i += 2
|
||||
text = data[start:i].decode('UTF-16LE')
|
||||
i += 2
|
||||
return i, text
|
||||
|
||||
|
||||
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 = 0xfeef04bd
|
||||
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(
|
||||
'13L',
|
||||
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 __eq__(self, other):
|
||||
return self.toRaw() == other
|
||||
|
||||
def __str__(self, indent=''):
|
||||
fv = (
|
||||
self.fileVersionMS >> 16, self.fileVersionMS & 0xffff,
|
||||
self.fileVersionLS >> 16, self.fileVersionLS & 0xffff,
|
||||
) # yapf: disable
|
||||
pv = (
|
||||
self.productVersionMS >> 16, self.productVersionMS & 0xffff,
|
||||
self.productVersionLS >> 16, self.productVersionLS & 0xffff,
|
||||
) # yapf: disable
|
||||
fd = (self.fileDateMS, self.fileDateLS)
|
||||
tmp = [
|
||||
'FixedFileInfo(',
|
||||
'# filevers and prodvers should be always a tuple with four items: (1, 2, 3, 4)',
|
||||
'# Set not needed items to zero 0.',
|
||||
'filevers=%s,' % (fv,),
|
||||
'prodvers=%s,' % (pv,),
|
||||
"# Contains a bitmask that specifies the valid bits 'flags'r",
|
||||
'mask=%s,' % hex(self.fileFlagsMask),
|
||||
'# Contains a bitmask that specifies the Boolean attributes of the file.',
|
||||
'flags=%s,' % hex(self.fileFlags),
|
||||
'# The operating system for which this file was designed.',
|
||||
'# 0x4 - NT and there is no need to change it.',
|
||||
'OS=%s,' % hex(self.fileOS),
|
||||
'# The general type of file.',
|
||||
'# 0x1 - the file is an application.',
|
||||
'fileType=%s,' % hex(self.fileType),
|
||||
'# The function of the file.',
|
||||
'# 0x0 - the function is not defined for this fileType',
|
||||
'subtype=%s,' % hex(self.fileSubtype),
|
||||
'# Creation date and time stamp.',
|
||||
'date=%s' % (fd,),
|
||||
')',
|
||||
]
|
||||
return f'\n{indent} '.join(tmp)
|
||||
|
||||
def __repr__(self):
|
||||
fv = (
|
||||
self.fileVersionMS >> 16, self.fileVersionMS & 0xffff,
|
||||
self.fileVersionLS >> 16, self.fileVersionLS & 0xffff,
|
||||
) # yapf: disable
|
||||
pv = (
|
||||
self.productVersionMS >> 16, self.productVersionMS & 0xffff,
|
||||
self.productVersionLS >> 16, self.productVersionLS & 0xffff,
|
||||
) # yapf: disable
|
||||
fd = (self.fileDateMS, self.fileDateLS)
|
||||
return (
|
||||
'versioninfo.FixedFileInfo(filevers=%r, prodvers=%r, '
|
||||
'mask=0x%x, flags=0x%x, OS=0x%x, '
|
||||
'fileType=%r, subtype=0x%x, date=%r)' %
|
||||
(fv, pv, self.fileFlagsMask, self.fileFlags, self.fileOS, self.fileType, self.fileSubtype, fd)
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
self.kids.append(st)
|
||||
i = j
|
||||
return i
|
||||
|
||||
def toRaw(self):
|
||||
raw_name = getRaw(self.name)
|
||||
vallen = 0
|
||||
typ = 1
|
||||
sublen = 6 + len(raw_name) + 2
|
||||
pad = b''
|
||||
if sublen % 4:
|
||||
pad = b'\000\000'
|
||||
tmp = b''.join([kid.toRaw() for kid in self.kids])
|
||||
sublen = sublen + len(pad) + len(tmp)
|
||||
return struct.pack('HHH', sublen, vallen, typ) + raw_name + b'\000\000' + pad + tmp
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.toRaw() == other
|
||||
|
||||
def __str__(self, indent=''):
|
||||
new_indent = indent + ' '
|
||||
tmp = ', \n'.join(kid.__str__(new_indent) for kid in self.kids)
|
||||
return f'{indent}StringFileInfo(\n{new_indent}[\n{tmp}\n{new_indent}])'
|
||||
|
||||
def __repr__(self):
|
||||
return 'versioninfo.StringFileInfo(%r)' % self.kids
|
||||
|
||||
|
||||
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):
|
||||
i, (cpsublen, cpwValueLength, cpwType, self.name) = parseCodePage(data, i, limit) # should be code page junk
|
||||
i = nextDWord(i)
|
||||
while i < limit:
|
||||
ss = StringStruct()
|
||||
j = ss.fromRaw(data, i, limit)
|
||||
i = j
|
||||
self.kids.append(ss)
|
||||
i = nextDWord(i)
|
||||
return i
|
||||
|
||||
def toRaw(self):
|
||||
raw_name = getRaw(self.name)
|
||||
vallen = 0
|
||||
typ = 1
|
||||
sublen = 6 + len(raw_name) + 2
|
||||
tmp = []
|
||||
for kid in self.kids:
|
||||
raw = kid.toRaw()
|
||||
if len(raw) % 4:
|
||||
raw = raw + b'\000\000'
|
||||
tmp.append(raw)
|
||||
tmp = b''.join(tmp)
|
||||
sublen += len(tmp)
|
||||
return struct.pack('HHH', sublen, vallen, typ) + raw_name + b'\000\000' + tmp
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.toRaw() == other
|
||||
|
||||
def __str__(self, indent=''):
|
||||
new_indent = indent + ' '
|
||||
tmp = (',\n' + new_indent).join(str(kid) for kid in self.kids)
|
||||
return f"{indent}StringTable(\n{new_indent}'{self.name}',\n{new_indent}[{tmp}])"
|
||||
|
||||
def __repr__(self):
|
||||
return 'versioninfo.StringTable(%r, %r)' % (self.name, self.kids)
|
||||
|
||||
|
||||
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 = nextDWord(i)
|
||||
i, self.val = parseUString(data, i, limit)
|
||||
return i
|
||||
|
||||
def toRaw(self):
|
||||
raw_name = getRaw(self.name)
|
||||
raw_val = getRaw(self.val)
|
||||
# TODO: document the size of vallen and sublen.
|
||||
vallen = len(self.val) + 1 # Number of (wide-)characters, not bytes!
|
||||
typ = 1
|
||||
sublen = 6 + len(raw_name) + 2
|
||||
pad = b''
|
||||
if sublen % 4:
|
||||
pad = b'\000\000'
|
||||
sublen = sublen + len(pad) + (vallen * 2)
|
||||
return struct.pack('HHH', sublen, vallen, typ) + raw_name + b'\000\000' + pad + raw_val + b'\000\000'
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.toRaw() == other
|
||||
|
||||
def __str__(self, indent=''):
|
||||
return "StringStruct(%r, %r)" % (self.name, self.val)
|
||||
|
||||
def __repr__(self):
|
||||
return 'versioninfo.StringStruct(%r, %r)' % (self.name, self.val)
|
||||
|
||||
|
||||
def parseCodePage(data, i, limit):
|
||||
i, (sublen, wValueLength, wType, nm) = parseCommon(data, i)
|
||||
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 = nextDWord(i)
|
||||
while i < limit:
|
||||
vs = VarStruct()
|
||||
j = vs.fromRaw(data, i, limit)
|
||||
self.kids.append(vs)
|
||||
i = j
|
||||
return i
|
||||
|
||||
def toRaw(self):
|
||||
self.vallen = 0
|
||||
self.wType = 1
|
||||
self.name = 'VarFileInfo'
|
||||
raw_name = getRaw(self.name)
|
||||
sublen = 6 + len(raw_name) + 2
|
||||
pad = b''
|
||||
if sublen % 4:
|
||||
pad = b'\000\000'
|
||||
tmp = b''.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) + raw_name + b'\000\000' + pad + tmp
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.toRaw() == other
|
||||
|
||||
def __str__(self, indent=''):
|
||||
return indent + "VarFileInfo([%s])" % ', '.join(str(kid) for kid in self.kids)
|
||||
|
||||
def __repr__(self):
|
||||
return 'versioninfo.VarFileInfo(%r)' % self.kids
|
||||
|
||||
|
||||
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 = nextDWord(i)
|
||||
for j in range(0, 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
|
||||
raw_name = getRaw(self.name)
|
||||
sublen = 6 + len(raw_name) + 2
|
||||
pad = b''
|
||||
if sublen % 4:
|
||||
pad = b'\000\000'
|
||||
self.sublen = sublen + len(pad) + self.wValueLength
|
||||
tmp = b''.join([struct.pack('H', kid) for kid in self.kids])
|
||||
return struct.pack('HHH', self.sublen, self.wValueLength, self.wType) + raw_name + b'\000\000' + pad + tmp
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.toRaw() == other
|
||||
|
||||
def __str__(self, indent=''):
|
||||
return "VarStruct('%s', %r)" % (self.name, self.kids)
|
||||
|
||||
def __repr__(self):
|
||||
return 'versioninfo.VarStruct(%r, %r)' % (self.name, self.kids)
|
||||
|
||||
|
||||
def load_version_info_from_text_file(filename):
|
||||
"""
|
||||
Load the `VSVersionInfo` structure from its string-based (`VSVersionInfo.__str__`) serialization by reading the
|
||||
text from the file and running it through `eval()`.
|
||||
"""
|
||||
|
||||
# Read and parse the version file. It may have a byte order marker or encoding cookie - respect it if it does.
|
||||
import PyInstaller.utils.misc as miscutils
|
||||
with open(filename, 'rb') as fp:
|
||||
text = miscutils.decode(fp.read())
|
||||
|
||||
# Deserialize via eval()
|
||||
try:
|
||||
info = eval(text)
|
||||
except Exception as e:
|
||||
raise ValueError("Failed to deserialize VSVersionInfo from text-based representation!") from e
|
||||
|
||||
# Sanity check
|
||||
assert isinstance(info, VSVersionInfo), \
|
||||
f"Loaded incompatible structure type! Expected VSVersionInfo, got: {type(info)!r}"
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def write_version_info_to_executable(exe_filename, info):
|
||||
assert isinstance(info, VSVersionInfo)
|
||||
|
||||
# Remember overlay
|
||||
pe = pefile.PE(exe_filename, fast_load=True)
|
||||
overlay_before = pe.get_overlay()
|
||||
pe.close()
|
||||
|
||||
hdst = win32api.BeginUpdateResource(exe_filename, 0)
|
||||
win32api.UpdateResource(hdst, pefile.RESOURCE_TYPE['RT_VERSION'], 1, info.toRaw())
|
||||
win32api.EndUpdateResource(hdst, 0)
|
||||
|
||||
if overlay_before:
|
||||
# Check if the overlay is still present
|
||||
pe = pefile.PE(exe_filename, fast_load=True)
|
||||
overlay_after = pe.get_overlay()
|
||||
pe.close()
|
||||
|
||||
# If the update removed the overlay data, re-append it
|
||||
if not overlay_after:
|
||||
with open(exe_filename, 'ab') as exef:
|
||||
exef.write(overlay_before)
|
||||
244
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/winmanifest.py
Executable file
244
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/winmanifest.py
Executable file
@@ -0,0 +1,244 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2013-2023, PyInstaller Development Team.
|
||||
#
|
||||
# Distributed under the terms of the GNU General Public License (version 2
|
||||
# or later) with exception for distributing the bootloader.
|
||||
#
|
||||
# The full license is in the file COPYING.txt, distributed with this software.
|
||||
#
|
||||
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
|
||||
#-----------------------------------------------------------------------------
|
||||
import xml.dom
|
||||
import xml.dom.minidom
|
||||
|
||||
#- Relevant constants from Windows headers
|
||||
# Manifest resource code
|
||||
RT_MANIFEST = 24
|
||||
|
||||
# Resource IDs (names) for manifest.
|
||||
# See: https://www.gamedev.net/blogs/entry/2154553-manifest-embedding-and-activation
|
||||
CREATEPROCESS_MANIFEST_RESOURCE_ID = 1
|
||||
ISOLATIONAWARE_MANIFEST_RESOURCE_ID = 2
|
||||
|
||||
LANG_NEUTRAL = 0
|
||||
|
||||
#- Default application manifest template, based on the one found in python executable.
|
||||
|
||||
_DEFAULT_MANIFEST_XML = \
|
||||
b"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"></supportedOS>
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"></supportedOS>
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"></supportedOS>
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"></supportedOS>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"></supportedOS>
|
||||
</application>
|
||||
</compatibility>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity>
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
</assembly>
|
||||
""" # noqa: E122,E501
|
||||
|
||||
#- DOM navigation helpers
|
||||
|
||||
|
||||
def _find_elements_by_tag(root, tag):
|
||||
"""
|
||||
Find all elements with given tag under the given root element.
|
||||
"""
|
||||
return [node for node in root.childNodes if node.nodeType == xml.dom.Node.ELEMENT_NODE and node.tagName == tag]
|
||||
|
||||
|
||||
def _find_element_by_tag(root, tag):
|
||||
"""
|
||||
Attempt to find a single element with given tag under the given root element, and return None if no such element
|
||||
is found. Raises an error if multiple elements are found.
|
||||
"""
|
||||
elements = _find_elements_by_tag(root, tag)
|
||||
if len(elements) > 1:
|
||||
raise ValueError(f"Expected a single {tag!r} element, found {len(elements)} element(s)!")
|
||||
if not elements:
|
||||
return None
|
||||
return elements[0]
|
||||
|
||||
|
||||
#- Application manifest modification helpers
|
||||
|
||||
|
||||
def _set_execution_level(manifest_dom, root_element, uac_admin=False, uac_uiaccess=False):
|
||||
"""
|
||||
Find <security> -> <requestedPrivileges> -> <requestedExecutionLevel> element, and set its `level` and `uiAccess`
|
||||
attributes based on supplied arguments. Create the XML elements if necessary, as they are optional.
|
||||
"""
|
||||
|
||||
# <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
trust_info_element = _find_element_by_tag(root_element, "trustInfo")
|
||||
if not trust_info_element:
|
||||
trust_info_element = manifest_dom.createElement("trustInfo")
|
||||
trust_info_element.setAttribute("xmlns", "urn:schemas-microsoft-com:asm.v3")
|
||||
root_element.appendChild(trust_info_element)
|
||||
|
||||
# <security>
|
||||
security_element = _find_element_by_tag(trust_info_element, "security")
|
||||
if not security_element:
|
||||
security_element = manifest_dom.createElement("security")
|
||||
trust_info_element.appendChild(security_element)
|
||||
|
||||
# <requestedPrivileges>
|
||||
requested_privileges_element = _find_element_by_tag(security_element, "requestedPrivileges")
|
||||
if not requested_privileges_element:
|
||||
requested_privileges_element = manifest_dom.createElement("requestedPrivileges")
|
||||
security_element.appendChild(requested_privileges_element)
|
||||
|
||||
# <requestedExecutionLevel>
|
||||
requested_execution_level_element = _find_element_by_tag(requested_privileges_element, "requestedExecutionLevel")
|
||||
if not requested_execution_level_element:
|
||||
requested_execution_level_element = manifest_dom.createElement("requestedExecutionLevel")
|
||||
requested_privileges_element.appendChild(requested_execution_level_element)
|
||||
|
||||
requested_execution_level_element.setAttribute("level", "requireAdministrator" if uac_admin else "asInvoker")
|
||||
requested_execution_level_element.setAttribute("uiAccess", "true" if uac_uiaccess else "false")
|
||||
|
||||
|
||||
def _ensure_common_controls_dependency(manifest_dom, root_element):
|
||||
"""
|
||||
Scan <dependency> elements for the one whose <<dependentAssembly> -> <assemblyIdentity> corresponds to the
|
||||
`Microsoft.Windows.Common-Controls`. If found, overwrite its properties. If not, create new <dependency>
|
||||
element with corresponding sub-elements and attributes.
|
||||
"""
|
||||
|
||||
# <dependency>
|
||||
dependency_elements = _find_elements_by_tag(root_element, "dependency")
|
||||
for dependency_element in dependency_elements:
|
||||
# <dependentAssembly>
|
||||
dependent_assembly_element = _find_element_by_tag(dependency_element, "dependentAssembly")
|
||||
# <assemblyIdentity>
|
||||
assembly_identity_element = _find_element_by_tag(dependent_assembly_element, "assemblyIdentity")
|
||||
# Check the name attribute
|
||||
if assembly_identity_element.attributes["name"].value == "Microsoft.Windows.Common-Controls":
|
||||
common_controls_element = assembly_identity_element
|
||||
break
|
||||
else:
|
||||
# Create <dependency>
|
||||
dependency_element = manifest_dom.createElement("dependency")
|
||||
root_element.appendChild(dependency_element)
|
||||
# Create <dependentAssembly>
|
||||
dependent_assembly_element = manifest_dom.createElement("dependentAssembly")
|
||||
dependency_element.appendChild(dependent_assembly_element)
|
||||
# Create <assemblyIdentity>
|
||||
common_controls_element = manifest_dom.createElement("assemblyIdentity")
|
||||
dependent_assembly_element.appendChild(common_controls_element)
|
||||
|
||||
common_controls_element.setAttribute("type", "win32")
|
||||
common_controls_element.setAttribute("name", "Microsoft.Windows.Common-Controls")
|
||||
common_controls_element.setAttribute("version", "6.0.0.0")
|
||||
common_controls_element.setAttribute("processorArchitecture", "*")
|
||||
common_controls_element.setAttribute("publicKeyToken", "6595b64144ccf1df")
|
||||
common_controls_element.setAttribute("language", "*")
|
||||
|
||||
|
||||
def create_application_manifest(manifest_xml=None, uac_admin=False, uac_uiaccess=False):
|
||||
"""
|
||||
Create application manifest, from built-in or custom manifest XML template. If provided, `manifest_xml` must be
|
||||
a string or byte string containing XML source. The returned manifest is a byte string, encoded in UTF-8.
|
||||
|
||||
This function sets the attributes of `requestedExecutionLevel` based on provided `uac_admin` and `auc_uiacces`
|
||||
arguments (creating the parent elements in the XML, if necessary). It also scans `dependency` elements for the
|
||||
entry corresponding to `Microsoft.Windows.Common-Controls` and creates or modifies it as necessary.
|
||||
"""
|
||||
|
||||
if manifest_xml is None:
|
||||
manifest_xml = _DEFAULT_MANIFEST_XML
|
||||
|
||||
with xml.dom.minidom.parseString(manifest_xml) as manifest_dom:
|
||||
root_element = manifest_dom.documentElement
|
||||
|
||||
# Validate root element - must be <assembly>
|
||||
assert root_element.tagName == "assembly"
|
||||
assert root_element.namespaceURI == "urn:schemas-microsoft-com:asm.v1"
|
||||
assert root_element.attributes["manifestVersion"].value == "1.0"
|
||||
|
||||
# Modify the manifest
|
||||
_set_execution_level(manifest_dom, root_element, uac_admin, uac_uiaccess)
|
||||
_ensure_common_controls_dependency(manifest_dom, root_element)
|
||||
|
||||
# Create output XML
|
||||
output = manifest_dom.toprettyxml(indent=" ", encoding="UTF-8")
|
||||
|
||||
# Strip extra newlines
|
||||
output = [line for line in output.splitlines() if line.strip()]
|
||||
|
||||
# Replace: `<?xml version="1.0" encoding="UTF-8"?>` with `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`.
|
||||
# Support for `standalone` was added to `toprettyxml` in python 3.9, so do a manual work around.
|
||||
output[0] = b"""<?xml version="1.0" encoding="UTF-8" standalone="yes"?>"""
|
||||
|
||||
output = b"\n".join(output)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def write_manifest_to_executable(filename, manifest_xml):
|
||||
"""
|
||||
Write the given manifest XML to the given executable's RT_MANIFEST resource.
|
||||
"""
|
||||
from PyInstaller.utils.win32 import winresource
|
||||
|
||||
# CREATEPROCESS_MANIFEST_RESOURCE_ID is used for manifest resource in executables.
|
||||
# ISOLATIONAWARE_MANIFEST_RESOURCE_ID is used for manifest resources in DLLs.
|
||||
names = [CREATEPROCESS_MANIFEST_RESOURCE_ID]
|
||||
|
||||
# Ensure LANG_NEUTRAL is updated, and also update any other present languages.
|
||||
languages = [LANG_NEUTRAL, "*"]
|
||||
|
||||
winresource.add_or_update_resource(filename, manifest_xml, RT_MANIFEST, names, languages)
|
||||
|
||||
|
||||
def read_manifest_from_executable(filename):
|
||||
"""
|
||||
Read manifest from the given executable."
|
||||
"""
|
||||
from PyInstaller.utils.win32 import winresource
|
||||
|
||||
resources = winresource.get_resources(filename, [RT_MANIFEST])
|
||||
|
||||
# `resources` is a three-level dictionary:
|
||||
# - level 1: resource type (RT_MANIFEST)
|
||||
# - level 2: resource name (CREATEPROCESS_MANIFEST_RESOURCE_ID)
|
||||
# - level 3: resource language (LANG_NEUTRAL)
|
||||
|
||||
# Level 1
|
||||
if RT_MANIFEST not in resources:
|
||||
raise ValueError(f"No RT_MANIFEST resources found in {filename!r}.")
|
||||
resources = resources[RT_MANIFEST]
|
||||
|
||||
# Level 2
|
||||
if CREATEPROCESS_MANIFEST_RESOURCE_ID not in resources:
|
||||
raise ValueError(f"No RT_MANIFEST resource named CREATEPROCESS_MANIFEST_RESOURCE_ID found in {filename!r}.")
|
||||
resources = resources[CREATEPROCESS_MANIFEST_RESOURCE_ID]
|
||||
|
||||
# Level 3
|
||||
# We prefer LANG_NEUTRAL, but allow fall back to the first available entry.
|
||||
if LANG_NEUTRAL in resources:
|
||||
resources = resources[LANG_NEUTRAL]
|
||||
else:
|
||||
resources = next(iter(resources.items()))
|
||||
|
||||
manifest_xml = resources
|
||||
return manifest_xml
|
||||
189
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/winresource.py
Executable file
189
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/winresource.py
Executable file
@@ -0,0 +1,189 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2013-2023, PyInstaller Development Team.
|
||||
#
|
||||
# Distributed under the terms of the GNU General Public License (version 2
|
||||
# or later) with exception for distributing the bootloader.
|
||||
#
|
||||
# The full license is in the file COPYING.txt, distributed with this software.
|
||||
#
|
||||
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
|
||||
#-----------------------------------------------------------------------------
|
||||
"""
|
||||
Read and write resources from/to Win32 PE files.
|
||||
"""
|
||||
|
||||
import PyInstaller.log as logging
|
||||
from PyInstaller.compat import pywintypes, win32api
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
LOAD_LIBRARY_AS_DATAFILE = 2
|
||||
ERROR_BAD_EXE_FORMAT = 193
|
||||
ERROR_RESOURCE_DATA_NOT_FOUND = 1812
|
||||
ERROR_RESOURCE_TYPE_NOT_FOUND = 1813
|
||||
ERROR_RESOURCE_NAME_NOT_FOUND = 1814
|
||||
ERROR_RESOURCE_LANG_NOT_FOUND = 1815
|
||||
|
||||
|
||||
def get_resources(filename, types=None, names=None, languages=None):
|
||||
"""
|
||||
Retrieve resources from the given PE file.
|
||||
|
||||
filename: path to the PE file.
|
||||
types: a list of resource types (integers or strings) to search for (None = all).
|
||||
names: a list of resource names (integers or strings) to search for (None = all).
|
||||
languages: a list of resource languages (integers) to search for (None = all).
|
||||
|
||||
Returns a dictionary of the form {type: {name: {language: data}}}, which might also be empty if no matching
|
||||
resources were found.
|
||||
"""
|
||||
types = set(types) if types is not None else {"*"}
|
||||
names = set(names) if names is not None else {"*"}
|
||||
languages = set(languages) if languages is not None else {"*"}
|
||||
|
||||
output = {}
|
||||
|
||||
# Errors codes for which we swallow exceptions
|
||||
_IGNORE_EXCEPTIONS = {
|
||||
ERROR_RESOURCE_DATA_NOT_FOUND,
|
||||
ERROR_RESOURCE_TYPE_NOT_FOUND,
|
||||
ERROR_RESOURCE_NAME_NOT_FOUND,
|
||||
ERROR_RESOURCE_LANG_NOT_FOUND,
|
||||
}
|
||||
|
||||
# Open file
|
||||
module_handle = win32api.LoadLibraryEx(filename, 0, LOAD_LIBRARY_AS_DATAFILE)
|
||||
|
||||
# Enumerate available resource types
|
||||
try:
|
||||
available_types = win32api.EnumResourceTypes(module_handle)
|
||||
except pywintypes.error as e:
|
||||
if e.args[0] not in _IGNORE_EXCEPTIONS:
|
||||
raise
|
||||
available_types = []
|
||||
|
||||
if "*" not in types:
|
||||
available_types = [res_type for res_type in available_types if res_type in types]
|
||||
|
||||
for res_type in available_types:
|
||||
# Enumerate available names for the resource type.
|
||||
try:
|
||||
available_names = win32api.EnumResourceNames(module_handle, res_type)
|
||||
except pywintypes.error as e:
|
||||
if e.args[0] not in _IGNORE_EXCEPTIONS:
|
||||
raise
|
||||
continue
|
||||
|
||||
if "*" not in names:
|
||||
available_names = [res_name for res_name in available_names if res_name in names]
|
||||
|
||||
for res_name in available_names:
|
||||
# Enumerate available languages for the resource type and name combination.
|
||||
try:
|
||||
available_languages = win32api.EnumResourceLanguages(module_handle, res_type, res_name)
|
||||
except pywintypes.error as e:
|
||||
if e.args[0] not in _IGNORE_EXCEPTIONS:
|
||||
raise
|
||||
continue
|
||||
|
||||
if "*" not in languages:
|
||||
available_languages = [res_lang for res_lang in available_languages if res_lang in languages]
|
||||
|
||||
for res_lang in available_languages:
|
||||
# Read data
|
||||
try:
|
||||
data = win32api.LoadResource(module_handle, res_type, res_name, res_lang)
|
||||
except pywintypes.error as e:
|
||||
if e.args[0] not in _IGNORE_EXCEPTIONS:
|
||||
raise
|
||||
continue
|
||||
|
||||
if res_type not in output:
|
||||
output[res_type] = {}
|
||||
if res_name not in output[res_type]:
|
||||
output[res_type][res_name] = {}
|
||||
output[res_type][res_name][res_lang] = data
|
||||
|
||||
# Close file
|
||||
win32api.FreeLibrary(module_handle)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def add_or_update_resource(filename, data, res_type, names=None, languages=None):
|
||||
"""
|
||||
Update or add a single resource in the PE file with the given binary data.
|
||||
|
||||
filename: path to the PE file.
|
||||
data: binary data to write to the resource.
|
||||
res_type: resource type to add/update (integer or string).
|
||||
names: a list of resource names (integers or strings) to update (None = all).
|
||||
languages: a list of resource languages (integers) to update (None = all).
|
||||
"""
|
||||
if res_type == "*":
|
||||
raise ValueError("res_type cannot be a wildcard (*)!")
|
||||
|
||||
names = set(names) if names is not None else {"*"}
|
||||
languages = set(languages) if languages is not None else {"*"}
|
||||
|
||||
# Retrieve existing resources, filtered by the given resource type and given resource names and languages.
|
||||
resources = get_resources(filename, [res_type], names, languages)
|
||||
|
||||
# Add res_type, name, language combinations that are not already present
|
||||
resources = resources.get(res_type, {}) # This is now a {name: {language: data}} dictionary
|
||||
|
||||
for res_name in names:
|
||||
if res_name == "*":
|
||||
continue
|
||||
if res_name not in resources:
|
||||
resources[res_name] = {}
|
||||
|
||||
for res_lang in languages:
|
||||
if res_lang == "*":
|
||||
continue
|
||||
if res_lang not in resources[res_name]:
|
||||
resources[res_name][res_lang] = None # Just an indicator
|
||||
|
||||
# Add resource to the target file, overwriting the existing resources with same type, name, language combinations.
|
||||
module_handle = win32api.BeginUpdateResource(filename, 0)
|
||||
for res_name in resources.keys():
|
||||
for res_lang in resources[res_name].keys():
|
||||
win32api.UpdateResource(module_handle, res_type, res_name, data, res_lang)
|
||||
win32api.EndUpdateResource(module_handle, 0)
|
||||
|
||||
|
||||
def copy_resources_from_pe_file(filename, src_filename, types=None, names=None, languages=None):
|
||||
"""
|
||||
Update or add resources in the given PE file by copying them over from the specified source PE file.
|
||||
|
||||
filename: path to the PE file.
|
||||
src_filename: path to the source PE file.
|
||||
types: a list of resource types (integers or strings) to add/update via copy for (None = all).
|
||||
names: a list of resource names (integers or strings) to add/update via copy (None = all).
|
||||
languages: a list of resource languages (integers) to add/update via copy (None = all).
|
||||
"""
|
||||
types = set(types) if types is not None else {"*"}
|
||||
names = set(names) if names is not None else {"*"}
|
||||
languages = set(languages) if languages is not None else {"*"}
|
||||
|
||||
# Retrieve existing resources, filtered by the given resource type and given resource names and languages.
|
||||
resources = get_resources(src_filename, types, names, languages)
|
||||
|
||||
for res_type, resources_for_type in resources.items():
|
||||
if "*" not in types and res_type not in types:
|
||||
continue
|
||||
for res_name, resources_for_type_name in resources_for_type.items():
|
||||
if "*" not in names and res_name not in names:
|
||||
continue
|
||||
for res_lang, data in resources_for_type_name.items():
|
||||
if "*" not in languages and res_lang not in languages:
|
||||
continue
|
||||
add_or_update_resource(filename, data, res_type, [res_name], [res_lang])
|
||||
|
||||
|
||||
def remove_all_resources(filename):
|
||||
"""
|
||||
Remove all resources from the given PE file:
|
||||
"""
|
||||
module_handle = win32api.BeginUpdateResource(filename, True) # bDeleteExistingResources=True
|
||||
win32api.EndUpdateResource(module_handle, False)
|
||||
257
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/winutils.py
Executable file
257
venv/lib/python3.12/site-packages/PyInstaller/utils/win32/winutils.py
Executable file
@@ -0,0 +1,257 @@
|
||||
#-----------------------------------------------------------------------------
|
||||
# Copyright (c) 2013-2023, PyInstaller Development Team.
|
||||
#
|
||||
# Distributed under the terms of the GNU General Public License (version 2
|
||||
# or later) with exception for distributing the bootloader.
|
||||
#
|
||||
# The full license is in the file COPYING.txt, distributed with this software.
|
||||
#
|
||||
# SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
|
||||
#-----------------------------------------------------------------------------
|
||||
"""
|
||||
Utilities for Windows platform.
|
||||
"""
|
||||
|
||||
from PyInstaller import compat
|
||||
|
||||
|
||||
def get_windows_dir():
|
||||
"""
|
||||
Return the Windows directory, e.g., C:\\Windows.
|
||||
"""
|
||||
windir = compat.win32api.GetWindowsDirectory()
|
||||
if not windir:
|
||||
raise SystemExit("ERROR: Cannot determine Windows directory!")
|
||||
return windir
|
||||
|
||||
|
||||
def get_system_path():
|
||||
"""
|
||||
Return the required Windows system paths.
|
||||
"""
|
||||
sys_dir = compat.win32api.GetSystemDirectory()
|
||||
# Ensure C:\Windows\system32 and C:\Windows directories are always present in PATH variable.
|
||||
# C:\Windows\system32 is valid even for 64-bit Windows. Access do DLLs are transparently redirected to
|
||||
# C:\Windows\syswow64 for 64bit applactions.
|
||||
# See http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx
|
||||
return [sys_dir, get_windows_dir()]
|
||||
|
||||
|
||||
def get_pe_file_machine_type(filename):
|
||||
"""
|
||||
Return the machine type code from the header of the given PE file.
|
||||
"""
|
||||
import pefile
|
||||
|
||||
with pefile.PE(filename, fast_load=True) as pe:
|
||||
return pe.FILE_HEADER.Machine
|
||||
|
||||
|
||||
def set_exe_build_timestamp(exe_path, timestamp):
|
||||
"""
|
||||
Modifies the executable's build timestamp by updating values in the corresponding PE headers.
|
||||
"""
|
||||
import pefile
|
||||
|
||||
with pefile.PE(exe_path, fast_load=True) as pe:
|
||||
# Manually perform a full load. We need it to load all headers, but specifying it in the constructor triggers
|
||||
# byte statistics gathering that takes forever with large files. So we try to go around that...
|
||||
pe.full_load()
|
||||
|
||||
# Set build timestamp.
|
||||
# See: https://0xc0decafe.com/malware-analyst-guide-to-pe-timestamps
|
||||
timestamp = int(timestamp)
|
||||
# Set timestamp field in FILE_HEADER
|
||||
pe.FILE_HEADER.TimeDateStamp = timestamp
|
||||
# MSVC-compiled executables contain (at least?) one DIRECTORY_ENTRY_DEBUG entry that also contains timestamp
|
||||
# with same value as set in FILE_HEADER. So modify that as well, as long as it is set.
|
||||
debug_entries = getattr(pe, 'DIRECTORY_ENTRY_DEBUG', [])
|
||||
for debug_entry in debug_entries:
|
||||
if debug_entry.struct.TimeDateStamp:
|
||||
debug_entry.struct.TimeDateStamp = timestamp
|
||||
|
||||
# Generate updated EXE data
|
||||
data = pe.write()
|
||||
|
||||
# Rewrite the exe
|
||||
with open(exe_path, 'wb') as fp:
|
||||
fp.write(data)
|
||||
|
||||
|
||||
def update_exe_pe_checksum(exe_path):
|
||||
"""
|
||||
Compute the executable's PE checksum, and write it to PE headers.
|
||||
|
||||
This optional checksum is supposed to protect the executable against corruption but some anti-viral software have
|
||||
taken to flagging anything without it set correctly as malware. See issue #5579.
|
||||
"""
|
||||
import pefile
|
||||
|
||||
# Compute checksum using our equivalent of the MapFileAndCheckSumW - for large files, it is significantly faster
|
||||
# than pure-pyton pefile.PE.generate_checksum(). However, it requires the file to be on disk (i.e., cannot operate
|
||||
# on a memory buffer).
|
||||
try:
|
||||
checksum = compute_exe_pe_checksum(exe_path)
|
||||
except Exception as e:
|
||||
raise RuntimeError("Failed to compute PE checksum!") from e
|
||||
|
||||
# Update the checksum
|
||||
with pefile.PE(exe_path, fast_load=True) as pe:
|
||||
pe.OPTIONAL_HEADER.CheckSum = checksum
|
||||
|
||||
# Generate updated EXE data
|
||||
data = pe.write()
|
||||
|
||||
# Rewrite the exe
|
||||
with open(exe_path, 'wb') as fp:
|
||||
fp.write(data)
|
||||
|
||||
|
||||
def compute_exe_pe_checksum(exe_path):
|
||||
"""
|
||||
This is a replacement for the MapFileAndCheckSumW function. As noted in MSDN documentation, the Microsoft's
|
||||
implementation of MapFileAndCheckSumW internally calls its ASCII variant (MapFileAndCheckSumA), and therefore
|
||||
cannot handle paths that contain characters that are not representable in the current code page.
|
||||
See: https://docs.microsoft.com/en-us/windows/win32/api/imagehlp/nf-imagehlp-mapfileandchecksumw
|
||||
|
||||
This function is based on Wine's implementation of MapFileAndCheckSumW, and due to being based entirely on
|
||||
the pure widechar-API functions, it is not limited by the current code page.
|
||||
"""
|
||||
# ctypes bindings for relevant win32 API functions
|
||||
import ctypes
|
||||
from ctypes import windll, wintypes
|
||||
|
||||
INVALID_HANDLE = wintypes.HANDLE(-1).value
|
||||
|
||||
GetLastError = ctypes.windll.kernel32.GetLastError
|
||||
GetLastError.argtypes = ()
|
||||
GetLastError.restype = wintypes.DWORD
|
||||
|
||||
CloseHandle = windll.kernel32.CloseHandle
|
||||
CloseHandle.argtypes = (
|
||||
wintypes.HANDLE, # hObject
|
||||
)
|
||||
CloseHandle.restype = wintypes.BOOL
|
||||
|
||||
CreateFileW = windll.kernel32.CreateFileW
|
||||
CreateFileW.argtypes = (
|
||||
wintypes.LPCWSTR, # lpFileName
|
||||
wintypes.DWORD, # dwDesiredAccess
|
||||
wintypes.DWORD, # dwShareMode
|
||||
wintypes.LPVOID, # lpSecurityAttributes
|
||||
wintypes.DWORD, # dwCreationDisposition
|
||||
wintypes.DWORD, # dwFlagsAndAttributes
|
||||
wintypes.HANDLE, # hTemplateFile
|
||||
)
|
||||
CreateFileW.restype = wintypes.HANDLE
|
||||
|
||||
CreateFileMappingW = windll.kernel32.CreateFileMappingW
|
||||
CreateFileMappingW.argtypes = (
|
||||
wintypes.HANDLE, # hFile
|
||||
wintypes.LPVOID, # lpSecurityAttributes
|
||||
wintypes.DWORD, # flProtect
|
||||
wintypes.DWORD, # dwMaximumSizeHigh
|
||||
wintypes.DWORD, # dwMaximumSizeLow
|
||||
wintypes.LPCWSTR, # lpName
|
||||
)
|
||||
CreateFileMappingW.restype = wintypes.HANDLE
|
||||
|
||||
MapViewOfFile = windll.kernel32.MapViewOfFile
|
||||
MapViewOfFile.argtypes = (
|
||||
wintypes.HANDLE, # hFileMappingObject
|
||||
wintypes.DWORD, # dwDesiredAccess
|
||||
wintypes.DWORD, # dwFileOffsetHigh
|
||||
wintypes.DWORD, # dwFileOffsetLow
|
||||
wintypes.DWORD, # dwNumberOfBytesToMap
|
||||
)
|
||||
MapViewOfFile.restype = wintypes.LPVOID
|
||||
|
||||
UnmapViewOfFile = windll.kernel32.UnmapViewOfFile
|
||||
UnmapViewOfFile.argtypes = (
|
||||
wintypes.LPCVOID, # lpBaseAddress
|
||||
)
|
||||
UnmapViewOfFile.restype = wintypes.BOOL
|
||||
|
||||
GetFileSizeEx = windll.kernel32.GetFileSizeEx
|
||||
GetFileSizeEx.argtypes = (
|
||||
wintypes.HANDLE, # hFile
|
||||
wintypes.PLARGE_INTEGER, # lpFileSize
|
||||
)
|
||||
|
||||
CheckSumMappedFile = windll.imagehlp.CheckSumMappedFile
|
||||
CheckSumMappedFile.argtypes = (
|
||||
wintypes.LPVOID, # BaseAddress
|
||||
wintypes.DWORD, # FileLength
|
||||
wintypes.PDWORD, # HeaderSum
|
||||
wintypes.PDWORD, # CheckSum
|
||||
)
|
||||
CheckSumMappedFile.restype = wintypes.LPVOID
|
||||
|
||||
# Open file
|
||||
hFile = CreateFileW(
|
||||
ctypes.c_wchar_p(exe_path),
|
||||
0x80000000, # dwDesiredAccess = GENERIC_READ
|
||||
0x00000001 | 0x00000002, # dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
None, # lpSecurityAttributes = NULL
|
||||
3, # dwCreationDisposition = OPEN_EXISTING
|
||||
0x80, # dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL
|
||||
None # hTemplateFile = NULL
|
||||
)
|
||||
if hFile == INVALID_HANDLE:
|
||||
err = GetLastError()
|
||||
raise RuntimeError(f"Failed to open file {exe_path}! Error code: {err}")
|
||||
|
||||
# Query file size
|
||||
fileLength = wintypes.LARGE_INTEGER(0)
|
||||
if GetFileSizeEx(hFile, fileLength) == 0:
|
||||
err = GetLastError()
|
||||
CloseHandle(hFile)
|
||||
raise RuntimeError(f"Failed to query file size file! Error code: {err}")
|
||||
fileLength = fileLength.value
|
||||
if fileLength > (2**32 - 1):
|
||||
raise RuntimeError("Executable size exceeds maximum allowed executable size on Windows (4 GiB)!")
|
||||
|
||||
# Map the file
|
||||
hMapping = CreateFileMappingW(
|
||||
hFile,
|
||||
None, # lpFileMappingAttributes = NULL
|
||||
0x02, # flProtect = PAGE_READONLY
|
||||
0, # dwMaximumSizeHigh = 0
|
||||
0, # dwMaximumSizeLow = 0
|
||||
None # lpName = NULL
|
||||
)
|
||||
if not hMapping:
|
||||
err = GetLastError()
|
||||
CloseHandle(hFile)
|
||||
raise RuntimeError(f"Failed to map file! Error code: {err}")
|
||||
|
||||
# Create map view
|
||||
baseAddress = MapViewOfFile(
|
||||
hMapping,
|
||||
4, # dwDesiredAccess = FILE_MAP_READ
|
||||
0, # dwFileOffsetHigh = 0
|
||||
0, # dwFileOffsetLow = 0
|
||||
0 # dwNumberOfBytesToMap = 0
|
||||
)
|
||||
if baseAddress == 0:
|
||||
err = GetLastError()
|
||||
CloseHandle(hMapping)
|
||||
CloseHandle(hFile)
|
||||
raise RuntimeError(f"Failed to create map view! Error code: {err}")
|
||||
|
||||
# Finally, compute the checksum
|
||||
headerSum = wintypes.DWORD(0)
|
||||
checkSum = wintypes.DWORD(0)
|
||||
ret = CheckSumMappedFile(baseAddress, fileLength, ctypes.byref(headerSum), ctypes.byref(checkSum))
|
||||
if ret is None:
|
||||
err = GetLastError()
|
||||
|
||||
# Cleanup
|
||||
UnmapViewOfFile(baseAddress)
|
||||
CloseHandle(hMapping)
|
||||
CloseHandle(hFile)
|
||||
|
||||
if ret is None:
|
||||
raise RuntimeError(f"CheckSumMappedFile failed! Error code: {err}")
|
||||
|
||||
return checkSum.value
|
||||
Reference in New Issue
Block a user