wix-on-linux/makecab.py
Simon Tatham f0cdcb370e My own CAB-maker, which compresses.
lcab produces uncompressed CAB files, which is a bit low-quality for
my taste - especially when it turns out CAB files have Deflate as one
of their compression options, so I can write a compressed CAB-builder
in only about 100 lines of Python using the zlib module! I'd expected
to have to faff about finding an implementation of LZX, but there's
really no need to bother.
2017-05-16 19:06:05 +01:00

107 lines
3.5 KiB
Python
Executable file

#!/usr/bin/env python
import sys
import os
import time
import zlib
import struct
from collections import namedtuple
CFHEADER_s = struct.Struct("<4sLLLLLBBHHHHH")
CFHEADER = namedtuple("CFHEADER", "sig res0 size res1 firstfile res2 "
"verminor vermajor folders files flags setid icabinet")
CFHEADER_sig = "MSCF"
CFFOLDER_s = struct.Struct("<LHH")
CFFOLDER = namedtuple("CFFOLDER", "firstdata ndata compresstype")
CFFILE_s = struct.Struct("<LLHHHH")
CFFILE = namedtuple("CFFILE", "size offset ifolder date time attrs")
CFDATA_s = struct.Struct("<LHH")
CFDATA = namedtuple("CFDATA", "checksum compressedlen uncompressedlen")
def mszip(data):
compressor = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS,
zlib.DEF_MEM_LEVEL, zlib.Z_DEFAULT_STRATEGY)
compressed = compressor.compress(data)
compressed += compressor.flush()
return "CK" + compressed # add MSZIP header
def packdate(y,m,d):
return ((y - 1980) << 9) | (m << 5) | d
def packtime(h,m,s):
return ((h << 11) | (m << 5) | (s >> 1))
def checksum(data):
data += "\0" * (3 & -len(data)) # pad to multiple of 4 bytes
toret = 0
for offset in xrange(0, len(data), 4):
toret ^= struct.unpack_from("<L", data, offset)[0]
return toret
def build_cab(files):
uncompressed_data = ""
fileheaders = ""
for name, data, mtime in files:
mtime_u = time.gmtime(mtime)
fileheader = CFFILE(
size=len(data), offset=len(uncompressed_data), ifolder=0, attrs=0,
date=packdate(mtime_u.tm_year, mtime_u.tm_mon, mtime_u.tm_mday),
time=packtime(mtime_u.tm_hour, mtime_u.tm_min, mtime_u.tm_sec))
uncompressed_data += data
fileheaders += CFFILE_s.pack(*fileheader) + name + "\0"
compressed_data = ""
offset = 0
n_data_blocks = 0
while offset < len(uncompressed_data):
uncompressed_block = uncompressed_data[offset:offset+0x8000]
compressed_block = mszip(uncompressed_block)
blockheader = CFDATA(
checksum=0,
compressedlen=len(compressed_block),
uncompressedlen=len(uncompressed_block))
header_after_checksum = CFDATA_s.pack(*blockheader)[4:]
blockheader = blockheader._replace(
checksum=checksum(header_after_checksum + compressed_block))
compressed_data += CFDATA_s.pack(*blockheader) + compressed_block
offset += len(uncompressed_block)
n_data_blocks += 1
totalsize = (CFHEADER_s.size +
CFFOLDER_s.size +
len(fileheaders) +
len(compressed_data))
header = CFHEADER(
sig=CFHEADER_sig, res0=0, res1=0, res2=0,
vermajor=1, verminor=3, folders=1, files=len(files),
flags=0, setid=0, icabinet=0, size=totalsize,
firstfile=CFHEADER_s.size + CFFOLDER_s.size)
folder = CFFOLDER(
ndata=n_data_blocks, compresstype=1,
firstdata = (CFHEADER_s.size + CFFOLDER_s.size + len(fileheaders)))
return (CFHEADER_s.pack(*header) +
CFFOLDER_s.pack(*folder) +
fileheaders +
compressed_data)
def main():
args = sys.argv[1:]
outfile = args.pop(0)
files = []
while len(args) > 0:
cabname = args.pop(0)
filename = args.pop(0)
with open(filename, "rb") as f:
filedata = f.read()
files.append((cabname, filedata, os.stat(filename).st_mtime))
cabdata = build_cab(files)
with open(outfile, "wb") as f:
f.write(cabdata)
if __name__ == '__main__':
main()