Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(143)

Side by Side Diff: chrome/tools/build/win/resedit.py

Issue 8775026: A utility script to manipulate and extract resources from Windows programs. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """A utility script that can extract and edit resources in a Windows binary.
7
8 For detailed help, see the script's usage by invoking it with --help."""
9
10 import ctypes
11 import ctypes.wintypes
12 import logging
13 import optparse
14 import os
15 import shutil
16 import sys
17 import tempfile
18 import win32api
19 import win32con
20
21
22 _LOGGER = logging.getLogger(__name__)
23
24
25 # The win32api-supplied UpdateResource wrapper unfortunately does not allow
grt (UTC plus 2) 2011/12/02 02:38:11 is there an appropriate place to file a bug for th
Sigurður Ásgeirsson 2011/12/02 13:57:45 I don't know. This'd be in the win32api module imp
26 # one to remove resources due to overzeolus parameter verification.
robertshield 2011/12/02 00:35:42 overzealous
Sigurður Ásgeirsson 2011/12/02 13:57:45 Done.
27 # For that case we're foced to go straight to the native API implementation.
robertshield 2011/12/02 00:35:42 forced
Sigurður Ásgeirsson 2011/12/02 13:57:45 Done.
28 UpdateResource = ctypes.windll.kernel32.UpdateResourceW
29 UpdateResource.argtypes = [
30 ctypes.wintypes.HANDLE, # HANDLE hUpdate
31 ctypes.c_wchar_p, # LPCTSTR lpType
32 ctypes.c_wchar_p, # LPCTSTR lpName
33 ctypes.c_short, # WORD wLanguage
34 ctypes.c_void_p, # LPVOID lpData
35 ctypes.c_ulong, # DWORD cbData
36 ]
37 UpdateResource.restype = ctypes.c_short
38
39
40 def _ResIdToString(res_id):
41 # Convert integral res types/ids to a string.
42 if isinstance(res_id, int):
43 return "#%d" % res_id
44
45 return res_id
46
47
48 class _ResourceEditor(object):
49 """A utility class to make it easy to extract and manipulate resources in a
50 Windows binary."""
51
52 def __init__(self, input_file, output_file):
53 """Create a new editor.
54
55 Args:
56 input_file: path to the input file.
57 output_file: (optional) path to the output file.
58 """
59 self._input_file = input_file
60 self._output_file = output_file
61 self._modified = False
62 self._module = None
63 self._temp_dir = None
64 self._update_handle = None
65
66 def __del__(self):
67 if self._module:
68 win32api.FreeLibrary(self._module)
69 self._module = None
70
71 if self._update_handle:
72 _LOGGER.info('Canceling edits to "%s".', self.input_file)
73 win32api.EndUpdateResource(self._update_handle, False)
74 self._update_handle = None
75
76 if self._temp_dir:
77 _LOGGER.info('Removing temporary directory "%s".', self._temp_dir)
78 shutil.rmtree(self._temp_dir)
79 self._temp_dir = None
80
81 def _GetModule(self):
82 if not self._module:
83 # Specify a full path to LoadLibraryEx to prevent
84 # it from searching the path.
85 input_file = os.path.abspath(self.input_file)
86 _LOGGER.info('Loading input_file from "%s"', input_file)
87 self._module = win32api.LoadLibraryEx(
88 input_file, None, win32con.LOAD_LIBRARY_AS_DATAFILE)
robertshield 2011/12/02 00:35:42 does this raise an exception with a readable error
Sigurður Ásgeirsson 2011/12/02 13:57:45 Yups, you get the API call, the error code and the
89 return self._module
90
91 def _GetTempDir(self):
92 if not self._temp_dir:
93 self._temp_dir = tempfile.mkdtemp()
94 _LOGGER.info('Created temporary directory "%s".', self._temp_dir)
95
96 return self._temp_dir
97
98 def _GetUpdateHandle(self):
99 if not self._update_handle:
100 # Make a copy of the input file in the temp dir.
101 file_copy = os.path.join(self.temp_dir,
grt (UTC plus 2) 2011/12/02 02:38:11 how about keeping this value in the instance so yo
Sigurður Ásgeirsson 2011/12/02 13:57:45 Done.
102 os.path.basename(self._input_file))
103 shutil.copyfile(self._input_file, file_copy)
104 # Open a resource update handle on the copy.
105 _LOGGER.info('Opening temp file "%s".', file_copy)
106 self._update_handle = win32api.BeginUpdateResource(file_copy, False)
107
108 return self._update_handle
109
110 modified = property(lambda self: self._modified)
111 input_file = property(lambda self: self._input_file)
112 module = property(_GetModule)
113 temp_dir = property(_GetTempDir)
114 update_handle = property(_GetUpdateHandle)
115
116 def ExtractAllToDir(self, extract_to):
117 """Extracts all resources from our input file to a directory hierarchy
118 in the directory named extract_to.
119
120 The generated directory hierarchy is three-level, and looks like:
121 resource-type/
122 resource-name/
123 lang-id.
124
125 Args:
126 extract_to: path to the folder to output to. This folder will be erased
127 and recreated if it already exists.
128 """
129 _LOGGER.info('Extracting all resources from "%s" to directory "%s".',
130 self.input_file, extract_to)
131
132 if os.path.exists(extract_to):
133 _LOGGER.info('Destination directory "%s" exists, deleting', extract_to)
134 shutil.rmtree(extract_to)
135
136 # Make sure the destination dir exists.
137 os.makedirs(extract_to)
138
139 # Now enumerate the resource types.
140 for res_type in win32api.EnumResourceTypes(self.module):
141 res_type_str = _ResIdToString(res_type)
142
143 # And the resource names.
144 for res_name in win32api.EnumResourceNames(self.module, res_type):
145 res_name_str = _ResIdToString(res_name)
146
147 # Then the languages.
148 for res_lang in win32api.EnumResourceLanguages(self.module,
149 res_type, res_name):
150 res_lang_str = _ResIdToString(res_lang)
151
152 dest_dir = os.path.join(extract_to, res_type_str, res_lang_str)
153 dest_file = os.path.join(dest_dir, res_name_str)
154 _LOGGER.info('Extracting resource "%s", lang "%d" name "%s" '
155 'to file "%s".',
156 res_type_str, res_lang, res_name_str, dest_file)
157
158 # Extract each resource to a file in the output dir.
159 os.makedirs(dest_dir)
160 with open(dest_file, 'wb') as f:
161 f.write(win32api.LoadResource(self.module, res_type, res_name))
grt (UTC plus 2) 2011/12/02 02:38:11 This appears to be using FindResource rather than
Sigurður Ásgeirsson 2011/12/02 13:57:45 It does take a langid as an optional param, my ove
162
163 def ExtractResource(self, res_type, res_name, dest_file):
164 """Extracts a given resource, specified by type and name, to a given file.
165
166 Args:
167 res_type: the type of the resource, e.g. "B7".
168 res_name: the name of the resource, e.g. "SETUP.EXE".
169 dest_file: path to the file where the resource data will be written.
170 """
171 _LOGGER.info('Extracting resource "%s:%s" to file "%s".',
172 res_type, res_name, dest_file)
173
174 with open(dest_file, 'wb') as f:
175 f.write(win32api.LoadResource(self.module, res_type, res_name))
grt (UTC plus 2) 2011/12/02 02:38:11 See previous comment.
Sigurður Ásgeirsson 2011/12/02 13:57:45 Done.
176
177 def RemoveResource(self, res_type, res_lang, res_name):
178 """Removes a given resource, specified by type, language id and name.
179
180 Args:
181 res_type: the type of the resource, e.g. "B7".
182 res_lang: the language id of the resource, e.g. 1033.
183 res_name: the name of the resource, e.g. "SETUP.EXE".
184 """
185 _LOGGER.info('Removing resource "%s:%s".', res_type, res_name)
186 # We have to go native to perform a removal.
187 ret = UpdateResource(self.update_handle,
188 res_type,
189 res_name,
190 res_lang,
191 None,
192 0)
193 # Raise an error on failure.
194 if ret == 0:
195 error = win32api.GetLastError()
196 print "error", error
197 raise RuntimeError(error)
198 self._modified = True
199
200 def UpdateResource(self, res_type, res_lang, res_name, file_path):
201 """Inserts or updates a given resource with the contents of a file.
202
203 Args:
204 res_type: the type of the resource, e.g. "B7".
205 res_lang: the language id of the resource, e.g. 1033.
206 res_name: the name of the resource, e.g. "SETUP.EXE".
207 file_path: path to the file containing the new resource data.
208 """
209 _LOGGER.info('Writing resource "%s:%s" from file.',
210 res_type, res_name, file_path)
211
212 with open(file_path, 'rb') as f:
213 win32api.UpdateResource(self.update_handle,
214 res_type,
215 res_name,
216 f.read(),
217 res_lang);
218
219 self._modified = True
220
221 def Commit(self):
222 """Commit any successful resource edits this editor has performed.
223
224 This has the effect of writing the output file.
225 """
226 if self._update_handle:
227 update_handle = self._update_handle
228 self._update_handle = None
229 win32api.EndUpdateResource(update_handle, False)
230
231 temp_file = os.path.join(self.temp_dir, os.path.basename(self.input_file))
232 _LOGGER.info('Writing edited file to "%s".', self._output_file)
233 shutil.copyfile(temp_file, self._output_file)
234
235
236 _USAGE = """\
237 usage: %prog [options] input_file
238
239 A utility script to extract and edit the resources in a Windows executable.
240
241 EXAMPLE USAGE:
242 # Extract from mini_installer.exe, the resource type "B7", langid 1033 and
grt (UTC plus 2) 2011/12/02 02:38:11 Either remove references to langid and 1033, or fi
Sigurður Ásgeirsson 2011/12/02 13:57:45 Done.
243 # name "CHROME.PACKED.7Z" to a file named chrome.7z.
244 # Note that 1033 corresponds to English (United States).
245 %prog mini_installer.exe --extract B7 1033 CHROME.PACKED.7Z chrome.7z
246
247 # Update mini_installer.exe by removing the resouce type "BL", langid 1033 and
248 # name "SETUP.EXE". Add the resource type "B7", langid 1033 and name
249 # "SETUP.EXE.packed.7z" from the file setup.packed.7z.
250 # Write the edited file to mini_installer_packed.exe.
251 %prog mini_installer.exe \\
252 --remove BL 1033 SETUP.EXE \\
253 --update B7 1033 SETUP.EXE.packed.7z setup.packed.7z \\
254 --output-file mini_installer_packed.exe
255 """
256
257 def _ParseArgs():
258 parser = optparse.OptionParser(_USAGE)
259 parser.add_option('', '--verbose', action='store_true',
260 help='Enable verbose logging.')
261 parser.add_option('', '--extract_all',
262 help='Path to a folder which will be created, in which all resources '
263 'from the input_file will be stored, each in a file named '
264 '"res_type/lang_id/res_name".')
265 parser.add_option('', '--extract', action='append', default=[], nargs=3,
266 help='Extract the resource with the given type and name to the given '
267 'file.',
268 metavar='type name')
269 parser.add_option('', '--remove', action='append', default=[], nargs=3,
270 help='Remove the resource with the given type, langid and name.',
271 metavar='type langid name')
272 parser.add_option('', '--update', action='append', default=[], nargs=4,
273 help='Insert or update the resource with the given type, langid and '
274 'name with the contents of the file given.',
275 metavar='type langid name file_path')
276 parser.add_option('', '--output_file',
277 help='On success, OUTPUT_FILE will written with a copy of the '
robertshield 2011/12/02 00:35:42 will be written
Sigurður Ásgeirsson 2011/12/02 13:57:45 Done.
278 'input file with the edits specified by any remove or update '
279 'options.')
280
281 options, args = parser.parse_args()
282
283 if len(args) != 1:
284 parser.error('You have to specify an input file to work on.')
285
286 modify = options.remove or options.update
287 if modify and not options.output_file:
288 parser.error('You have to specify an output file with edit options.')
289
290 return options, args
291
292
293 def main(options, args):
294 """Main program for the script."""
295 if options.verbose:
296 logging.basicConfig(level=logging.INFO)
297
298 # Create the editor for our input file.
299 editor = _ResourceEditor(args[0], options.output_file)
300
301 if options.extract_all:
302 editor.ExtractAllToDir(options.extract_all)
303
304 for res_type, res_name, dest_file in options.extract:
305 editor.ExtractResource(res_type, res_name, dest_file)
306
307 for res_type, res_lang, res_name in options.remove:
308 editor.RemoveResource(res_type, int(res_lang), res_name)
309
310 for res_type, res_lang, res_name, src_file in options.update:
311 editor.UpdateResource(res_type, int(res_lang), res_name, src_file)
312
313 if editor.modified:
314 editor.Commit()
315
316
317 if __name__ == '__main__':
318 sys.exit(main(*_ParseArgs()))
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698