111 lines
4.5 KiB
Python
111 lines
4.5 KiB
Python
|
import zlib
|
||
|
import bps.operations as ops
|
||
|
|
||
|
def make_bps(source, target):
|
||
|
# This function was adapted from
|
||
|
# https://github.com/Alcaro/Flips/blob/master/libbps.cpp
|
||
|
# originally written by Alcaro, used under the GPLv3.0 license
|
||
|
sourcelen = len(source)
|
||
|
targetlen = len(target)
|
||
|
sourcepos = 0
|
||
|
targetpos = 0
|
||
|
lastknownchange = 0
|
||
|
|
||
|
yield ops.Header(sourcelen, targetlen, "")
|
||
|
while targetpos < targetlen:
|
||
|
numunchanged = 0
|
||
|
while (sourcepos+numunchanged < sourcelen and
|
||
|
targetpos+numunchanged < targetlen and
|
||
|
source[sourcepos+numunchanged] == target[targetpos+numunchanged]):
|
||
|
numunchanged += 1
|
||
|
if numunchanged > 1 or numunchanged == targetlen - targetpos:
|
||
|
yield ops.SourceRead(numunchanged)
|
||
|
sourcepos += numunchanged
|
||
|
targetpos += numunchanged
|
||
|
|
||
|
numchanged = 0
|
||
|
if lastknownchange > targetpos:
|
||
|
numchanged = lastknownchange - targetpos
|
||
|
while targetpos + numchanged < targetlen and not (
|
||
|
sourcepos + numchanged < sourcelen and
|
||
|
source[sourcepos+numchanged] == target[targetpos+numchanged] and
|
||
|
source[sourcepos+numchanged+1] == target[targetpos+numchanged+1] and
|
||
|
source[sourcepos+numchanged+2] == target[targetpos+numchanged+2]):
|
||
|
numchanged += 1
|
||
|
if sourcepos+numchanged >= sourcelen:
|
||
|
numchanged = targetlen - targetpos
|
||
|
lastknownchange = targetpos + numchanged
|
||
|
if numchanged:
|
||
|
rle1start = max(targetpos, 1)
|
||
|
while rle1start+3 < targetpos+numchanged:
|
||
|
if (target[rle1start-1] == target[rle1start+0] and
|
||
|
target[rle1start+0] == target[rle1start+1] and
|
||
|
target[rle1start+1] == target[rle1start+2] and
|
||
|
target[rle1start+2] == target[rle1start+3]):
|
||
|
numchanged = rle1start - targetpos
|
||
|
break
|
||
|
|
||
|
if rle1start >= 2 and rle1start+4 < targetpos+numchanged:
|
||
|
if (target[rle1start-2] == target[rle1start+0] and
|
||
|
target[rle1start-1] == target[rle1start+1] and
|
||
|
target[rle1start+0] == target[rle1start+2] and
|
||
|
target[rle1start+1] == target[rle1start+3] and
|
||
|
target[rle1start+2] == target[rle1start+4]):
|
||
|
numchanged = rle1start - targetpos
|
||
|
break
|
||
|
rle1start += 1
|
||
|
assert numchanged >= 0, numchanged
|
||
|
if numchanged:
|
||
|
yield ops.TargetRead(target[targetpos:targetpos+numchanged])
|
||
|
sourcepos += numchanged
|
||
|
targetpos += numchanged
|
||
|
if (targetpos >= 2 and targetpos+2 < targetlen and
|
||
|
target[targetpos-2] == target[targetpos+0] and
|
||
|
target[targetpos-1] == target[targetpos+1] and
|
||
|
target[targetpos-0] == target[targetpos+2]):
|
||
|
# Two-byte RLE
|
||
|
rlelen = 0
|
||
|
while (targetpos + rlelen < targetlen and
|
||
|
target[targetpos+0] == target[targetpos+rlelen+0] and
|
||
|
target[targetpos+1] == target[targetpos+rlelen+1]):
|
||
|
rlelen += 2
|
||
|
yield ops.TargetCopy(rlelen, targetpos - 2)
|
||
|
sourcepos += rlelen
|
||
|
targetpos += rlelen
|
||
|
elif (targetpos >= 1 and targetpos+1 < targetlen and
|
||
|
target[targetpos-1] == target[targetpos+0] and
|
||
|
target[targetpos-0] == target[targetpos+1]):
|
||
|
# One-byte RLE
|
||
|
rlelen = 0
|
||
|
while (targetpos + rlelen < targetlen and
|
||
|
target[targetpos] == target[targetpos+rlelen]):
|
||
|
rlelen += 1
|
||
|
yield ops.TargetCopy(rlelen, targetpos - 1)
|
||
|
sourcepos += rlelen
|
||
|
targetpos += rlelen
|
||
|
|
||
|
yield ops.SourceCRC32(zlib.crc32(source) & 0xFFFFFFFF)
|
||
|
yield ops.TargetCRC32(zlib.crc32(target) & 0xFFFFFFFF)
|
||
|
|
||
|
def main():
|
||
|
import bps.io
|
||
|
import bps.apply
|
||
|
import bps.validate
|
||
|
import sys
|
||
|
with open(sys.argv[1], "rb") as f:
|
||
|
source = f.read()
|
||
|
with open(sys.argv[2], "rb") as f:
|
||
|
target = f.read()
|
||
|
|
||
|
l = list(
|
||
|
bps.validate.check_stream(
|
||
|
make_bps(source, target)))
|
||
|
|
||
|
target2 = bytearray(len(target))
|
||
|
bps.apply.apply_to_bytearrays(l, source, target2)
|
||
|
assert target == bytes(target2), "invalid patch created"
|
||
|
|
||
|
bps.io.write_bps(l, sys.stdout.buffer)
|
||
|
|
||
|
main()
|