| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- #-----------------------------------------------------------------------------
- # 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)
|