1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 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)
|