diff options
author | Simon Tatham <anakin@pobox.com> | 2017-05-16 19:06:05 +0100 |
---|---|---|
committer | Simon Tatham <anakin@pobox.com> | 2017-05-16 19:06:05 +0100 |
commit | f0cdcb370ec7023428bc421e3a8b5f5cbf1de502 (patch) | |
tree | 8e488b5a958d9c37f23986d8fbda19781b142db5 | |
parent | decda92874250a1e25d185fa8fd1f2d2f4b94d20 (diff) | |
download | wix-on-linux-f0cdcb370ec7023428bc421e3a8b5f5cbf1de502.tar.gz wix-on-linux-f0cdcb370ec7023428bc421e3a8b5f5cbf1de502.tar.bz2 wix-on-linux-f0cdcb370ec7023428bc421e3a8b5f5cbf1de502.zip |
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.
-rw-r--r-- | fake-winterop.c | 17 | ||||
-rwxr-xr-x | makecab.py | 107 |
2 files changed, 112 insertions, 12 deletions
diff --git a/fake-winterop.c b/fake-winterop.c index eb891c4..cc69179 100644 --- a/fake-winterop.c +++ b/fake-winterop.c | |||
@@ -26,7 +26,6 @@ uint32_t ResetAcls(const char16_t **pwzFiles, uint32_t cFiles) | |||
26 | } | 26 | } |
27 | 27 | ||
28 | typedef struct CabCreateContext { | 28 | typedef struct CabCreateContext { |
29 | char *tempdir; | ||
30 | char *outdir; | 29 | char *outdir; |
31 | char *outfile; | 30 | char *outfile; |
32 | 31 | ||
@@ -43,14 +42,11 @@ uint32_t CreateCabBegin(const char16_t *wzCab, const char16_t *wzCabDir, | |||
43 | ctx->outdir = ascii(wzCabDir, true); | 42 | ctx->outdir = ascii(wzCabDir, true); |
44 | ctx->outfile = dupcat(ctx->outdir, "/", | 43 | ctx->outfile = dupcat(ctx->outdir, "/", |
45 | ascii(wzCab, true), (const char *)NULL); | 44 | ascii(wzCab, true), (const char *)NULL); |
46 | ctx->tempdir = dupcat(ctx->outdir, "/cabXXXXXX", (const char *)NULL); | ||
47 | if (!mkdtemp(ctx->tempdir)) | ||
48 | err(1, "mkdtemp"); | ||
49 | ctx->nargs = 0; | 45 | ctx->nargs = 0; |
50 | ctx->argsize = 16; | 46 | ctx->argsize = 16; |
51 | ctx->args = snewn(ctx->argsize, char *); | 47 | ctx->args = snewn(ctx->argsize, char *); |
52 | ctx->args[ctx->nargs++] = dupcat("lcab", (const char *)NULL); | 48 | ctx->args[ctx->nargs++] = dupcat("makecab.py", (const char *)NULL); |
53 | ctx->args[ctx->nargs++] = dupcat("-n", (const char *)NULL); | 49 | ctx->args[ctx->nargs++] = ctx->outfile; |
54 | *out_ctx = ctx; | 50 | *out_ctx = ctx; |
55 | return 0; | 51 | return 0; |
56 | } | 52 | } |
@@ -61,16 +57,14 @@ uint32_t CreateCabAddFile(const char16_t *wzFile, const char16_t *wzToken, | |||
61 | char *file = ascii(wzFile, true); | 57 | char *file = ascii(wzFile, true); |
62 | char *file_abs = realpath(file, NULL); | 58 | char *file_abs = realpath(file, NULL); |
63 | char *cabname = ascii(wzToken, true); | 59 | char *cabname = ascii(wzToken, true); |
64 | char *cab_abs = dupcat(ctx->tempdir, "/", cabname, (const char *)NULL); | ||
65 | printf("CreateCabAddFile: %s :: %s <- %s\n", ctx->outfile, | 60 | printf("CreateCabAddFile: %s :: %s <- %s\n", ctx->outfile, |
66 | cabname, file_abs); | 61 | cabname, file_abs); |
67 | if (symlink(file_abs, cab_abs) < 0) | ||
68 | err(1, "symlink"); | ||
69 | if (ctx->nargs + 1 >= ctx->argsize) { | 62 | if (ctx->nargs + 1 >= ctx->argsize) { |
70 | ctx->argsize = ctx->nargs * 5 / 4 + 16; | 63 | ctx->argsize = ctx->nargs * 5 / 4 + 16; |
71 | ctx->args = sresize(ctx->args, ctx->argsize, char *); | 64 | ctx->args = sresize(ctx->args, ctx->argsize, char *); |
72 | } | 65 | } |
73 | ctx->args[ctx->nargs++] = cab_abs; | 66 | ctx->args[ctx->nargs++] = cabname; |
67 | ctx->args[ctx->nargs++] = file_abs; | ||
74 | return 0; | 68 | return 0; |
75 | } | 69 | } |
76 | 70 | ||
@@ -86,11 +80,10 @@ uint32_t CreateCabAddFiles(const char16_t *const *pwzFiles, | |||
86 | 80 | ||
87 | uint32_t CreateCabFinish(CabCreateContext *ctx, void (*split_callback)(void)) | 81 | uint32_t CreateCabFinish(CabCreateContext *ctx, void (*split_callback)(void)) |
88 | { | 82 | { |
89 | if (ctx->nargs + 2 >= ctx->argsize) { | 83 | if (ctx->nargs + 1 >= ctx->argsize) { |
90 | ctx->argsize = ctx->nargs * 5 / 4 + 16; | 84 | ctx->argsize = ctx->nargs * 5 / 4 + 16; |
91 | ctx->args = sresize(ctx->args, ctx->argsize, char *); | 85 | ctx->args = sresize(ctx->args, ctx->argsize, char *); |
92 | } | 86 | } |
93 | ctx->args[ctx->nargs++] = ctx->outfile; | ||
94 | ctx->args[ctx->nargs++] = NULL; | 87 | ctx->args[ctx->nargs++] = NULL; |
95 | system_argv_array(ctx->args); | 88 | system_argv_array(ctx->args); |
96 | return 0; | 89 | return 0; |
diff --git a/makecab.py b/makecab.py new file mode 100755 index 0000000..befa94a --- /dev/null +++ b/makecab.py | |||
@@ -0,0 +1,107 @@ | |||
1 | #!/usr/bin/env python | ||
2 | |||
3 | import sys | ||
4 | import os | ||
5 | import time | ||
6 | import zlib | ||
7 | import struct | ||
8 | from collections import namedtuple | ||
9 | |||
10 | CFHEADER_s = struct.Struct("<4sLLLLLBBHHHHH") | ||
11 | CFHEADER = namedtuple("CFHEADER", "sig res0 size res1 firstfile res2 " | ||
12 | "verminor vermajor folders files flags setid icabinet") | ||
13 | CFHEADER_sig = "MSCF" | ||
14 | |||
15 | CFFOLDER_s = struct.Struct("<LHH") | ||
16 | CFFOLDER = namedtuple("CFFOLDER", "firstdata ndata compresstype") | ||
17 | |||
18 | CFFILE_s = struct.Struct("<LLHHHH") | ||
19 | CFFILE = namedtuple("CFFILE", "size offset ifolder date time attrs") | ||
20 | |||
21 | CFDATA_s = struct.Struct("<LHH") | ||
22 | CFDATA = namedtuple("CFDATA", "checksum compressedlen uncompressedlen") | ||
23 | |||
24 | def mszip(data): | ||
25 | compressor = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS, | ||
26 | zlib.DEF_MEM_LEVEL, zlib.Z_DEFAULT_STRATEGY) | ||
27 | compressed = compressor.compress(data) | ||
28 | compressed += compressor.flush() | ||
29 | return "CK" + compressed # add MSZIP header | ||
30 | |||
31 | def packdate(y,m,d): | ||
32 | return ((y - 1980) << 9) | (m << 5) | d | ||
33 | def packtime(h,m,s): | ||
34 | return ((h << 11) | (m << 5) | (s >> 1)) | ||
35 | |||
36 | def checksum(data): | ||
37 | data += "\0" * (3 & -len(data)) # pad to multiple of 4 bytes | ||
38 | toret = 0 | ||
39 | for offset in xrange(0, len(data), 4): | ||
40 | toret ^= struct.unpack_from("<L", data, offset)[0] | ||
41 | return toret | ||
42 | |||
43 | def build_cab(files): | ||
44 | uncompressed_data = "" | ||
45 | fileheaders = "" | ||
46 | for name, data, mtime in files: | ||
47 | mtime_u = time.gmtime(mtime) | ||
48 | fileheader = CFFILE( | ||
49 | size=len(data), offset=len(uncompressed_data), ifolder=0, attrs=0, | ||
50 | date=packdate(mtime_u.tm_year, mtime_u.tm_mon, mtime_u.tm_mday), | ||
51 | time=packtime(mtime_u.tm_hour, mtime_u.tm_min, mtime_u.tm_sec)) | ||
52 | uncompressed_data += data | ||
53 | fileheaders += CFFILE_s.pack(*fileheader) + name + "\0" | ||
54 | |||
55 | compressed_data = "" | ||
56 | offset = 0 | ||
57 | n_data_blocks = 0 | ||
58 | while offset < len(uncompressed_data): | ||
59 | uncompressed_block = uncompressed_data[offset:offset+0x8000] | ||
60 | compressed_block = mszip(uncompressed_block) | ||
61 | blockheader = CFDATA( | ||
62 | checksum=0, | ||
63 | compressedlen=len(compressed_block), | ||
64 | uncompressedlen=len(uncompressed_block)) | ||
65 | header_after_checksum = CFDATA_s.pack(*blockheader)[4:] | ||
66 | blockheader = blockheader._replace( | ||
67 | checksum=checksum(header_after_checksum + compressed_block)) | ||
68 | compressed_data += CFDATA_s.pack(*blockheader) + compressed_block | ||
69 | offset += len(uncompressed_block) | ||
70 | n_data_blocks += 1 | ||
71 | |||
72 | totalsize = (CFHEADER_s.size + | ||
73 | CFFOLDER_s.size + | ||
74 | len(fileheaders) + | ||
75 | len(compressed_data)) | ||
76 | |||
77 | header = CFHEADER( | ||
78 | sig=CFHEADER_sig, res0=0, res1=0, res2=0, | ||
79 | vermajor=1, verminor=3, folders=1, files=len(files), | ||
80 | flags=0, setid=0, icabinet=0, size=totalsize, | ||
81 | firstfile=CFHEADER_s.size + CFFOLDER_s.size) | ||
82 | |||
83 | folder = CFFOLDER( | ||
84 | ndata=n_data_blocks, compresstype=1, | ||
85 | firstdata = (CFHEADER_s.size + CFFOLDER_s.size + len(fileheaders))) | ||
86 | |||
87 | return (CFHEADER_s.pack(*header) + | ||
88 | CFFOLDER_s.pack(*folder) + | ||
89 | fileheaders + | ||
90 | compressed_data) | ||
91 | |||
92 | def main(): | ||
93 | args = sys.argv[1:] | ||
94 | outfile = args.pop(0) | ||
95 | files = [] | ||
96 | while len(args) > 0: | ||
97 | cabname = args.pop(0) | ||
98 | filename = args.pop(0) | ||
99 | with open(filename, "rb") as f: | ||
100 | filedata = f.read() | ||
101 | files.append((cabname, filedata, os.stat(filename).st_mtime)) | ||
102 | cabdata = build_cab(files) | ||
103 | with open(outfile, "wb") as f: | ||
104 | f.write(cabdata) | ||
105 | |||
106 | if __name__ == '__main__': | ||
107 | main() | ||