#!/usr/bin/env python
# Patches either the statically or the dynamically linked D3 Loki executables
# to resolve multiplayer level checksum mismatches with the Windows version
# By Jeff250

PATCHES = [
    (
        [
            (0, '\x55\x89\xe5\x83\xec\x18\x8b\x45\x08\xd9\x00\xd9\x40\x04\xd9\xc9\xd8\xc8\xd9\xc9\xd8\xc8\xde\xc1\xd9\x40\x08\xd8\xc8\xde\xc1\xd9\xfa\xd9\x5d\xfc\xd9\x45\xfc\x89\xec\x5d\xc3'),
        ],
        [
            (33, '\x90\x90\x90\x90\x90\x90'),
        ]
    ),
    (
        [
            (0, '\x55\x89\xe5\x83\xec\x3c\x57\x56\x53\x8b\x75\x0c\xd9\xee\x31\xc9\x39\xf1\x7d\x76\x8b\x5d\x10\x8d\x41\x02\x99\xf7\xfe\x8b\x7d\x14\x0f\xbf\x04\x53\x8d\x04\x40\x8d\x04\x87\x50\x8b\x7d\x10\x8d\x59\x01\x89\xd8\x99\xf7\xfe\x0f\xbf\x04\x57\x8b\x55\x14\x8d\x04\x40\x8d\x04\x82\x50\x0f\xbf\x04\x4f\x8d\x04\x40\x8d\x04\x82\x50\x8d\x45\xf4\x50\xdb\x7d\xd0\xe8'),
            (4, '\x83\xc4\x10\xdb\x6d\xd0\xd8\xd1\xdf\xe0\x80\xe4\x05\x74\x18\xdd\xd8\x8b\x45\xf4\x8b\x55\x08\x89\x02\x8b\x45\xf8\x89\x42\x04\x8b\x45\xfc\x89\x42\x08\xeb\x02\xdd\xd9\x89\xd9\x39\xf1\x7c\x8a\xdc\x1d'),
            (4, '\xdf\xe0\x80\xe4\x45\x80\xfc\x01\x74\x06\xb0\x01\xeb\x04\x89\xf6\xb0\x00\x8d\x65\xb8\x5b\x5e\x5f\x89\xec\x5d\xc3'),
        ],
        [
            (83, '\xd9\x5d\xd0'),
            (8, '\xd9\x45\xd0'),
        ],
    ),
]

def patch(path):
    try:
        with open(path, 'rb') as f:
            buf = f.read()
    except IOError:
        print '[-] Cannot read %s' % path
        exit(1)
    print '[+] Read %s' % path
    patched = list(buf)
    for i, (before, after) in enumerate(PATCHES):
        first_skip, first_find = before[0]
        start = 0
        found = 0
        while True:
            start = buf.find(first_find, start + first_skip) - first_skip
            if start < 0:
                break
            offset = start + first_skip + len(first_find)
            for skip, find in before[1:]:
                offset += skip
                offset = buf.find(find, offset, offset + len(find))
                if offset < 0:
                    break
                offset += len(find)
            if offset >= 0:
                for skip, replace in after:
                    start += skip
                    patched[start:start + len(replace)] = replace
                    start += len(replace)
                print '[+] Offset #%d to patch found at %#x' % (i + 1, start)
                found += 1
            else:
                start += 1
        if found < 1:
            print '[-] Cannot find offset #%d to patch' % (i + 1)
            exit(1)
    backup_path = path + '.old'
    try:
        with open(backup_path, 'wb') as f:
            f.write(buf)
    except IOError:
        print '[-] Cannot save backup to %s' % backup_path
        exit(1)
    print '[+] Saved backup to %s' % backup_path
    try:
        with open(path, 'wb') as f:
            f.write(''.join(patched))
    except IOError:
        print '[-] Cannot write patches to %s' % path
        exit(1)
    print '[+] Wrote patch to %s' % path

if __name__ == '__main__':
    import sys
    if len(sys.argv) != 2:
        sys.stderr.write('Usage: %s EXECUTABLE\n' % sys.argv[0])
        exit(1)
    patch(sys.argv[1])
