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()