#include "macunpack.h" #define LZH_INTERNAL #include #include #include "globals.h" #include "lzh.h" #include "crc.h" #include "../fileio/wrfile.h" #include "../fileio/machdr.h" #include "../util/masks.h" #include "../util/transname.h" #include "../util/util.h" #include "bits_be.h" #include "de_lzah.h" #define LZ5LOOKAHEAD 18 /* look ahead buffer size for LArc */ #define LZ5BUFFSIZE 8192 #define LZ5MASK 8191 #define LZSLOOKAHEAD 17 #define LZSBUFFSIZE 4096 #define LZSMASK 4095 #define LZBUFFSIZE 8192 /* Max of above buffsizes */ typedef struct methodinfo { char *name; int number; } methodinfo; static struct methodinfo methods[] = { {"-lh0-", lh0}, {"-lh1-", lh1}, {"-lh2-", lh2}, {"-lh3-", lh3}, {"-lh4-", lh4}, {"-lh5-", lh5}, {"-lz4-", lz4}, {"-lz5-", lz5}, {"-lzs-", lzs} }; static char *lzh_archive; static char *lzh_pointer; static char *lzh_data; static char *lzh_finfo; static int lzh_fsize; static int lzh_kind; static int oldsize; static char *lzh_file; static int lzh_filesize; static char *lzh_current; static char *tmp_out_ptr; static char lzh_lzbuf[LZBUFFSIZE]; static int lzh_filehdr(struct lzh_fileHdr *f); static int lzh_checkm(struct lzh_fileHdr *f); static char *lzh_methname(int n); static void lzh_wrfile(struct lzh_fileHdr *filehdr, int method); static void lzh_skip(struct lzh_fileHdr *filehdr); static void lzh_nocomp(uint32_t obytes); #ifdef UNTESTED static void lzh_lzss1(uint32_t obytes); static void lzh_lzss2(uint32_t obytes); #endif /* UNTESTED */ static void lzh_lzah(uint32_t obytes); static unsigned char lzh_getbyte(void); #ifdef UNDEF static void lzh_lh2(uint32_t obytes); static void lzh_lh3(uint32_t obytes); #endif /* UNDEF */ #ifdef UNTESTED static void lzh_lzh12(uint32_t obytes); #endif /* UNTESTED */ static void lzh_lzh13(uint32_t obytes); void lzh (int kind) { struct lzh_fileHdr filehdr; int m, i, j; char loc_name[64]; char dirinfo[INFOBYTES]; updcrc = arc_updcrc; crcinit = arc_crcinit; write_it = 1; lzh_fsize = 0; lzh_kind = kind; if(lzh_archive == NULL) { lzh_archive = malloc((unsigned)in_data_size); oldsize = in_data_size; } else if(in_data_size > oldsize) { lzh_archive = realloc(lzh_archive, (unsigned)in_data_size); oldsize = in_data_size; } if(lzh_archive == NULL) { (void)fprintf(stderr, "Insufficient memory for archive.\n"); exit(1); } if(fread(lzh_archive, 1, in_data_size, infp) != in_data_size) { (void)fprintf(stderr, "Can't read archive.\n"); #ifdef SCAN do_error("macunpack: Can't read archive"); #endif /* SCAN */ exit(1); } lzh_pointer = lzh_archive; while(1) { if(in_data_size == 0) { break; } if(lzh_filehdr(&filehdr) == 0) { break; } m = lzh_checkm(&filehdr); if(m < 0) { (void)fprintf(stderr, "Skipping file: \"%s\"; unknown method: %.5s.\n", text, filehdr.method); lzh_skip(&filehdr); continue; } if(!write_it) { /* We are skipping a folder. Skip the file if lzh_finfo is a prefix of or identical to the folder info in the file. */ if(lzh_fsize <= filehdr.extendsize && !strncmp(lzh_finfo, filehdr.extend, lzh_fsize)) { /* It was true, so we skip. */ lzh_skip(&filehdr); continue; } /* We have left the folder we were skipping. */ } /* Now we must leave folders until lzh_finfo is a proper prefix or identical to the folder info in the file. */ while(lzh_fsize > filehdr.extendsize || strncmp(lzh_finfo, filehdr.extend, lzh_fsize)) { /* Not a proper prefix, leave folder. First determine which! */ i = lzh_fsize - 1; while(--i >= 0 && lzh_finfo[i] != ':'); i = i + 1; transname(lzh_finfo + i, loc_name, lzh_fsize - i - 1); lzh_fsize = i; if(write_it) { indent--; if(!info_only) { enddir(); } if(list) { do_indent(indent); (void)fprintf(stderr, "leaving folder \"%s\"\n", loc_name); } } write_it = 1; } write_it = 1; /* lzh_finfo is a proper prefix or identical, just show so. */ lzh_finfo = filehdr.extend; /* Now enter directories while lzh_finfo is smaller than extend. */ while(lzh_fsize < filehdr.extendsize) { i = lzh_fsize; while(lzh_finfo[++i] != ':'); transname(lzh_finfo + lzh_fsize, loc_name, i - lzh_fsize); for(j = 0; j < INFOBYTES; j++) { dirinfo[j] = 0; } dirinfo[I_NAMEOFF] = i - lzh_fsize; copy(dirinfo + I_NAMEOFF + 1, lzh_finfo + lzh_fsize, i - lzh_fsize); lzh_fsize = i + 1; if(list) { do_indent(indent); (void)fprintf(stderr, "folder=\"%s\"", loc_name); if(query) { write_it = do_query(); } else { (void)fputc('\n', stderr); } if(write_it) { indent++; } } if(write_it && !info_only) { do_mkdir(loc_name, dirinfo); } if(!write_it) { break; } } if(!write_it) { lzh_skip(&filehdr); } else { lzh_wrfile(&filehdr, m); } } /* Leaving some more directories! */ while(lzh_fsize != 0) { i = lzh_fsize - 1; while(--i >= 0 && lzh_finfo[i] != ':'); i = i + 1; transname(lzh_finfo + i, loc_name, lzh_fsize - i - 1); lzh_fsize = i; if(write_it) { } if(write_it) { indent--; if(!info_only) { enddir(); } if(list) { do_indent(indent); (void)fprintf(stderr, "leaving folder \"%s\"\n", loc_name); } } } } static int lzh_filehdr (struct lzh_fileHdr *f) { register int i; char *hdr; int c; int ext_ptr; int chk_sum = 0; char *ptr; if(in_data_size <= 0) { return 0; } for(i = 0; i < INFOBYTES; i++) { info[i] = '\0'; } hdr = lzh_pointer; in_data_size -= 2; lzh_pointer += 2; if(in_data_size < 0) { in_data_size++; } f->hsize = (unsigned char)hdr[L_HSIZE]; if(f->hsize == 0) { return 0; } f->hcrc = (unsigned char)hdr[L_HCRC]; ptr = hdr + L_METHOD; in_data_size -= f->hsize; lzh_pointer += f->hsize; copy(&(f->method[0]), hdr + L_METHOD, 5); f->psize = get4i(hdr + L_PSIZE); f->upsize = get4i(hdr + L_UPSIZE); f->lastmod = get4i(hdr + L_LASTMOD); f->attribute = hdr[L_ATTRIBUTE + 1]; if(f->attribute < 2) { for(i = 0; i < f->hsize; i++) { chk_sum += *ptr++; } chk_sum &= BYTEMASK; if(chk_sum != f->hcrc) { (void)fprintf(stderr, "Header checksum error; got %.2x, must be %.2x.\n", chk_sum, f->hcrc); #ifdef SCAN do_error("macunpack: Header checksum error"); #endif /* SCAN */ exit(1); } f->nlength = (unsigned char)hdr[L_NLENGTH]; info[I_NAMEOFF] = f->nlength; copy(info + I_NAMEOFF + 1, hdr + L_NAME, (int)f->nlength); transname(hdr + L_NAME, text, (int)f->nlength); ext_ptr = L_NLENGTH + f->nlength + 1; f->crc = get2i(hdr + ext_ptr + L_CRC); if(f->attribute == 1) { f->etype = hdr[ext_ptr + L_ETYPE]; f->extendsize = hdr[ext_ptr + L_EXTENDSZ]; f->extend = hdr + ext_ptr + L_EXTEND; } else { f->extend = NULL; f->extendsize = 0; } } else if(f->attribute == 2) { in_data_size += 2; lzh_pointer -= 2; f->nlength = hdr[L_2EXTENDSZ] - 3; info[I_NAMEOFF] = f->nlength; copy(info + I_NAMEOFF + 1, hdr + L_2EXTEND + 2, (int)f->nlength); transname(hdr + L_2EXTEND + 2, text, (int)f->nlength); ext_ptr = f->crc = get2i(hdr + L_2CRC); f->etype = hdr[L_2ETYPE]; ext_ptr = L_2EXTEND + 2 + f->nlength; f->extendsize = hdr[ext_ptr + L_EEXTENDSZ]; f->extend = hdr + ext_ptr + L_EEXTEND; } else { (void)fprintf(stderr, "Unknown file header format (%d).\n", (int)f->attribute); #ifdef SCAN do_error("macunpack: Unknown file header format"); #endif /* SCAN */ exit(1); } if(f->extend != NULL) { if(f->extendsize > 5) { f->extend += 2; hdr = f->extend; f->extendsize -= 3; for(i = 0; i < f->extendsize; i++) { c = *hdr++; if((c & BYTEMASK) == BYTEMASK) { hdr[-1] = ':'; c = ':'; } } c = *hdr++; if(c == 5) { hdr += 5; } } else { if(f->extendsize == 5) { hdr = f->extend; f->extend = NULL; f->extendsize = 0; hdr += 5; } else { hdr = f->extend; f->extend = NULL; f->extendsize = 0; } } } else { hdr = hdr + ext_ptr; } lzh_data = hdr; if(f->attribute != 0) { lzh_data++; } return 1; } static int lzh_checkm (struct lzh_fileHdr *f) { int i, nummeth; char *meth; meth = f->method; nummeth = sizeof(methods) / sizeof(struct methodinfo); for(i = 0; i < nummeth; i++) { if(!strncmp(methods[i].name, meth, 5)) { return methods[i].number; } } return -1; } static char * lzh_methname (int n) { if(n > sizeof(methods) / sizeof(struct methodinfo)) { return NULL; } return methods[n].name; } static void lzh_wrfile (struct lzh_fileHdr *filehdr, int method) { char ftype[5], fauth[5]; int rsrcLength, dataLength; int doit; char *mname; uint32_t crc; if(filehdr->upsize > lzh_filesize) { if(lzh_filesize == 0) { lzh_file = malloc((unsigned)filehdr->upsize); } else { lzh_file = realloc(lzh_file, (unsigned)filehdr->upsize); } if(lzh_file == NULL) { (void)fprintf(stderr, "Insufficient memory to unpack file.\n"); exit(1); } } switch(method) { case lz4: lzh_nocomp((uint32_t)128); break; #ifdef UNTESTED case lz5: lzh_lzss1((uint32_t)128); break; case lzs: lzh_lzss2((uint32_t)128); break; #endif /* UNTESTED */ case lh0: lzh_nocomp((uint32_t)128); break; case lh1: lzh_lzah((uint32_t)128); break; #ifdef UNDEF case lh2: lzh_lh2((uint32_t)128); break; case lh3: lzh_lh3((uint32_t)128); break; #endif /* UNDEF */ #ifdef UNTESTED case lh4: lzh_lzh12((uint32_t)128); break; #endif /* UNTESTED */ case lh5: lzh_lzh13((uint32_t)128); break; default: mname = lzh_methname(method); if(mname != NULL) { do_indent(indent); (void)fprintf(stderr, "\tSorry, packing method not yet implemented.\n"); do_indent(indent); (void)fprintf(stderr, "File = \"%s\"; ", text); (void)fprintf(stderr, "method = %s, skipping file.\n", mname); lzh_skip(filehdr); return; } (void)fprintf(stderr, "There is something very wrong with this program!\n"); #ifdef SCAN do_error("macunpack: program error"); #endif /* SCAN */ exit(1); } /* Checks whether everything is packed as MacBinary. */ if(*lzh_file != 0 /* More checks possible here. */) { do_indent(indent); (void)fprintf(stderr, "File = \"%s\" ", text); (void)fprintf(stderr, "not packed in MacBinary, skipping file.\n"); #ifdef SCAN do_error("macunpack: not MacBinary"); #endif /* SCAN */ lzh_skip(filehdr); return; } copy(info, lzh_file, 128); rsrcLength = get4(info + I_RLENOFF); dataLength = get4(info + I_DLENOFF); transname(info + I_TYPEOFF, ftype, 4); transname(info + I_AUTHOFF, fauth, 4); if(list) { do_indent(indent); (void)fprintf(stderr, "name=\"%s\", type=%4.4s, author=%4.4s, data=%d, rsrc=%d", text, ftype, fauth, (int32_t)dataLength, (int32_t)rsrcLength); } if(info_only) { doit = 0; } else { doit = 1; } if(query) { doit = do_query(); } else if(list) { (void)fputc('\n', stderr); } if(doit) { define_name(text); start_info(info, (uint32_t)rsrcLength, (uint32_t)dataLength); } switch(method) { case lz4: if(verbose) { (void)fprintf(stderr, "\tNo Compression (%.5s)", filehdr->method); } if(doit) { lzh_nocomp(filehdr->upsize); } break; #ifdef UNTESTED case lz5: if(verbose) { (void)fprintf(stderr, "\tLZSS (%.5s) compressed (%4.1f%%)", filehdr->method, 100.0 * filehdr->psize / filehdr->upsize); } if(doit) { lzh_lzss1(filehdr->upsize); } break; case lzs: if(verbose) { (void)fprintf(stderr, "\tLZSS (%.5s) compressed (%4.1f%%)", filehdr->method, 100.0 * filehdr->psize / filehdr->upsize); } if(doit) { lzh_lzss2(filehdr->upsize); } break; #endif /* UNTESTED */ case lh0: if(verbose) { (void)fprintf(stderr, "\tNo Compression (%.5s)", filehdr->method); } if(doit) { lzh_nocomp(filehdr->upsize); } break; case lh1: if(verbose) { (void)fprintf(stderr, "\tLZAH (%.5s) compressed (%4.1f%%)", filehdr->method, 100.0 * filehdr->psize / filehdr->upsize); } if(doit) { lzh_lzah(filehdr->upsize); } break; #ifdef UNDEF case lh2: if(verbose) { (void)fprintf(stderr, "\tLZAH (%.5s) compressed (%4.1f%%)", filehdr->method, 100.0 * filehdr->psize / filehdr->upsize); } if(doit) { lzh_lh2(filehdr->upsize); } break; case lh3: if(verbose) { (void)fprintf(stderr, "\tLZH (%.5s) compressed (%4.1f%%)", filehdr->method, 100.0 * filehdr->psize / filehdr->upsize); } if(doit) { lzh_lzh3(filehdr->upsize); } break; #endif /* UNDEF */ #ifdef UNTESTED case lh4: if(verbose) { (void)fprintf(stderr, "\tLZH (%.5s) compressed (%4.1f%%)", filehdr->method, 100.0 * filehdr->psize / filehdr->upsize); } if(doit) { lzh_lzh12(filehdr->upsize); } break; #endif /* UNTESTED */ case lh5: if(verbose) { (void)fprintf(stderr, "\tLZH (%.5s) compressed (%4.1f%%)", filehdr->method, 100.0 * filehdr->psize / filehdr->upsize); } if(doit) { lzh_lzh13(filehdr->upsize); } } if(doit) { crc = (*updcrc)(INIT_CRC, (unsigned char*)lzh_file, filehdr->upsize); if(filehdr->crc != crc) { (void)fprintf(stderr, "CRC error on file: need 0x%04x, got 0x%04x\n", filehdr->crc, (int)crc); #ifdef SCAN do_error("macunpack: CRC error on file"); #endif /* SCAN */ exit(1); } start_data(); copy(out_ptr, lzh_file + 128, (int)(filehdr->upsize - 128)); } if(verbose) { (void)fprintf(stderr, ".\n"); } if(doit) { end_file(); } lzh_skip(filehdr); } static void lzh_skip (struct lzh_fileHdr *filehdr) { lzh_pointer += filehdr->psize; in_data_size -= filehdr->psize; } /*---------------------------------------------------------------------------*/ /* -lz4- and -lh0: No compression */ /*---------------------------------------------------------------------------*/ static void lzh_nocomp (uint32_t obytes) { copy(lzh_file, lzh_data, (int)obytes); } #ifdef UNTESTED /*---------------------------------------------------------------------------*/ /* -lz5-: LZSS compression, variant 1 */ /*---------------------------------------------------------------------------*/ static void lzh_lzss1 (uint32_t obytes) { int mask, ch, lzcnt, lzptr, ptr, count; char *p = lzh_lzbuf; int i, j; for(i = 0; i < 256; i++) { for(j = 0; j < 13; j++) { *p++ = i; } } for(i = 0; i < 256; i++) { *p++ = i; } for(i = 0; i < 256; i++) { *p++ = 255 - i; } for(i = 0; i < 128; i++) { *p++ = 0; } for(i = 0; i < 128; i++) { *p++ = ' '; } tmp_out_ptr = out_ptr; out_ptr = lzh_file; ptr = LZ5BUFFSIZE - LZ5LOOKAHEAD; count = 0; lzh_current = lzh_data; while(obytes != 0) { if(count == 0) { mask = *lzh_current++ & BYTEMASK; count = 8; } count--; ch = *lzh_current++ & BYTEMASK; if ((mask & 1) != 0) { *out_ptr++ = ch; lzh_lzbuf[ptr++] = ch; ptr &= LZ5MASK; obytes--; } else { lzcnt = *lzh_current++; lzptr = (ch & 0x00ff) | ((lzcnt << 4) & 0x0f00); lzcnt = (lzcnt & 0x000f) + 3; obytes -= lzcnt; do { ch = lzh_lzbuf[lzptr++]; lzh_lzbuf[ptr++] = ch; *out_ptr++ = ch; lzptr &= LZ5MASK; ptr &= LZ5MASK; } while (--lzcnt != 0) ; } mask >>= 1; } out_ptr = tmp_out_ptr; } /*---------------------------------------------------------------------------*/ /* -lzs-: LZSS compression, variant 2 */ /*---------------------------------------------------------------------------*/ static void lzh_lzss2 (uint32_t obytes) { int ch, lzcnt, lzptr, ptr, i; tmp_out_ptr = out_ptr; out_ptr = lzh_file; ptr = LZSBUFFSIZE - LZSLOOKAHEAD; for(i = 0; i < ptr; i++) { lzh_lzbuf[i] = ' '; } for(i = ptr; i < LZSBUFFSIZE; i++) { lzh_lzbuf[i] = 0; } bit_be_init_getbits(); bit_be_filestart = lzh_data; bit_be_inbytes = -1; while(obytes != 0) { if(bit_be_getbits(1) == 0) { ch = bit_be_getbits(8); *out_ptr++ = ch; lzh_lzbuf[ptr++] = ch; ptr &= LZSMASK; obytes--; } else { lzptr = bit_be_getbits(11); lzcnt = bit_be_getbits(4) + 3; obytes -= lzcnt; do { ch = lzh_lzbuf[lzptr++]; lzh_lzbuf[ptr++] = ch; *out_ptr++ = ch; lzptr &= LZSMASK; ptr &= LZSMASK; } while (--lzcnt != 0) ; } } out_ptr = tmp_out_ptr; } #endif /* UNTESTED */ /*---------------------------------------------------------------------------*/ /* -lh1-: LZ compression plus adaptive Huffman encoding */ /*---------------------------------------------------------------------------*/ static void lzh_lzah (uint32_t obytes) { lzh_current = lzh_data + 2; /* SKIPPING BLOCKSIZE! */ tmp_out_ptr = out_ptr; out_ptr = lzh_file; lzah_getbyte = lzh_getbyte; de_lzah(obytes); out_ptr = tmp_out_ptr; } static unsigned char lzh_getbyte (void) { return *lzh_current++; } #ifdef UNDEF /*---------------------------------------------------------------------------*/ /* -lh2-: LZ** compression */ /*---------------------------------------------------------------------------*/ static void lzh_lh2 (uint32_t obytes) { } /*---------------------------------------------------------------------------*/ /* -lh3-: LZ** compression */ /*---------------------------------------------------------------------------*/ static void lzh_lh3 (uint32_t obytes) { } #endif /* UNDEF */ #ifdef UNTESTED /*---------------------------------------------------------------------------*/ /* -lh4-: LZ(12) compression plus Huffman encoding */ /*---------------------------------------------------------------------------*/ static void lzh_lzh12 (uint32_t obytes) { lzh_current = lzh_data; tmp_out_ptr = out_ptr; out_ptr = lzh_file; /* Controlled by obytes only */ de_lzh((int32_t)(-1), (int32_t)obytes, &lzh_current, 12); out_ptr = tmp_out_ptr; } #endif /* UNTESTED */ /*---------------------------------------------------------------------------*/ /* -lh5-: LZ(13) compression plus Huffman encoding */ /*---------------------------------------------------------------------------*/ static void lzh_lzh13 (uint32_t obytes) { lzh_current = lzh_data; tmp_out_ptr = out_ptr; out_ptr = lzh_file; /* Controlled by obytes only */ de_lzh((int32_t)(-1), (int32_t)obytes, &lzh_current, 13); out_ptr = tmp_out_ptr; }