diff --git a/win/tools/autopatch/.gitignore b/win/tools/autopatch/.gitignore new file mode 100644 index 0000000..9cfdd43 --- /dev/null +++ b/win/tools/autopatch/.gitignore @@ -0,0 +1,107 @@ +# Do not stage vim swapfiles to commit +*.swp + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ diff --git a/win/tools/autopatch/README.md b/win/tools/autopatch/README.md new file mode 100644 index 0000000..fdaf9b4 --- /dev/null +++ b/win/tools/autopatch/README.md @@ -0,0 +1,43 @@ +autopatch +========= + +This tool is intended for internal usage. + +This script accepts path to Nvidia Drivers installer, makes .1337 patch and saves it in appropriate location in your working copy of this repo. All you have to do then is to test patch, stage it for commit and push it. Optionally you can just save patch to stdout. + +## Requirements + +* Python 3.2+ +* 7zip CLI utility + +## Synopsys + +``` +$ ./autopatch.py --help +usage: autopatch.py [-h] [-7 SEVENZIP] [-T TARGET] [-N TARGET_NAME] + [-S SEARCH] [-R REPLACEMENT] [-o] + installer_file + +Generates .1337 patch for Nvidia drivers for Windows + +positional arguments: + installer_file location of installer executable + +optional arguments: + -h, --help show this help message and exit + -7 SEVENZIP, --7zip SEVENZIP + location of 7-zip `7z` executable (default: 7z) + -T TARGET, --target TARGET + target location in archive (default: + Display.Driver/nvcuvid64.dl_) + -N TARGET_NAME, --target-name TARGET_NAME + name of installed target file. Used for patch header + (default: nvcuvid.dll) + -S SEARCH, --search SEARCH + representation of search pattern binary string + (default: FF908000000084C07408) + -R REPLACEMENT, --replacement REPLACEMENT + representation of replacement binary string (default: + FF908000000084C09090) + -o, --stdout output into stdout (default: False) +``` diff --git a/win/tools/autopatch/autopatch.py b/win/tools/autopatch/autopatch.py new file mode 100755 index 0000000..40c3b0e --- /dev/null +++ b/win/tools/autopatch/autopatch.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 + +import sys +import subprocess +import tempfile +import os.path +from binascii import unhexlify +import xml.etree.ElementTree as ET + + +PATCH_EXT = ".1337" +CRLF = b"\x0d\x0a" +HEADER_FORMAT = b">%s" +LINE_FORMAT = CRLF + b"%016X:%02X->%02X" +OFFSET_ADJUSTMENT = 0xC00 # shift specific to x64dbg .1337 format + + +def parse_args(): + import argparse + + parser = argparse.ArgumentParser( + description="Generates .1337 patch for Nvidia drivers for Windows", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument("installer_file", + help="location of installer executable") + parser.add_argument("-7", "--7zip", + default="7z", + dest="sevenzip", + help="location of 7-zip `7z` executable") + parser.add_argument("-T", "--target", + default="Display.Driver/nvcuvid64.dl_", + help="target location in archive") + parser.add_argument("-N", "--target-name", + default="nvcuvid.dll", + help="name of installed target file. Used for patch " + "header") + parser.add_argument("-S", "--search", + default="FF908000000084C07408", + help="representation of search pattern binary string") + parser.add_argument("-R", "--replacement", + default="FF908000000084C09090", + help="representation of replacement binary string") + parser.add_argument("-o", "--stdout", + action="store_true", + help="output into stdout") + args = parser.parse_args() + return args + + +class ExtractException(Exception): + pass + + +class PatternNotFoundException(Exception): + pass + + +class ExtractedTarget: + name = None + + def __init__(self, archive, dst_dir, arch_tgt, *, sevenzip="7z"): + self._archive = archive + self._dst_dir = dst_dir + self._sevenzip = sevenzip + self._arch_tgt = arch_tgt + + def __enter__(self): + ret = subprocess.call([self._sevenzip, + "e", + "-o" + self._dst_dir, + self._archive, + self._arch_tgt], + stdout=sys.stderr) + if ret != 0: + raise ExtractException("Subprocess returned non-zero exit code.") + name = os.path.join(self._dst_dir, os.path.basename(self._arch_tgt)) + self.name = name + return name + + def __exit__(self, exc_type, exc_value, traceback): + if self.name is not None: + os.remove(self.name) + + +def expand(filename, *, sevenzip="7z"): + proc = subprocess.Popen([sevenzip, + "x", + "-so", + filename], stdout=subprocess.PIPE) + result = proc.communicate()[0] + if proc.returncode != 0: + raise ExtractException("Subprocess returned non-zero exit code.") + return result + + +def extract_single_file(archive, filename, *, sevenzip="7z"): + proc = subprocess.Popen([sevenzip, + "e", + "-so", + archive, + filename], stdout=subprocess.PIPE) + result = proc.communicate()[0] + if proc.returncode != 0: + raise ExtractException("Subprocess returned non-zero exit code.") + return result + + +def make_patch(archive, *, + arch_tgt, + search, + replacement, + sevenzip="7z"): + with tempfile.TemporaryDirectory() as tmpdir: + with ExtractedTarget(archive, + tmpdir, + arch_tgt, + sevenzip=sevenzip) as tgt: + f = expand(tgt, sevenzip=sevenzip) + offset = f.find(search) + del f + if offset == -1: + raise PatternNotFoundException("Pattern not found.") + print("Pattern found @ %016X" % (offset,), file=sys.stderr) + + res = [] + for (i, (left, right)) in enumerate(zip(search, replacement)): + if left != right: + res.append((offset + i, left, right)) + return res + + +def identify_driver(archive, *, sevenzip="7z"): + manifest = extract_single_file(archive, "setup.cfg", sevenzip=sevenzip) + root = ET.fromstring(manifest) + version = root.attrib['version'] + product_type = root.find('./properties/string[@name="ProductType"]')\ + .attrib['value'] + return version, product_type + + +def format_patch(diff, filename): + res = HEADER_FORMAT % filename.encode('utf-8') + for offset, left, right in diff: + res += LINE_FORMAT % (offset + OFFSET_ADJUSTMENT, left, right) + return res + + +def main(): + args = parse_args() + search = unhexlify(args.search) + replacement = unhexlify(args.replacement) + assert len(search) == len(replacement), "len() of search and replacement"\ + " is not equal" + patch = make_patch(args.installer_file, + arch_tgt=args.target, + search=search, + replacement=replacement, + sevenzip=args.sevenzip) + patch_content = format_patch(patch, args.target_name) + if args.stdout: + with open(sys.stdout.fileno(), mode='wb', closefd=False) as out: + out.write(patch_content) + else: + version, product_type = identify_driver(args.installer_file, + sevenzip=args.sevenzip) + driver_name = "quadro_" + version if product_type == "100" else version + out_dir = os.path.join( + os.path.dirname( + os.path.abspath(__file__)), '..', '..', 'win10_x64', driver_name) + os.mkdir(out_dir, 0o755) + out_filename = os.path.join(out_dir, + os.path.splitext(args.target_name)[0] + PATCH_EXT) + with open(out_filename, 'wb') as out: + out.write(patch_content) + + +if __name__ == '__main__': + main()