Index: chrome/tools/build/win/resedit.py |
diff --git a/chrome/tools/build/win/resedit.py b/chrome/tools/build/win/resedit.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..22724a35f89f945dd8e95bfcba6692c90dd192f8 |
--- /dev/null |
+++ b/chrome/tools/build/win/resedit.py |
@@ -0,0 +1,320 @@ |
+#!/usr/bin/env python |
+# Copyright (c) 2011 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+"""A utility script that can extract and edit resources in a Windows binary. |
+ |
+For detailed help, see the script's usage by invoking it with --help.""" |
+ |
+import ctypes |
+import ctypes.wintypes |
+import logging |
+import optparse |
+import os |
+import shutil |
+import sys |
+import tempfile |
+import win32api |
+import win32con |
+ |
+ |
+_LOGGER = logging.getLogger(__name__) |
+ |
+ |
+# The win32api-supplied UpdateResource wrapper unfortunately does not allow |
+# one to remove resources due to overzealous parameter verification. |
+# For that case we're forced to go straight to the native API implementation. |
+UpdateResource = ctypes.windll.kernel32.UpdateResourceW |
+UpdateResource.argtypes = [ |
+ ctypes.wintypes.HANDLE, # HANDLE hUpdate |
+ ctypes.c_wchar_p, # LPCTSTR lpType |
+ ctypes.c_wchar_p, # LPCTSTR lpName |
+ ctypes.c_short, # WORD wLanguage |
+ ctypes.c_void_p, # LPVOID lpData |
+ ctypes.c_ulong, # DWORD cbData |
+ ] |
+UpdateResource.restype = ctypes.c_short |
+ |
+ |
+def _ResIdToString(res_id): |
+ # Convert integral res types/ids to a string. |
+ if isinstance(res_id, int): |
+ return "#%d" % res_id |
+ |
+ return res_id |
+ |
+ |
+class _ResourceEditor(object): |
+ """A utility class to make it easy to extract and manipulate resources in a |
+ Windows binary.""" |
+ |
+ def __init__(self, input_file, output_file): |
+ """Create a new editor. |
+ |
+ Args: |
+ input_file: path to the input file. |
+ output_file: (optional) path to the output file. |
+ """ |
+ self._input_file = input_file |
+ self._output_file = output_file |
+ self._modified = False |
+ self._module = None |
+ self._temp_dir = None |
+ self._temp_file = None |
+ self._update_handle = None |
+ |
+ def __del__(self): |
+ if self._module: |
+ win32api.FreeLibrary(self._module) |
+ self._module = None |
+ |
+ if self._update_handle: |
+ _LOGGER.info('Canceling edits to "%s".', self.input_file) |
+ win32api.EndUpdateResource(self._update_handle, False) |
+ self._update_handle = None |
+ |
+ if self._temp_dir: |
+ _LOGGER.info('Removing temporary directory "%s".', self._temp_dir) |
+ shutil.rmtree(self._temp_dir) |
+ self._temp_dir = None |
+ |
+ def _GetModule(self): |
+ if not self._module: |
+ # Specify a full path to LoadLibraryEx to prevent |
+ # it from searching the path. |
+ input_file = os.path.abspath(self.input_file) |
+ _LOGGER.info('Loading input_file from "%s"', input_file) |
+ self._module = win32api.LoadLibraryEx( |
+ input_file, None, win32con.LOAD_LIBRARY_AS_DATAFILE) |
+ return self._module |
+ |
+ def _GetTempDir(self): |
+ if not self._temp_dir: |
+ self._temp_dir = tempfile.mkdtemp() |
+ _LOGGER.info('Created temporary directory "%s".', self._temp_dir) |
+ |
+ return self._temp_dir |
+ |
+ def _GetUpdateHandle(self): |
+ if not self._update_handle: |
+ # Make a copy of the input file in the temp dir. |
+ self._temp_file = os.path.join(self.temp_dir, |
+ os.path.basename(self._input_file)) |
+ shutil.copyfile(self._input_file, self._temp_file) |
+ # Open a resource update handle on the copy. |
+ _LOGGER.info('Opening temp file "%s".', self._temp_file) |
+ self._update_handle = win32api.BeginUpdateResource(self._temp_file, False) |
+ |
+ return self._update_handle |
+ |
+ modified = property(lambda self: self._modified) |
+ input_file = property(lambda self: self._input_file) |
+ module = property(_GetModule) |
+ temp_dir = property(_GetTempDir) |
+ update_handle = property(_GetUpdateHandle) |
+ |
+ def ExtractAllToDir(self, extract_to): |
+ """Extracts all resources from our input file to a directory hierarchy |
+ in the directory named extract_to. |
+ |
+ The generated directory hierarchy is three-level, and looks like: |
+ resource-type/ |
+ resource-name/ |
+ lang-id. |
+ |
+ Args: |
+ extract_to: path to the folder to output to. This folder will be erased |
+ and recreated if it already exists. |
+ """ |
+ _LOGGER.info('Extracting all resources from "%s" to directory "%s".', |
+ self.input_file, extract_to) |
+ |
+ if os.path.exists(extract_to): |
+ _LOGGER.info('Destination directory "%s" exists, deleting', extract_to) |
+ shutil.rmtree(extract_to) |
+ |
+ # Make sure the destination dir exists. |
+ os.makedirs(extract_to) |
+ |
+ # Now enumerate the resource types. |
+ for res_type in win32api.EnumResourceTypes(self.module): |
+ res_type_str = _ResIdToString(res_type) |
+ |
+ # And the resource names. |
+ for res_name in win32api.EnumResourceNames(self.module, res_type): |
+ res_name_str = _ResIdToString(res_name) |
+ |
+ # Then the languages. |
+ for res_lang in win32api.EnumResourceLanguages(self.module, |
+ res_type, res_name): |
+ res_lang_str = _ResIdToString(res_lang) |
+ |
+ dest_dir = os.path.join(extract_to, res_type_str, res_lang_str) |
+ dest_file = os.path.join(dest_dir, res_name_str) |
+ _LOGGER.info('Extracting resource "%s", lang "%d" name "%s" ' |
+ 'to file "%s".', |
+ res_type_str, res_lang, res_name_str, dest_file) |
+ |
+ # Extract each resource to a file in the output dir. |
+ os.makedirs(dest_dir) |
+ self.ExtractResource(res_type, res_lang, res_name, dest_file) |
+ |
+ def ExtractResource(self, res_type, res_lang, res_name, dest_file): |
+ """Extracts a given resource, specified by type, language id and name, |
+ to a given file. |
+ |
+ Args: |
+ res_type: the type of the resource, e.g. "B7". |
+ res_lang: the language id of the resource e.g. 1033. |
+ res_name: the name of the resource, e.g. "SETUP.EXE". |
+ dest_file: path to the file where the resource data will be written. |
+ """ |
+ _LOGGER.info('Extracting resource "%s", lang "%d" name "%s" ' |
+ 'to file "%s".', res_type, res_lang, res_name, dest_file) |
+ |
+ data = win32api.LoadResource(self.module, res_type, res_name, res_lang) |
+ with open(dest_file, 'wb') as f: |
+ f.write(data) |
+ |
+ def RemoveResource(self, res_type, res_lang, res_name): |
+ """Removes a given resource, specified by type, language id and name. |
+ |
+ Args: |
+ res_type: the type of the resource, e.g. "B7". |
+ res_lang: the language id of the resource, e.g. 1033. |
+ res_name: the name of the resource, e.g. "SETUP.EXE". |
+ """ |
+ _LOGGER.info('Removing resource "%s:%s".', res_type, res_name) |
+ # We have to go native to perform a removal. |
+ ret = UpdateResource(self.update_handle, |
+ res_type, |
+ res_name, |
+ res_lang, |
+ None, |
+ 0) |
+ # Raise an error on failure. |
+ if ret == 0: |
+ error = win32api.GetLastError() |
+ print "error", error |
+ raise RuntimeError(error) |
+ self._modified = True |
+ |
+ def UpdateResource(self, res_type, res_lang, res_name, file_path): |
+ """Inserts or updates a given resource with the contents of a file. |
+ |
+ Args: |
+ res_type: the type of the resource, e.g. "B7". |
+ res_lang: the language id of the resource, e.g. 1033. |
+ res_name: the name of the resource, e.g. "SETUP.EXE". |
+ file_path: path to the file containing the new resource data. |
+ """ |
+ _LOGGER.info('Writing resource "%s:%s" from file.', |
+ res_type, res_name, file_path) |
+ |
+ with open(file_path, 'rb') as f: |
+ win32api.UpdateResource(self.update_handle, |
+ res_type, |
+ res_name, |
+ f.read(), |
+ res_lang); |
+ |
+ self._modified = True |
+ |
+ def Commit(self): |
+ """Commit any successful resource edits this editor has performed. |
+ |
+ This has the effect of writing the output file. |
+ """ |
+ if self._update_handle: |
+ update_handle = self._update_handle |
+ self._update_handle = None |
+ win32api.EndUpdateResource(update_handle, False) |
+ |
+ _LOGGER.info('Writing edited file to "%s".', self._output_file) |
+ shutil.copyfile(self._temp_file, self._output_file) |
+ |
+ |
+_USAGE = """\ |
+usage: %prog [options] input_file |
+ |
+A utility script to extract and edit the resources in a Windows executable. |
+ |
+EXAMPLE USAGE: |
+# Extract from mini_installer.exe, the resource type "B7", langid 1033 and |
+# name "CHROME.PACKED.7Z" to a file named chrome.7z. |
+# Note that 1033 corresponds to English (United States). |
+%prog mini_installer.exe --extract B7 1033 CHROME.PACKED.7Z chrome.7z |
+ |
+# Update mini_installer.exe by removing the resouce type "BL", langid 1033 and |
+# name "SETUP.EXE". Add the resource type "B7", langid 1033 and name |
+# "SETUP.EXE.packed.7z" from the file setup.packed.7z. |
+# Write the edited file to mini_installer_packed.exe. |
+%prog mini_installer.exe \\ |
+ --remove BL 1033 SETUP.EXE \\ |
+ --update B7 1033 SETUP.EXE.packed.7z setup.packed.7z \\ |
+ --output-file mini_installer_packed.exe |
+""" |
+ |
+def _ParseArgs(): |
+ parser = optparse.OptionParser(_USAGE) |
+ parser.add_option('', '--verbose', action='store_true', |
+ help='Enable verbose logging.') |
+ parser.add_option('', '--extract_all', |
+ help='Path to a folder which will be created, in which all resources ' |
+ 'from the input_file will be stored, each in a file named ' |
+ '"res_type/lang_id/res_name".') |
+ parser.add_option('', '--extract', action='append', default=[], nargs=4, |
+ help='Extract the resource with the given type, language id and name ' |
+ 'to the given file.', |
+ metavar='type langid name file_path') |
+ parser.add_option('', '--remove', action='append', default=[], nargs=3, |
+ help='Remove the resource with the given type, langid and name.', |
+ metavar='type langid name') |
+ parser.add_option('', '--update', action='append', default=[], nargs=4, |
+ help='Insert or update the resource with the given type, langid and ' |
+ 'name with the contents of the file given.', |
+ metavar='type langid name file_path') |
+ parser.add_option('', '--output_file', |
+ help='On success, OUTPUT_FILE will be written with a copy of the ' |
+ 'input file with the edits specified by any remove or update ' |
+ 'options.') |
+ |
+ options, args = parser.parse_args() |
+ |
+ if len(args) != 1: |
+ parser.error('You have to specify an input file to work on.') |
+ |
+ modify = options.remove or options.update |
+ if modify and not options.output_file: |
+ parser.error('You have to specify an output file with edit options.') |
+ |
+ return options, args |
+ |
+ |
+def main(options, args): |
+ """Main program for the script.""" |
+ if options.verbose: |
+ logging.basicConfig(level=logging.INFO) |
+ |
+ # Create the editor for our input file. |
+ editor = _ResourceEditor(args[0], options.output_file) |
+ |
+ if options.extract_all: |
+ editor.ExtractAllToDir(options.extract_all) |
+ |
+ for res_type, res_lang, res_name, dest_file in options.extract: |
+ editor.ExtractResource(res_type, int(res_lang), res_name, dest_file) |
+ |
+ for res_type, res_lang, res_name in options.remove: |
+ editor.RemoveResource(res_type, int(res_lang), res_name) |
+ |
+ for res_type, res_lang, res_name, src_file in options.update: |
+ editor.UpdateResource(res_type, int(res_lang), res_name, src_file) |
+ |
+ if editor.modified: |
+ editor.Commit() |
+ |
+ |
+if __name__ == '__main__': |
+ sys.exit(main(*_ParseArgs())) |