aboutsummaryrefslogtreecommitdiff
path: root/makecab.py
blob: e9ad9d02fc8059fc970abbb7ce48eceb579dd178 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#!/usr/bin/env python3

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 = b"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 b"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_fullwords_len = len(data) & ~3
    data_last_word = data[data_fullwords_len:]
    data_last_word = bytes(reversed(data_last_word))
    data_last_word += b"\0" * (3 & -len(data)) # pad to multiple of 4 bytes
    toret = 0
    for part, partlen in ((data, data_fullwords_len),
                          (data_last_word, len(data_last_word))):
        for offset in range(0, partlen, 4):
            toret ^= struct.unpack_from("<L", part, offset)[0]
    return toret

def build_cab(files):
    uncompressed_data = b""
    fileheaders = b""
    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)
        fileheaders += name.encode("ASCII") + b"\0"

    compressed_data = b""
    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()