From: TJ Date: Fri, 13 Nov 2015 05:34:31 +0000 (+0000) Subject: InstallShield Z Archive extractor, v0.5 X-Git-Url: https://iam.tj/gitweb/gitweb.cgi?p=installshield_z.git;a=commitdiff_plain;h=6ba19d62b0b5923e96351f6b86f7ae2e76b74ccb InstallShield Z Archive extractor, v0.5 --- 6ba19d62b0b5923e96351f6b86f7ae2e76b74ccb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..73f8eaa --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +COMMON +PDDOC +PDMAIN + +*.swp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4946167 --- /dev/null +++ b/Makefile @@ -0,0 +1,22 @@ +EXE=isz +DATA=DATA.Z +SRC=installshield_main.c +CFLAGS=$(CLFAGS) -g + +build: + gcc $(CFLAGS) -o $(EXE) $(SRC) + +run: build + ./$(EXE) $(DATA) -x + +debug: build + DEBUG=1 ./$(EXE) $(DATA) -x + +strace: build + DEBU=1 strace ./$(EXE) $(DATA) -x + +clean: + rm -f $(EXE) *.o + rm -rf COMMON PDMAIN PDDOC + +PHONY: clean diff --git a/installshield_main.c b/installshield_main.c new file mode 100644 index 0000000..40bb743 --- /dev/null +++ b/installshield_main.c @@ -0,0 +1,244 @@ +/* + InstallShield Z file format + Copyright 2015 + Licenced on the terms of then GNU General Public Licence version 3 + + Read and extract InstallShield .Z archive files + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "installshield_z.h" + +static const size_t BUFFER_SIZE = 0x4000; +static bool debug = 0; +static bool extract = 0; + +void +usage(const char *name) +{ + printf("usage: %s %s\n\n", name, + " FILENAME\n" + " [-x|--extract]" + ); +} + +int +headers_decode(int z_fd) +{ + int result = -1; + struct archive_header *header = NULL; + off_t offset = 0; + ssize_t qty = 0; + + if (z_fd != -1 && (header = calloc(BUFFER_SIZE, 1))) { + offset = lseek(z_fd, offset, SEEK_SET); + if ((qty = read(z_fd, header, Z_FILE_HEADER_BYTES))< Z_FILE_HEADER_BYTES) + fprintf(stderr, "Warning: Tried to read %ld header bytes but only got %ld. Will do my best\n", Z_FILE_HEADER_BYTES, qty); + printf("Magic %08x\n", header->magic); + printf("CRC32 %08x\n", header->__crc32); + printf("File size %08x\n", header->file_bytes); + printf("Payload size %08x\n", header->payload_bytes); + printf("Directories offset %08x\n", header->directories_offset); + printf("Directories size %08x\n", header->directories_bytes); + printf("Directories entries %04x\n", header->directories_entries); + printf("Files offset %08x\n", header->files_offset); + printf("Files size %08x\n", header->files_bytes); + printf("Files entries %04x\n", header->files_entries); + + offset = lseek(z_fd, header->directories_offset, SEEK_SET); + struct dirent_prefix *dirent, *p; + + if (offset == header->directories_offset && ((dirent = p = calloc(header->directories_bytes, 1)))) { + + if ((qty = read(z_fd, dirent, header->directories_bytes)) != header->directories_bytes) + fprintf(stderr, "Warning: Tried to read %d directory bytes but only got %ld. Will do my best\n", header->directories_bytes, qty); + + printf("Directory entries: %d ( %08x bytes @ 0x%08x)\n", header->directories_entries, header->directories_bytes, header->directories_offset); + + struct ll_dirents dirents[header->directories_entries]; + for (int i = 0; i < header->directories_entries; ++i) { + printf(" Entry %4d Size %x @ %08x \"%s\"\n", i, p->entry_bytes, header->directories_offset + (uint32_t)((void *)p - (void *)dirent), p->name); + dirents[i].entry = p; + p = (void *)p + p->entry_bytes; + } + + offset = lseek(z_fd, header->files_offset, SEEK_SET); + struct fileent_prefix *fileent, *q; + if (offset == header->files_offset && ((fileent = q = calloc(header->files_bytes, 1)))) { + if ((qty = read(z_fd, fileent, header->files_bytes)) != header->files_bytes) + fprintf(stderr, "Warning: Tried to read %d file entry bytes but only got %ld. Will do my best\n", header->files_bytes, qty); + + printf("File entries: %d ( %08x bytes @ 0x%08x)\n", header->files_entries, header->files_bytes, header->files_offset); + + for (int i = 0; i < header->files_entries; ++i) { + 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); + + if (debug) { + printf(" next @ %08x", q->payload_offset + q->payload_bytes); + for (int r = 0; r < 0x1E; ++r) + printf(" %02x", *(unsigned char *)(r + (void *)q)); + } + + printf(" \"%s\\%s\"\n", dirents[q->dir_index].entry->name, q->name); + + int dir_fd = AT_FDCWD; + int dir_fd_last = dir_fd; + if (extract) { + char *dir = dirents[q->dir_index].entry->name; + + for (char *sep = dir;; ++sep) { + if (*sep == '\\' || *sep == 0) { + char temp = *sep; + *sep = 0; + + if (strlen(dir) > 0) { + int ret; + + if (debug) + printf("Creating directory '%s': ", dir); + + if (!dirents[q->dir_index].created && mkdirat(dir_fd, dir, 0755) != 0) { + if (errno != EEXIST) { + *sep = temp; + fprintf(stderr, "Error %d: cannot create directory %s\n", errno, dir); + perror(NULL); + goto dir_create_abort; + } + if (debug) + printf("Directory already exists: %s\n", dir); + } + + dir_fd_last = dir_fd; + if (debug) + printf("Opening directory '%s'\n", dir); + if ((dir_fd = openat(dir_fd_last, dir, O_DIRECTORY)) == -1) { + *sep = temp; + fprintf(stderr, "Error %d: cannot open directory %s\n", errno, dir); + perror(NULL); + goto dir_create_abort; + } + + if (debug) + printf("%s\n", ""); + + if (dir_fd_last != AT_FDCWD) close(dir_fd_last); +dir_exists: + *sep = temp; + dir = sep + 1; + } + } + if (*sep == 0) break; + } + close(dir_fd); + dir_fd = AT_FDCWD; + dirents[q->dir_index].created = true; + goto dir_create_done; + +dir_create_abort: + if (debug) + printf("%s", "\n"); + if (dir_fd_last != AT_FDCWD) { + close(dir_fd_last); + dir_fd_last = AT_FDCWD; + } + if (dir_fd != AT_FDCWD) { + close(dir_fd); + dir_fd = AT_FDCWD; + } + + } +dir_create_done: + if (NULL); + char *dir_name = strdup(dirents[q->dir_index].entry->name); + for (char *p = dir_name; *p; ++p) + if (*p == '\\') *p = '/'; + + if (dir_fd == AT_FDCWD && (dir_fd = openat(AT_FDCWD, dir_name, O_DIRECTORY)) != -1) { + + unsigned int z_filename_len = strlen(q->name) + 4; + char z_filename[z_filename_len]; + strncpy(z_filename, q->name, z_filename_len); + strncat(z_filename, ".z", 2); + + if (debug) + printf(" Creating payload file \"%s/%s\"\n", dir_name, z_filename); + + if ((offset = lseek(z_fd, q->payload_offset, SEEK_SET)) == q->payload_offset) { + void *buffer; + if ((buffer = calloc(q->payload_bytes, 1)) != 0) { + int pay_fd; + if ((pay_fd = openat(dir_fd, z_filename, O_WRONLY | O_CREAT | O_TRUNC, 0644))) { + ssize_t z_qty, pay_qty; + if ((z_qty = read(z_fd, buffer, q->payload_bytes)) == q->payload_bytes) { + if ((pay_qty = write(pay_fd, buffer, q->payload_bytes)) != q->payload_bytes) + fprintf(stderr, "Error: whilst writing payload expected %d bytes but wrote %ld\n", q->payload_bytes, pay_qty); + } else + fprintf(stderr, "Error: whilst reading payload expected %d bytes but read %ld\n", q->payload_bytes, z_qty); + close(pay_fd); + } + free(buffer); + } + } else + fprintf(stderr, "Error %d: failed to create %s\n", errno, z_filename); + close(dir_fd); + } else { + fprintf(stderr, "Error %d: cannot open directory at %d %s\n", errno, dir_fd, dir_name); + perror(NULL); + } + + q = (void *)q + q->entry_bytes; + } + free(fileent); + } + free(dirent); + } else + fprintf(stderr, "Error: %s\n", "failed to seek or couldn't allocate memory for directories"); + + free(header); + result = 0; + } + return result; +} + +int +main(int argc, char **argv, char **env) +{ + int result = -1; + int fd = 0; + + if (argc < 2 ) { + usage(argv[0]); + return 0; + } + + for (char **e = env; *e; ++e) { + if (strncmp("DEBUG", *e, 5) == 0) + debug = true; + } + for (char **a = argv; *a; ++a) { + if (debug) printf("Arg: %s\n", *a); + if (strncmp("-x", *a, 2) == 0 || strncmp("--extract", *a, 9) == 0) + extract = true; + } + + if ((fd = open(argv[1], O_RDONLY)) != -1) { + result = headers_decode(fd); + close(fd); + } else { + fprintf(stderr, "Error: could not open %s\n", argv[0]); + return errno; + } + + + return result; +} + diff --git a/installshield_z.h b/installshield_z.h new file mode 100644 index 0000000..c3b8375 --- /dev/null +++ b/installshield_z.h @@ -0,0 +1,80 @@ +/* + InstallShield Z file format + Copyright 2015 + Licenced on the terms of then GNU General Public Licence version 3 + + Data structures used in InstallShield .Z archive files + */ + +#include +#include +#include + +const unsigned int file_magic = 0x8C655D12; +const ssize_t Z_FILE_HEADER_BYTES = 0x100; + +/* __prefixed field purpose is not confirmed */ +struct __attribute__((packed)) archive_header { + uint32_t magic; // 0x00 + uint16_t __version; // 0x04 + uint16_t __flags; // 0x06 + uint32_t __unknown08; // 0x08 + uint16_t files_entries; // 0x0C + uint32_t __crc32; // 0x0E + uint32_t file_bytes; // 0x12 + uint32_t payload_bytes; // 0x16 + uint8_t __known1A; // 0x1A + uint16_t __unknown1B; // 0x1B + uint32_t __unknown1D; // 0x1D + uint32_t __unknown21; // 0x21 + uint32_t __unknown25; // 0x25 + uint32_t directories_offset; // 0x29 + uint32_t directories_bytes; // 0x2D + uint16_t directories_entries; // 0x31 + uint32_t files_offset; // 0x33 + uint32_t files_bytes; // 0x37 + unsigned char unknown3B[0x100 - 0x3B]; // 0x3B to 0xFF +}; + +struct __attribute__((packed)) dirent_prefix { + uint16_t __flags; // 0x00 + uint16_t entry_bytes; // 0x02 + uint16_t name_bytes; // 0x04 + unsigned char name[]; // 0x06 +}; + +struct __attribute__((packed)) dirent_suffix { + unsigned char __eos; // end of string NUL + uint32_t __unknown00; // 0x01 after end of directory_entry_prefix +}; + +struct __attribute__((packed)) fileent_prefix { + uint8_t __unknown00; + uint16_t dir_index; + uint32_t __unknown03; + uint32_t payload_bytes; + uint32_t payload_offset; + uint32_t __crc32; + uint32_t __unknown13; + uint16_t entry_bytes; + uint32_t __unknown01; + uint8_t name_bytes; + unsigned char name[]; +}; + +struct __attribute__((packed)) fileent_suffix { + uint32_t __unkown00; + uint32_t __unkown04; + uint32_t __unkown08; +}; + +struct ll_dirents { + struct dirent_prefix *entry; + struct ll_dirents *next; + bool created; +}; + +struct ll_fileents { + struct fileent_prefix *entry; + struct ll_fileents *next; +};