Skip to content

Latest commit

 

History

History
143 lines (114 loc) · 4.68 KB

Patch-Delta.md

File metadata and controls

143 lines (114 loc) · 4.68 KB
layout title parent
default
Patch Delta Script
Resources

Patch Delta

source: https://gist.githubusercontent.com/wumb0/9542469e3915953f7ae02d63998d2553/raw/bd1c5f4e54fd0245d51b5db282d1d4554876286a/delta_patch.py

from ctypes import (windll, wintypes, c\_uint64, cast, POINTER, Union, c\_ubyte,
                    LittleEndianStructure, byref, c\_size\_t)
import zlib


# types and flags
DELTA\_FLAG\_TYPE             = c\_uint64
DELTA\_FLAG\_NONE             = 0x00000000
DELTA\_APPLY\_FLAG\_ALLOW\_PA19 = 0x00000001


# structures
class DELTA\_INPUT(LittleEndianStructure):
    class U1(Union):
        \_fields\_ = \[('lpcStart', wintypes.LPVOID),
                    ('lpStart', wintypes.LPVOID)\]
    \_anonymous\_ = ('u1',)
    \_fields\_ = \[('u1', U1),
                ('uSize', c\_size\_t),
                ('Editable', wintypes.BOOL)\]


class DELTA\_OUTPUT(LittleEndianStructure):
    \_fields\_ = \[('lpStart', wintypes.LPVOID),
                ('uSize', c\_size\_t)\]


# functions
ApplyDeltaB = windll.msdelta.ApplyDeltaB
ApplyDeltaB.argtypes = \[DELTA\_FLAG\_TYPE, DELTA\_INPUT, DELTA\_INPUT,
                        POINTER(DELTA\_OUTPUT)\]
ApplyDeltaB.rettype = wintypes.BOOL
DeltaFree = windll.msdelta.DeltaFree
DeltaFree.argtypes = \[wintypes.LPVOID\]
DeltaFree.rettype = wintypes.BOOL
gle = windll.kernel32.GetLastError


def apply\_patchfile\_to\_buffer(buf, buflen, patchpath, legacy):
    with open(patchpath, 'rb') as patch:
        patch\_contents = patch.read()

    # some patches (Windows Update MSU) come with a CRC32 prepended to the file
    # if the file doesn't start with the signature (PA) then check it
    if patch\_contents\[:2\] != b"PA":
        paoff = patch\_contents.find(b"PA")
        if paoff != 4:
            raise Exception("Patch is invalid")
        crc = int.from\_bytes(patch\_contents\[:4\], 'little')
        patch\_contents = patch\_contents\[4:\]
        if zlib.crc32(patch\_contents) != crc:
            raise Exception("CRC32 check failed. Patch corrupted or invalid")

    applyflags = DELTA\_APPLY\_FLAG\_ALLOW\_PA19 if legacy else DELTA\_FLAG\_NONE

    dd = DELTA\_INPUT()
    ds = DELTA\_INPUT()
    dout = DELTA\_OUTPUT()

    ds.lpcStart = buf
    ds.uSize = buflen
    ds.Editable = False

    dd.lpcStart = cast(patch\_contents, wintypes.LPVOID)
    dd.uSize = len(patch\_contents)
    dd.Editable = False

    status = ApplyDeltaB(applyflags, ds, dd, byref(dout))
    if status == 0:
        raise Exception("Patch {} failed with error {}".format(patchpath, gle()))

    return (dout.lpStart, dout.uSize)


if \_\_name\_\_ == '\_\_main\_\_':
    import sys
    import base64
    import hashlib
    import argparse

    ap = argparse.ArgumentParser()
    mode = ap.add\_mutually\_exclusive\_group(required=True)
    output = ap.add\_mutually\_exclusive\_group(required=True)
    mode.add\_argument("-i", "--input-file",
                      help="File to patch (forward or reverse)")
    mode.add\_argument("-n", "--null", action="store\_true", default=False,
                      help="Create the output file from a null diff "
                           "(null diff must be the first one specified)")
    output.add\_argument("-o", "--output-file",
                        help="Destination to write patched file to")
    output.add\_argument("-d", "--dry-run", action="store\_true",
                        help="Don't write patch, just see if it would patch"
                             "correctly and get the resulting hash")
    ap.add\_argument("-l", "--legacy", action='store\_true', default=False,
                    help="Let the API use the PA19 legacy API (if required)")
    ap.add\_argument("patches", nargs='+', help="Patches to apply")
    args = ap.parse\_args()

    if not args.dry\_run and not args.output\_file:
        print("Either specify -d or -o", file=sys.stderr)
        ap.print\_help()
        sys.exit(1)

    if args.null:
        inbuf = b""
    else:
        with open(args.input\_file, 'rb') as r:
            inbuf = r.read()

    buf = cast(inbuf, wintypes.LPVOID)
    n = len(inbuf)
    to\_free = \[\]
    try:
        for patch in args.patches:
            buf, n = apply\_patchfile\_to\_buffer(buf, n, patch, args.legacy)
            to\_free.append(buf)

        outbuf = bytes((c\_ubyte\*n).from\_address(buf))
        if not args.dry\_run:
            with open(args.output\_file, 'wb') as w:
                w.write(outbuf)
    finally:
        for buf in to\_free:
            DeltaFree(buf)

    finalhash = hashlib.sha256(outbuf)
    print("Applied {} patch{} successfully"
          .format(len(args.patches), "es" if len(args.patches) > 1 else ""))
    print("Final hash: {}"
          .format(base64.b64encode(finalhash.digest()).decode()))