winresource.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #-----------------------------------------------------------------------------
  2. # Copyright (c) 2013-2023, PyInstaller Development Team.
  3. #
  4. # Distributed under the terms of the GNU General Public License (version 2
  5. # or later) with exception for distributing the bootloader.
  6. #
  7. # The full license is in the file COPYING.txt, distributed with this software.
  8. #
  9. # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
  10. #-----------------------------------------------------------------------------
  11. """
  12. Read and write resources from/to Win32 PE files.
  13. """
  14. import PyInstaller.log as logging
  15. from PyInstaller.compat import pywintypes, win32api
  16. logger = logging.getLogger(__name__)
  17. LOAD_LIBRARY_AS_DATAFILE = 2
  18. ERROR_BAD_EXE_FORMAT = 193
  19. ERROR_RESOURCE_DATA_NOT_FOUND = 1812
  20. ERROR_RESOURCE_TYPE_NOT_FOUND = 1813
  21. ERROR_RESOURCE_NAME_NOT_FOUND = 1814
  22. ERROR_RESOURCE_LANG_NOT_FOUND = 1815
  23. def get_resources(filename, types=None, names=None, languages=None):
  24. """
  25. Retrieve resources from the given PE file.
  26. filename: path to the PE file.
  27. types: a list of resource types (integers or strings) to search for (None = all).
  28. names: a list of resource names (integers or strings) to search for (None = all).
  29. languages: a list of resource languages (integers) to search for (None = all).
  30. Returns a dictionary of the form {type: {name: {language: data}}}, which might also be empty if no matching
  31. resources were found.
  32. """
  33. types = set(types) if types is not None else {"*"}
  34. names = set(names) if names is not None else {"*"}
  35. languages = set(languages) if languages is not None else {"*"}
  36. output = {}
  37. # Errors codes for which we swallow exceptions
  38. _IGNORE_EXCEPTIONS = {
  39. ERROR_RESOURCE_DATA_NOT_FOUND,
  40. ERROR_RESOURCE_TYPE_NOT_FOUND,
  41. ERROR_RESOURCE_NAME_NOT_FOUND,
  42. ERROR_RESOURCE_LANG_NOT_FOUND,
  43. }
  44. # Open file
  45. module_handle = win32api.LoadLibraryEx(filename, 0, LOAD_LIBRARY_AS_DATAFILE)
  46. # Enumerate available resource types
  47. try:
  48. available_types = win32api.EnumResourceTypes(module_handle)
  49. except pywintypes.error as e:
  50. if e.args[0] not in _IGNORE_EXCEPTIONS:
  51. raise
  52. available_types = []
  53. if "*" not in types:
  54. available_types = [res_type for res_type in available_types if res_type in types]
  55. for res_type in available_types:
  56. # Enumerate available names for the resource type.
  57. try:
  58. available_names = win32api.EnumResourceNames(module_handle, res_type)
  59. except pywintypes.error as e:
  60. if e.args[0] not in _IGNORE_EXCEPTIONS:
  61. raise
  62. continue
  63. if "*" not in names:
  64. available_names = [res_name for res_name in available_names if res_name in names]
  65. for res_name in available_names:
  66. # Enumerate available languages for the resource type and name combination.
  67. try:
  68. available_languages = win32api.EnumResourceLanguages(module_handle, res_type, res_name)
  69. except pywintypes.error as e:
  70. if e.args[0] not in _IGNORE_EXCEPTIONS:
  71. raise
  72. continue
  73. if "*" not in languages:
  74. available_languages = [res_lang for res_lang in available_languages if res_lang in languages]
  75. for res_lang in available_languages:
  76. # Read data
  77. try:
  78. data = win32api.LoadResource(module_handle, res_type, res_name, res_lang)
  79. except pywintypes.error as e:
  80. if e.args[0] not in _IGNORE_EXCEPTIONS:
  81. raise
  82. continue
  83. if res_type not in output:
  84. output[res_type] = {}
  85. if res_name not in output[res_type]:
  86. output[res_type][res_name] = {}
  87. output[res_type][res_name][res_lang] = data
  88. # Close file
  89. win32api.FreeLibrary(module_handle)
  90. return output
  91. def add_or_update_resource(filename, data, res_type, names=None, languages=None):
  92. """
  93. Update or add a single resource in the PE file with the given binary data.
  94. filename: path to the PE file.
  95. data: binary data to write to the resource.
  96. res_type: resource type to add/update (integer or string).
  97. names: a list of resource names (integers or strings) to update (None = all).
  98. languages: a list of resource languages (integers) to update (None = all).
  99. """
  100. if res_type == "*":
  101. raise ValueError("res_type cannot be a wildcard (*)!")
  102. names = set(names) if names is not None else {"*"}
  103. languages = set(languages) if languages is not None else {"*"}
  104. # Retrieve existing resources, filtered by the given resource type and given resource names and languages.
  105. resources = get_resources(filename, [res_type], names, languages)
  106. # Add res_type, name, language combinations that are not already present
  107. resources = resources.get(res_type, {}) # This is now a {name: {language: data}} dictionary
  108. for res_name in names:
  109. if res_name == "*":
  110. continue
  111. if res_name not in resources:
  112. resources[res_name] = {}
  113. for res_lang in languages:
  114. if res_lang == "*":
  115. continue
  116. if res_lang not in resources[res_name]:
  117. resources[res_name][res_lang] = None # Just an indicator
  118. # Add resource to the target file, overwriting the existing resources with same type, name, language combinations.
  119. module_handle = win32api.BeginUpdateResource(filename, 0)
  120. for res_name in resources.keys():
  121. for res_lang in resources[res_name].keys():
  122. win32api.UpdateResource(module_handle, res_type, res_name, data, res_lang)
  123. win32api.EndUpdateResource(module_handle, 0)
  124. def copy_resources_from_pe_file(filename, src_filename, types=None, names=None, languages=None):
  125. """
  126. Update or add resources in the given PE file by copying them over from the specified source PE file.
  127. filename: path to the PE file.
  128. src_filename: path to the source PE file.
  129. types: a list of resource types (integers or strings) to add/update via copy for (None = all).
  130. names: a list of resource names (integers or strings) to add/update via copy (None = all).
  131. languages: a list of resource languages (integers) to add/update via copy (None = all).
  132. """
  133. types = set(types) if types is not None else {"*"}
  134. names = set(names) if names is not None else {"*"}
  135. languages = set(languages) if languages is not None else {"*"}
  136. # Retrieve existing resources, filtered by the given resource type and given resource names and languages.
  137. resources = get_resources(src_filename, types, names, languages)
  138. for res_type, resources_for_type in resources.items():
  139. if "*" not in types and res_type not in types:
  140. continue
  141. for res_name, resources_for_type_name in resources_for_type.items():
  142. if "*" not in names and res_name not in names:
  143. continue
  144. for res_lang, data in resources_for_type_name.items():
  145. if "*" not in languages and res_lang not in languages:
  146. continue
  147. add_or_update_resource(filename, data, res_type, [res_name], [res_lang])
  148. def remove_all_resources(filename):
  149. """
  150. Remove all resources from the given PE file:
  151. """
  152. module_handle = win32api.BeginUpdateResource(filename, True) # bDeleteExistingResources=True
  153. win32api.EndUpdateResource(module_handle, False)