layout | title | parent |
---|---|---|
default |
Patch Delta Script |
Resources |
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()))