OpenMFOR/extra/make_bps.py

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