Add Archive timestamp decoding
[installshield_z.git] / installshield_main.c
1 /*
2   InstallShield Z file format
3   Copyright 2015 <hacker@iam.tj>
4   Licenced on the terms of then GNU General Public Licence version 3
5
6   Read and extract InstallShield .Z archive files
7  */
8
9 #include <stdio.h>
10 #include <sys/types.h>
11 #include <sys/stat.h>
12 #include <inttypes.h>
13 #include <fcntl.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <errno.h>
17 #include <string.h>
18 #include <time.h>
19 #include <utime.h>
20
21 #include "implode.h"
22 #include "installshield_z.h"
23
24 static const size_t BUFFER_SIZE = 0x4000;
25 static bool debug = 0;
26 static bool extract = 0;
27
28 void
29 usage(const char *name)
30 {
31         printf("usage: %s %s\n\n", name,
32                 " FILENAME\n"
33                 " [-x|--extract]"
34         );
35 }
36
37 time_t msdos2unixtime(uint16_t time, uint16_t date)
38 {
39         if (debug) printf("Date: %04x Time %04x\n", date, time);
40         struct tm dt;
41         dt.tm_year              =((date & 0xFE00) >>  9) + 80;
42         dt.tm_mon               = (date & 0x01E0) >>  5;
43         dt.tm_mday              = (date & 0x001F) >>  0;
44         dt.tm_hour              = (time & 0xF800) >> 11;
45         dt.tm_min               = (time & 0x07E0) >>  5;
46         dt.tm_sec               = (time & 0x001F) >>  0;
47
48         return mktime(&dt);
49 }
50
51 int
52 headers_decode(int z_fd)
53 {
54         int result = -1;
55         struct archive_header *header = NULL;
56         off_t offset = 0;
57         ssize_t qty = 0;
58
59         if (z_fd != -1 && (header = calloc(BUFFER_SIZE, 1))) {
60                 offset = lseek(z_fd, offset, SEEK_SET);
61                 if ((qty = read(z_fd, header, Z_FILE_HEADER_BYTES))< Z_FILE_HEADER_BYTES)
62                         fprintf(stderr, "Warning: Tried to read %ld header bytes but only got %ld. Will do my best\n", Z_FILE_HEADER_BYTES, qty);
63                 time_t timestamp = msdos2unixtime(header->timestamp, header->datestamp);
64                 char *datetime = ctime(&timestamp);
65                 printf("Magic               %08x\n", header->magic);
66                 printf("Timestamp           %s"  , datetime);
67                 printf("File size           %08x\n", header->file_bytes);
68                 printf("Payload size        %08x\n", header->payload_bytes);
69                 printf("Directories offset  %08x\n", header->directories_offset);
70                 printf("Directories size    %08x\n", header->directories_bytes);
71                 printf("Directories entries     %04x\n", header->directories_entries);
72                 printf("Files offset        %08x\n", header->files_offset);
73                 printf("Files size          %08x\n", header->files_bytes);
74                 printf("Files entries           %04x\n", header->files_entries);
75
76                 offset = lseek(z_fd, header->directories_offset, SEEK_SET);
77                 struct dirent_prefix *dirent, *p;
78
79                 if (offset == header->directories_offset && ((dirent = p = calloc(header->directories_bytes, 1)))) {
80
81                         if ((qty = read(z_fd, dirent, header->directories_bytes)) != header->directories_bytes)
82                                 fprintf(stderr, "Warning: Tried to read %d directory bytes but only got %ld. Will do my best\n", header->directories_bytes, qty);
83
84                         printf("Directory entries: %d ( %08x bytes @ 0x%08x)\n", header->directories_entries, header->directories_bytes, header->directories_offset);
85
86                         struct dirent dirents[header->directories_entries];
87                         for (int i = 0; i < header->directories_entries; ++i) {
88                                 printf(" Entry %4d Size %x @ %08x    \"%s\"\n", i, p->entry_bytes, header->directories_offset + (uint32_t)((void *)p - (void *)dirent), p->name);
89                                 dirents[i].entry = p;
90                                 p = (void *)p + p->entry_bytes;
91                         }
92
93                         offset = lseek(z_fd, header->files_offset, SEEK_SET);
94                         struct fileent_prefix *fileent, *q;
95                         if (offset == header->files_offset && ((fileent = q = calloc(header->files_bytes, 1)))) {
96                                 if ((qty = read(z_fd, fileent, header->files_bytes)) != header->files_bytes)
97                                         fprintf(stderr, "Warning: Tried to read %d file entry bytes but only got %ld. Will do my best\n", header->files_bytes, qty);
98
99                                 printf("File entries: %d ( %08x bytes @ 0x%08x)\n", header->files_entries, header->files_bytes, header->files_offset);
100
101                                 for (int i = 0; i < header->files_entries; ++i) {
102                                         timestamp = msdos2unixtime(q->timestamp, q->datestamp);
103                                         datetime = ctime(&timestamp);
104                                         printf(" Entry %4d uses %06x bytes @ %08x. Date %s File-size %08x (payload %08x @ %08x)", i, q->entry_bytes, header->files_offset +(uint32_t)((void *)q - (void *)fileent), datetime, q->file_bytes, q->payload_bytes, q->payload_offset);
105
106                                         if (debug) {
107                                                 printf(" next @ %08x", q->payload_offset + q->payload_bytes);
108                                                 for (int r = 0; r < 0x1E; ++r)
109                                                 printf(" %02x", *(unsigned char *)(r + (void *)q));
110                                         }
111
112                                         printf(" \"%s\\%s\"\n", dirents[q->dir_index].entry->name, q->name);
113
114                                         char *dir_name = strdup(dirents[q->dir_index].entry->name);
115                                         for (char *p = dir_name; *p; ++p)
116                                                 if (*p == '\\') *p = '/';
117                                          if (debug) printf("Parsing '%s' (%ld bytes)\n", dir_name, strlen(dir_name));
118
119                                         int dir_fd = AT_FDCWD;
120                                         int dir_fd_last = dir_fd;
121                                         if (extract) {
122                                                 if (!dirents[q->dir_index].created) {
123                                                         char *component = dir_name;
124                                                         bool last_component = false;
125                                                         for (char *sep = dir_name;; ++sep) {
126                                                                 if (*sep == '/' || *sep == 0) {
127                                                                         if (*sep == 0)
128                                                                                 last_component = true;
129                                                                         char temp = *sep;
130                                                                         *sep = 0;
131                                                                         if (strlen(component) > 0) {
132                                                                                 if (debug) printf("Component: '%s' %ld bytes @ %p\n", component, strlen(component), component);
133                                                                                 int ret = mkdirat(dir_fd, component, 0755);
134                                                                                 if (ret == 0 || (ret == -1 && errno == EEXIST)) {
135                                                                                         if (debug)
136                                                                                                 printf("Directory '%s' %s\n", component, ret == 0 ? "created" : "already exists");
137                                                                                         dir_fd_last = dir_fd;
138                                                                                         dir_fd = openat(dir_fd_last, component, O_DIRECTORY);
139                                                                                         close(dir_fd_last);
140                                                                                         if (dir_fd == -1) {
141                                                                                                 fprintf(stderr, "Error %d: cannot open directory '%s': ", errno, component);
142                                                                                                 perror(NULL);
143                                                                                                 *sep = temp;
144                                                                                                 break; // cannot continue if unable to open a path component
145                                                                                         } else if (last_component == true) {
146                                                                                                 dirents[q->dir_index].created = true;
147                                                                                         }
148                                                                                 } else {
149                                                                                         fprintf(stderr, "Error %d: cannot create directory '%s': ", errno, component);
150                                                                                         perror(NULL);
151                                                                                         *sep = temp;
152                                                                                         break; // leave the for() loop immediately
153                                                                                 }
154                                                                         } // strlen()
155                                                                         *sep = temp;
156                                                                         component = sep + 1;
157                                                                         if (last_component) break;
158                                                                 } // separator
159                                                         } // for()
160                                                         if (dir_fd < 0)
161                                                                 dir_fd = AT_FDCWD;
162                                                 } // .created == false
163                                         }
164                                         if ((dir_fd = openat(AT_FDCWD, dir_name, O_DIRECTORY)) != -1) {
165
166                                                 unsigned int z_filename_len = strlen(q->name) + 4;
167                                                 char z_filename[z_filename_len];
168                                                 strncpy(z_filename, q->name, z_filename_len);
169                                                 strncat(z_filename, ".z", 3);
170
171                                                 char filepath[strlen(dir_name) + z_filename_len];
172                                                 strncpy(filepath, dir_name, strlen(dir_name) + 1);
173                                                 strncat(filepath, "/", 2);
174                                                 strncat(filepath, z_filename, z_filename_len);
175
176                                                 if (debug)
177                                                         printf(" Creating payload file '%s/%s'\n", dir_name,  z_filename);
178
179                                                 if ((offset = lseek(z_fd, q->payload_offset, SEEK_SET)) == q->payload_offset) {
180                                                         void *buffer;
181                                                         if ((buffer = calloc(q->payload_bytes, 1)) != 0) {
182                                                                 struct utimbuf filetime;
183                                                                 filetime.actime  = timestamp;
184                                                                 filetime.modtime = timestamp;
185                                                                 int pay_fd;
186                                                                 if ((pay_fd = openat(dir_fd, z_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644))) {
187                                                                         ssize_t z_qty, pay_qty;
188                                                                         if ((z_qty = read(z_fd, buffer, q->payload_bytes)) == q->payload_bytes) {
189                                                                                 if (debug) {
190                                                                                         unsigned int checksum = 0x0;
191                                                                                         for (int i = 0; i < q->payload_bytes / sizeof(unsigned int); ++i) {
192                                                                                                 checksum ^= ((unsigned int *)buffer)[i];
193                                                                                         }
194                                                                                         printf("Checksum: %08x\n", checksum);
195                                                                                 }
196                                                                                 if ((pay_qty = write(pay_fd, buffer, q->payload_bytes)) != q->payload_bytes)
197                                                                                         fprintf(stderr, "Error: whilst writing payload expected %d bytes but wrote %ld\n", q->payload_bytes, pay_qty);
198                                                                         } else
199                                                                                 fprintf(stderr, "Error: whilst reading payload expected %d bytes but read %ld\n", q->payload_bytes, z_qty);
200                                                                         close(pay_fd);
201                                                                         if(utime(filepath, &filetime) == -1) {
202                                                                                 fprintf(stderr, "Error: unable to set timestamp on '%s' (%d) ", filepath, errno);
203                                                                                 perror(NULL);
204                                                                         }
205                                                                 }
206                                                                 if (q->payload_bytes >= 4) {
207                                                                         uint8_t comp_type = *(uint8_t *)buffer;
208                                                                         uint8_t dict_size = *(uint8_t *)(buffer + 1);
209                                                                         if (comp_type >= IMPLODE_BINARY && comp_type <= IMPLODE_ASCII && dict_size >= IMPLODE_DICT_1K && dict_size <= IMPLODE_DICT_4K) {
210                                                                                 void * buffer_decomp;
211                                                                                 if ((buffer_decomp = calloc(q->file_bytes, 1)) != 0) {
212                                                                                         uint32_t decomp_bytes = q->file_bytes;
213                                                                                         if (explode(buffer, q->payload_bytes, buffer_decomp, &decomp_bytes)) {
214                                                                                                 if (decomp_bytes > q->file_bytes) {
215                                                                                                         fprintf(stderr, "Expected %d uncompressed bytes but got %d\n", q->file_bytes, decomp_bytes);
216                                                                                                 }
217                                                                                                 int decomp_fd;
218                                                                                                 if ((decomp_fd = openat(dir_fd, q->name, O_WRONLY | O_CREAT | O_TRUNC, 0644))) {
219                                                                                                         ssize_t decomp_qty;
220                                                                                                         if (debug)
221                                                                                                                 printf("Creating uncompressed file '%s/%s'\n",dir_name,  q->name);
222                                                                                                         if ((decomp_qty = write(decomp_fd, buffer_decomp, q->file_bytes)) != q->file_bytes)
223                                                                                                                 fprintf(stderr, "Error: whilst writing decompressed data expected %d bytes but wrote %ld\n", q->file_bytes, decomp_qty);
224                                                                                                         close(decomp_fd);
225                                                                                                         filepath[strlen(filepath) - 2] = 0;
226                                                                                                         if(utime(filepath, &filetime) == -1) {
227                                                                                                                 fprintf(stderr, "Error: unable to set timestamp on '%s' (%d) ", filepath, errno);
228                                                                                                                 perror(NULL);
229                                                                                                         }
230                                                                                                 }
231                                                                                         } else {
232                                                                                                 fprintf(stderr, "Error: failed to decompress '%s', payload: %d decompressed: %d\n", q->name, q->payload_bytes, decomp_bytes);
233                                                                                         }
234                                                                                         free(buffer_decomp);
235                                                                                 } else {
236                                                                                         fprintf(stderr, "Error: unable to allocate %d bytes for decompression buffer\n", q->file_bytes);
237                                                                                 }
238                                                                         } else {
239                                                                                 fprintf(stderr, "Does not look like TTComp compressed data. header: %04x\n", *(uint16_t *)buffer);
240                                                                         }
241                                                                 } else {
242                                                                         fprintf(stderr, "Error: payload %d bytes smaller than minimum allowed TTComp of 4\n", q->payload_bytes);
243                                                                 }
244                                                                 free(buffer);
245                                                         }
246                                                 } else {
247                                                         fprintf(stderr, "Error %d: failed to create %s\n", errno, z_filename);
248                                                 }
249                                                 close(dir_fd);
250                                         } else {
251                                                 fprintf(stderr, "Error %d: cannot open directory at (%d) '%s': ", errno, dir_fd, dir_name);
252                                                 perror(NULL);
253                                         }
254                                         free(dir_name);
255                                         q = (void *)q + q->entry_bytes;
256                                 }
257                                 free(fileent);
258                         }
259                         free(dirent);
260                 } else 
261                         fprintf(stderr, "Error: %s\n", "failed to seek or couldn't allocate memory for directories");
262  
263                 free(header);
264                 result = 0;
265         }
266         return result;
267 }
268
269 int
270 main(int argc, char **argv, char **env)
271 {
272         int result = -1;
273         int fd = 0;
274
275         if (argc < 2 ) {
276                 usage(argv[0]);
277                 return 0;
278         }
279
280         for (char **e = env; *e; ++e) {
281                 if (strncmp("DEBUG", *e, 5) == 0)
282                         debug = true;
283         }
284         for (char **a = argv; *a; ++a) {
285                 if (debug) printf("Arg: %s\n", *a);
286                 if (strncmp("-x", *a, 2) == 0 || strncmp("--extract", *a, 9) == 0)
287                         extract = true;
288         }
289
290         if ((fd = open(argv[1], O_RDONLY)) != -1) {
291                 result = headers_decode(fd);
292                 close(fd);
293         } else {
294                 fprintf(stderr, "Error: could not open %s\n", argv[0]);
295                 return errno;
296         }
297
298
299         return result;
300 }
301