winresource.py 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. #-----------------------------------------------------------------------------
  2. # Copyright (c) 2013-2021, 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. Commandline usage:
  14. winresource.py <dstpath> <srcpath>
  15. Updates or adds resources from file <srcpath> in file <dstpath>.
  16. """
  17. import PyInstaller.log as logging
  18. from PyInstaller.compat import pywintypes, win32api
  19. logger = logging.getLogger(__name__)
  20. LOAD_LIBRARY_AS_DATAFILE = 2
  21. ERROR_BAD_EXE_FORMAT = 193
  22. ERROR_RESOURCE_DATA_NOT_FOUND = 1812
  23. ERROR_RESOURCE_TYPE_NOT_FOUND = 1813
  24. ERROR_RESOURCE_NAME_NOT_FOUND = 1814
  25. ERROR_RESOURCE_LANG_NOT_FOUND = 1815
  26. class File(object):
  27. """
  28. Win32 PE file class.
  29. """
  30. def __init__(self, filename):
  31. self.filename = filename
  32. def get_resources(self, types=None, names=None, languages=None):
  33. """
  34. Get resources.
  35. types = a list of resource types to search for (None = all)
  36. names = a list of resource names to search for (None = all)
  37. languages = a list of resource languages to search for (None = all)
  38. Return a dict of the form {type_: {name: {language: data}}}, which might also be empty if no matching resources
  39. were found.
  40. """
  41. return GetResources(self.filename, types, names, languages)
  42. def update_resources(self, data, type_, names=None, languages=None):
  43. """
  44. Update or add resource data.
  45. type_ = resource type to update
  46. names = a list of resource names to update (None = all)
  47. languages = a list of resource languages to update (None = all)
  48. """
  49. UpdateResources(self.filename, data, type_, names, languages)
  50. def update_resources_from_datafile(self, srcpath, type_, names=None, languages=None):
  51. """
  52. Update or add resource data from file srcpath.
  53. type_ = resource type to update
  54. names = a list of resource names to update (None = all)
  55. languages = a list of resource languages to update (None = all)
  56. """
  57. UpdateResourcesFromDataFile(self.filename, srcpath, type_, names, languages)
  58. def update_resources_from_dict(self, res, types=None, names=None, languages=None):
  59. """
  60. Update or add resources from resource dict.
  61. types = a list of resource types to update (None = all)
  62. names = a list of resource names to update (None = all)
  63. languages = a list of resource languages to update (None = all)
  64. """
  65. UpdateResourcesFromDict(self.filename, res, types, names, languages)
  66. def update_resources_from_resfile(self, srcpath, types=None, names=None, languages=None):
  67. """
  68. Update or add resources from dll/exe file srcpath.
  69. types = a list of resource types to update (None = all)
  70. names = a list of resource names to update (None = all)
  71. languages = a list of resource languages to update (None = all)
  72. """
  73. UpdateResourcesFromResFile(self.filename, srcpath, types, names, languages)
  74. def _GetResources(hsrc, types=None, names=None, languages=None):
  75. """
  76. Get resources from hsrc.
  77. types = a list of resource types to search for (None = all)
  78. names = a list of resource names to search for (None = all)
  79. languages = a list of resource languages to search for (None = all)
  80. Return a dict of the form {type_: {name: {language: data}}}, which might also be empty if no matching resources
  81. were found.
  82. """
  83. if types:
  84. types = set(types)
  85. if names:
  86. names = set(names)
  87. if languages:
  88. languages = set(languages)
  89. res = {}
  90. try:
  91. # logger.debug("Enumerating resource types")
  92. enum_types = win32api.EnumResourceTypes(hsrc)
  93. if types and "*" not in types:
  94. enum_types = filter(lambda type_: type_ in types, enum_types)
  95. for type_ in enum_types:
  96. # logger.debug("Enumerating resources of type %s", type_)
  97. enum_names = win32api.EnumResourceNames(hsrc, type_)
  98. if names and "*" not in names:
  99. enum_names = filter(lambda name: name in names, enum_names)
  100. for name in enum_names:
  101. # logger.debug("Enumerating resources of type %s name %s", type_, name)
  102. enum_languages = win32api.EnumResourceLanguages(hsrc, type_, name)
  103. if languages and "*" not in languages:
  104. enum_languages = filter(lambda language: language in languages, enum_languages)
  105. for language in enum_languages:
  106. data = win32api.LoadResource(hsrc, type_, name, language)
  107. if type_ not in res:
  108. res[type_] = {}
  109. if name not in res[type_]:
  110. res[type_][name] = {}
  111. res[type_][name][language] = data
  112. except pywintypes.error as exception:
  113. if exception.args[0] in (
  114. ERROR_RESOURCE_DATA_NOT_FOUND,
  115. ERROR_RESOURCE_TYPE_NOT_FOUND,
  116. ERROR_RESOURCE_NAME_NOT_FOUND,
  117. ERROR_RESOURCE_LANG_NOT_FOUND,
  118. ):
  119. # logger.info('%s: %s', exception.args[1:3])
  120. pass
  121. else:
  122. raise exception
  123. return res
  124. def GetResources(filename, types=None, names=None, languages=None):
  125. """
  126. Get resources from dll/exe file.
  127. types = a list of resource types to search for (None = all)
  128. names = a list of resource names to search for (None = all)
  129. languages = a list of resource languages to search for (None = all)
  130. Return a dict of the form {type_: {name: {language: data}}}, which might also be empty if no matching resources
  131. were found.
  132. """
  133. hsrc = win32api.LoadLibraryEx(filename, 0, LOAD_LIBRARY_AS_DATAFILE)
  134. res = _GetResources(hsrc, types, names, languages)
  135. win32api.FreeLibrary(hsrc)
  136. return res
  137. def UpdateResources(dstpath, data, type_, names=None, languages=None):
  138. """
  139. Update or add resource data in dll/exe file dstpath.
  140. type_ = resource type to update
  141. names = a list of resource names to update (None = all)
  142. languages = a list of resource languages to update (None = all)
  143. """
  144. # Look for existing resources.
  145. res = GetResources(dstpath, [type_], names, languages)
  146. # add type_, names and languages not already present in existing resources
  147. if type_ not in res and type_ != "*":
  148. res[type_] = {}
  149. if names:
  150. for name in names:
  151. if name not in res[type_] and name != "*":
  152. res[type_][name] = []
  153. if languages:
  154. for language in languages:
  155. if language not in res[type_][name] and language != "*":
  156. res[type_][name].append(language)
  157. # add resource to destination, overwriting existing resources
  158. hdst = win32api.BeginUpdateResource(dstpath, 0)
  159. for type_ in res:
  160. for name in res[type_]:
  161. for language in res[type_][name]:
  162. logger.info("Updating resource type %s name %s language %s", type_, name, language)
  163. win32api.UpdateResource(hdst, type_, name, data, language)
  164. win32api.EndUpdateResource(hdst, 0)
  165. def UpdateResourcesFromDataFile(dstpath, srcpath, type_, names=None, languages=None):
  166. """
  167. Update or add resource data from file srcpath in dll/exe file dstpath.
  168. type_ = resource type to update
  169. names = a list of resource names to update (None = all)
  170. languages = a list of resource languages to update (None = all)
  171. """
  172. with open(srcpath, "rb") as src:
  173. data = src.read()
  174. UpdateResources(dstpath, data, type_, names, languages)
  175. def UpdateResourcesFromDict(dstpath, res, types=None, names=None, languages=None):
  176. """
  177. Update or add resources from resource dict in dll/exe file dstpath.
  178. types = a list of resource types to update (None = all)
  179. names = a list of resource names to update (None = all)
  180. languages = a list of resource languages to update (None = all)
  181. """
  182. if types:
  183. types = set(types)
  184. if names:
  185. names = set(names)
  186. if languages:
  187. languages = set(languages)
  188. for type_ in res:
  189. if not types or type_ in types:
  190. for name in res[type_]:
  191. if not names or name in names:
  192. for language in res[type_][name]:
  193. if not languages or language in languages:
  194. UpdateResources(dstpath, res[type_][name][language], type_, [name], [language])
  195. def UpdateResourcesFromResFile(dstpath, srcpath, types=None, names=None, languages=None):
  196. """
  197. Update or add resources from dll/exe file srcpath in dll/exe file dstpath.
  198. types = a list of resource types to update (None = all)
  199. names = a list of resource names to update (None = all)
  200. languages = a list of resource languages to update (None = all)
  201. """
  202. res = GetResources(srcpath, types, names, languages)
  203. UpdateResourcesFromDict(dstpath, res)
  204. def RemoveAllResources(filename):
  205. """
  206. Remove all resources from the dll/exe file.
  207. """
  208. hsrc = win32api.BeginUpdateResource(filename, True) # bDeleteExistingResources=True
  209. win32api.EndUpdateResource(hsrc, False)