InstallShield Z Archive extractor, v0.5
[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
19 #include "installshield_z.h"
20
21 static const size_t BUFFER_SIZE = 0x4000;
22 static bool debug = 0;
23 static bool extract = 0;
24
25 void
26 usage(const char *name)
27 {
28         printf("usage: %s %s\n\n", name,
29                 " FILENAME\n"
30                 " [-x|--extract]"
31         );
32 }
33
34 int
35 headers_decode(int z_fd)
36 {
37         int result = -1;
38         struct archive_header *header = NULL;
39         off_t offset = 0;
40         ssize_t qty = 0;
41
42         if (z_fd != -1 && (header = calloc(BUFFER_SIZE, 1))) {
43                 offset = lseek(z_fd, offset, SEEK_SET);
44                 if ((qty = read(z_fd, header, Z_FILE_HEADER_BYTES))< Z_FILE_HEADER_BYTES)
45                         fprintf(stderr, "Warning: Tried to read %ld header bytes but only got %ld. Will do my best\n", Z_FILE_HEADER_BYTES, qty);
46                 printf("Magic               %08x\n", header->magic);
47                 printf("CRC32               %08x\n", header->__crc32);
48                 printf("File size           %08x\n", header->file_bytes);
49                 printf("Payload size        %08x\n", header->payload_bytes);
50         printf("Directories offset  %08x\n", header->directories_offset);
51                 printf("Directories size    %08x\n", header->directories_bytes);
52                 printf("Directories entries     %04x\n", header->directories_entries);
53                 printf("Files offset        %08x\n", header->files_offset);
54                 printf("Files size          %08x\n", header->files_bytes);
55                 printf("Files entries           %04x\n", header->files_entries);
56
57                 offset = lseek(z_fd, header->directories_offset, SEEK_SET);
58                 struct dirent_prefix *dirent, *p;
59
60                 if (offset == header->directories_offset && ((dirent = p = calloc(header->directories_bytes, 1)))) {
61
62                         if ((qty = read(z_fd, dirent, header->directories_bytes)) != header->directories_bytes)
63                                 fprintf(stderr, "Warning: Tried to read %d directory bytes but only got %ld. Will do my best\n", header->directories_bytes, qty);
64
65                         printf("Directory entries: %d ( %08x bytes @ 0x%08x)\n", header->directories_entries, header->directories_bytes, header->directories_offset);
66
67                         struct ll_dirents dirents[header->directories_entries];
68                         for (int i = 0; i < header->directories_entries; ++i) {
69                                 printf(" Entry %4d Size %x @ %08x    \"%s\"\n", i, p->entry_bytes, header->directories_offset + (uint32_t)((void *)p - (void *)dirent), p->name);
70                                 dirents[i].entry = p;
71                                 p = (void *)p + p->entry_bytes;
72                         }
73
74                         offset = lseek(z_fd, header->files_offset, SEEK_SET);
75                         struct fileent_prefix *fileent, *q;
76                         if (offset == header->files_offset && ((fileent = q = calloc(header->files_bytes, 1)))) {
77                                 if ((qty = read(z_fd, fileent, header->files_bytes)) != header->files_bytes)
78                                         fprintf(stderr, "Warning: Tried to read %d file entry bytes but only got %ld. Will do my best\n", header->files_bytes, qty);
79
80                                 printf("File entries: %d ( %08x bytes @ 0x%08x)\n", header->files_entries, header->files_bytes, header->files_offset);
81
82                                 for (int i = 0; i < header->files_entries; ++i) {
83                                         printf(" Entry %4d Size %06x @ %08x payload size %06x @ %08x", i, q->entry_bytes, header->files_offset +(uint32_t)((void *)q - (void *)fileent), q->payload_bytes, q->payload_offset);
84
85                                         if (debug) {
86                                                 printf(" next @ %08x", q->payload_offset + q->payload_bytes);
87                                                 for (int r = 0; r < 0x1E; ++r)
88                                                 printf(" %02x", *(unsigned char *)(r + (void *)q));
89                                         }
90
91                                         printf(" \"%s\\%s\"\n", dirents[q->dir_index].entry->name, q->name);
92
93                                         int dir_fd = AT_FDCWD;
94                                         int dir_fd_last = dir_fd;
95                                         if (extract) {
96                                                 char *dir = dirents[q->dir_index].entry->name;
97
98                                                 for (char *sep = dir;; ++sep) {
99                                                         if (*sep == '\\' || *sep == 0) {
100                                                                 char temp = *sep;
101                                                                 *sep = 0;
102
103                                                                 if (strlen(dir) > 0) {
104                                                                         int ret;
105
106                                                                         if (debug)
107                                                                                 printf("Creating directory '%s': ", dir);
108
109                                                                         if (!dirents[q->dir_index].created && mkdirat(dir_fd, dir, 0755) != 0) {
110                                                                                 if (errno != EEXIST) {
111                                                                                         *sep = temp;
112                                                                                         fprintf(stderr, "Error %d: cannot create directory %s\n", errno,  dir);
113                                                                                         perror(NULL);
114                                                                                         goto dir_create_abort;
115                                                                                 }
116                                                                                 if (debug)
117                                                                                         printf("Directory already exists: %s\n", dir);
118                                                                         }
119
120                                                                         dir_fd_last = dir_fd;
121                                                                         if (debug)
122                                                                                 printf("Opening directory '%s'\n", dir);
123                                                                         if ((dir_fd = openat(dir_fd_last, dir, O_DIRECTORY)) == -1) {
124                                                                                 *sep = temp;
125                                                                                 fprintf(stderr, "Error %d: cannot open directory %s\n", errno, dir);
126                                                                                 perror(NULL);
127                                                                                 goto dir_create_abort;
128                                                                         }
129
130                                                                         if (debug)
131                                                                                 printf("%s\n", "");
132
133                                                                         if (dir_fd_last != AT_FDCWD) close(dir_fd_last);
134 dir_exists:
135                                                                         *sep = temp;
136                                                                         dir = sep + 1;
137                                                                 }
138                                                         }
139                                                         if (*sep == 0) break;
140                                                 }
141                                                 close(dir_fd);
142                                                 dir_fd = AT_FDCWD;
143                                                 dirents[q->dir_index].created = true;
144                                                 goto dir_create_done;
145
146 dir_create_abort:
147                                                 if (debug)
148                                                         printf("%s", "\n");
149                                                 if (dir_fd_last != AT_FDCWD) {
150                                                         close(dir_fd_last);
151                                                         dir_fd_last = AT_FDCWD;
152                                                 }
153                                                 if (dir_fd != AT_FDCWD) {
154                                                         close(dir_fd);
155                                                         dir_fd = AT_FDCWD;
156                                                 }
157
158                                         }
159 dir_create_done:
160                                         if (NULL);
161                                         char *dir_name = strdup(dirents[q->dir_index].entry->name);
162                                         for (char *p = dir_name; *p; ++p)
163                                                 if (*p == '\\') *p = '/';
164
165                                         if (dir_fd == AT_FDCWD && (dir_fd = openat(AT_FDCWD, dir_name, O_DIRECTORY)) != -1) {
166
167                                                 unsigned int z_filename_len = strlen(q->name) + 4;
168                                                 char z_filename[z_filename_len];
169                                                 strncpy(z_filename, q->name, z_filename_len);
170                                                 strncat(z_filename, ".z", 2);
171
172                                                 if (debug)
173                                                         printf(" Creating payload file \"%s/%s\"\n", dir_name,  z_filename);
174
175                                                 if ((offset = lseek(z_fd, q->payload_offset, SEEK_SET)) == q->payload_offset) {
176                                                         void *buffer;
177                                                         if ((buffer = calloc(q->payload_bytes, 1)) != 0) {
178                                                                 int pay_fd;
179                                                                 if ((pay_fd = openat(dir_fd, z_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644))) {
180                                                                         ssize_t z_qty, pay_qty;
181                                                                         if ((z_qty = read(z_fd, buffer, q->payload_bytes)) == q->payload_bytes) {
182                                                                                 if ((pay_qty = write(pay_fd, buffer, q->payload_bytes)) != q->payload_bytes)
183                                                                                         fprintf(stderr, "Error: whilst writing payload expected %d bytes but wrote %ld\n", q->payload_bytes, pay_qty);
184                                                                         } else
185                                                                                 fprintf(stderr, "Error: whilst reading payload expected %d bytes but read %ld\n", q->payload_bytes, z_qty);
186                                                                         close(pay_fd);
187                                                                 }
188                                                                 free(buffer);
189                                                         }
190                                                 } else
191                                                         fprintf(stderr, "Error %d: failed to create %s\n", errno, z_filename);
192                                                 close(dir_fd);
193                                         } else {
194                                                 fprintf(stderr, "Error %d: cannot open directory at %d %s\n", errno, dir_fd, dir_name);
195                                                 perror(NULL);
196                                         }
197
198                                         q = (void *)q + q->entry_bytes;
199                                 }
200                                 free(fileent);
201                         }
202                         free(dirent);
203                 } else 
204                         fprintf(stderr, "Error: %s\n", "failed to seek or couldn't allocate memory for directories");
205  
206                 free(header);
207                 result = 0;
208         }
209         return result;
210 }
211
212 int
213 main(int argc, char **argv, char **env)
214 {
215         int result = -1;
216         int fd = 0;
217
218         if (argc < 2 ) {
219                 usage(argv[0]);
220                 return 0;
221         }
222
223         for (char **e = env; *e; ++e) {
224                 if (strncmp("DEBUG", *e, 5) == 0)
225                         debug = true;
226         }
227         for (char **a = argv; *a; ++a) {
228                 if (debug) printf("Arg: %s\n", *a);
229                 if (strncmp("-x", *a, 2) == 0 || strncmp("--extract", *a, 9) == 0)
230                         extract = true;
231         }
232
233         if ((fd = open(argv[1], O_RDONLY)) != -1) {
234                 result = headers_decode(fd);
235                 close(fd);
236         } else {
237                 fprintf(stderr, "Error: could not open %s\n", argv[0]);
238                 return errno;
239         }
240
241
242         return result;
243 }
244