summaryrefslogtreecommitdiffstats
path: root/chrome/tools
diff options
context:
space:
mode:
authorsiggi@chromium.org <siggi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-02 15:52:19 +0000
committersiggi@chromium.org <siggi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2011-12-02 15:52:19 +0000
commitfb9069d24ec3f0a234d3a256ac5fc8d5d49bbdcc (patch)
tree8338425c02713c14114d0f53a916b4eb48eefa6c /chrome/tools
parentbe09131b34b3f71a871370652584e07b18a7a6fa (diff)
downloadchromium_src-fb9069d24ec3f0a234d3a256ac5fc8d5d49bbdcc.zip
chromium_src-fb9069d24ec3f0a234d3a256ac5fc8d5d49bbdcc.tar.gz
chromium_src-fb9069d24ec3f0a234d3a256ac5fc8d5d49bbdcc.tar.bz2
A utility script to manipulate and extract resources from Windows programs.
This might be considered a first step in making our release signing process a little less opaque and brittle. R=bradnelson@chromium.org,robertshield@chromium.org,grt@chromium.org BUG=36234 Review URL: http://codereview.chromium.org/8775026 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@112707 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/tools')
-rw-r--r--chrome/tools/build/win/resedit.py320
1 files changed, 320 insertions, 0 deletions
diff --git a/chrome/tools/build/win/resedit.py b/chrome/tools/build/win/resedit.py
new file mode 100644
index 0000000..22724a3
--- /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()))