/* * UAE - The Un*x Amiga Emulator * * routines to handle compressed file automatically * * (c) 1996 Samuel Devulder, Tim Gunn * 2002-2004 Toni Wilen */ #include "sysconfig.h" #include "sysdeps.h" #include "options.h" #include "zfile.h" #include "unzip.h" #include "disk.h" #include "dms/cdata.h" #include "dms/pfile.h" #include "gui.h" #include "crc32.h" #include struct zfile { char *name; char *zipname; FILE *f; uae_u8 *data; int size; int seek; int deleteafterclose; struct zfile *next; }; static struct zfile *zlist = 0; int is_zlib; static int zlib_test (void) { #if defined WIN32 && !defined __MINGW32__ static int zlibmsg; if (is_zlib) return 1; if (zlibmsg) return 0; zlibmsg = 1; gui_message("zip and gzip support disabled because zlib1.dll is missing"); return 0; #else /* On non-Windows platforms, we can safely assume (I think) that if we got this * far zlib is present - Rich */ return 1; #endif } static struct zfile *zfile_create (void) { struct zfile *z; z = malloc (sizeof *z); if (!z) return 0; memset (z, 0, sizeof *z); z->next = zlist; zlist = z; return z; } static void zfile_free (struct zfile *f) { if (f->f) fclose (f->f); if (f->deleteafterclose) { unlink (f->name); write_log ("deleted temporary file '%s'\n", f->name); } free (f->name); free (f->data); free (f); } void zfile_exit (void) { struct zfile *l; while ((l = zlist)) { zlist = l->next; zfile_free (l); } } void zfile_fclose (struct zfile *f) { struct zfile *pl = NULL; struct zfile *l = zlist; struct zfile *nxt = NULL; if (!f) return; while (l != f) { if (l == 0) { write_log ("zfile: tried to free already freed filehandle!\n"); return; } pl = l; l = l->next; } if (l) nxt = l->next; zfile_free (f); if (l == 0) return; if(!pl) zlist = nxt; else pl->next = nxt; } static uae_u8 exeheader[]={0x00,0x00,0x03,0xf3,0x00,0x00,0x00,0x00}; int zfile_gettype (struct zfile *z) { uae_u8 buf[8]; char *ext; if (!z) return ZFILE_UNKNOWN; ext = strrchr (z->name, '.'); if (ext != NULL) { ext++; if (strcasecmp (ext, "adf") == 0) return ZFILE_DISKIMAGE; if (strcasecmp (ext, "adz") == 0) return ZFILE_DISKIMAGE; if (strcasecmp (ext, "roz") == 0) return ZFILE_ROM; if (strcasecmp (ext, "ipf") == 0) return ZFILE_DISKIMAGE; if (strcasecmp (ext, "fdi") == 0) return ZFILE_DISKIMAGE; if (strcasecmp (ext, "uss") == 0) return ZFILE_STATEFILE; if (strcasecmp (ext, "dms") == 0) return ZFILE_DISKIMAGE; if (strcasecmp (ext, "rom") == 0) return ZFILE_ROM; if (strcasecmp (ext, "key") == 0) return ZFILE_KEY; if (strcasecmp (ext, "nvr") == 0) return ZFILE_NVR; if (strcasecmp (ext, "uae") == 0) return ZFILE_CONFIGURATION; } memset (buf, 0, sizeof (buf)); zfile_fread (buf, 8, 1, z); zfile_fseek (z, -8, SEEK_CUR); if (!memcmp (buf, exeheader, sizeof(buf))) return ZFILE_DISKIMAGE; return ZFILE_UNKNOWN; } #if 0 #define TMP_PREFIX "uae_" static struct zfile *createinputfile (struct zfile *z) { FILE *f; struct zfile *z2; char *name; z2 = zfile_create (); if (!z->data) { z2->name = strdup (z->name); return z2; } name = tempnam (0, TMP_PREFIX); f = fopen (name, "wb"); if (!f) return 0; write_log ("created temporary file '%s'\n", name); fwrite (z->data, z->size, 1, f); fclose (f); z2->name = name; z2->deleteafterclose = 1; return z2; } static struct zfile *createoutputfile (struct zfile *z) { struct zfile *z2; char *name; name = tempnam (0, TMP_PREFIX); z2 = zfile_create (); z2->name = name; z2->deleteafterclose = 1; write_log ("allocated temporary file name '%s'\n", name); return z2; } /* we want to delete temporary files as early as possible */ static struct zfile *updateoutputfile (struct zfile *z) { struct zfile *z2 = 0; int size; FILE *f = fopen (z->name, "rb"); for (;;) { if (!f) break; fseek (f, 0, SEEK_END); size = ftell (f); fseek (f, 0, SEEK_SET); if (!size) break; z2 = zfile_fopen_empty (z->name, size); if (!z2) break; fread (z2->data, size, 1, f); fclose (f); zfile_fclose (z); return z2; } if (f) fclose (f); zfile_fclose (z); zfile_fclose (z2); return 0; } #endif static struct zfile *zuncompress (struct zfile *z); static struct zfile *gunzip (struct zfile *z) { uae_u8 header[2 + 1 + 1 + 4 + 1 + 1]; z_stream zs; int i, size, ret, first; uae_u8 flags; long offset; char name[MAX_DPATH]; uae_u8 buffer[8192]; struct zfile *z2; uae_u8 b; if (!zlib_test ()) return z; strcpy (name, z->name); memset (&zs, 0, sizeof (zs)); memset (header, 0, sizeof (header)); zfile_fread (header, sizeof (header), 1, z); flags = header[3]; if (header[0] != 0x1f && header[1] != 0x8b) return z; if (flags & 2) /* multipart not supported */ return z; if (flags & 32) /* encryption not supported */ return z; if (flags & 4) { /* skip extra field */ zfile_fread (&b, 1, 1, z); size = b; zfile_fread (&b, 1, 1, z); size |= b << 8; zfile_fseek (z, size + 2, SEEK_CUR); } if (flags & 8) { /* get original file name */ i = 0; do { zfile_fread (name + i, 1, 1, z); } while (name[i++]); } if (flags & 16) { /* skip comment */ i = 0; do { zfile_fread (&b, 1, 1, z); } while (b); } offset = zfile_ftell (z); zfile_fseek (z, -4, SEEK_END); zfile_fread (&b, 1, 1, z); size = b; zfile_fread (&b, 1, 1, z); size |= b << 8; zfile_fread (&b, 1, 1, z); size |= b << 16; zfile_fread (&b, 1, 1, z); size |= b << 24; if (size < 8 || size > 10000000) /* safety check */ return z; zfile_fseek (z, offset, SEEK_SET); z2 = zfile_fopen_empty (name, size); if (!z2) return z; zs.next_out = z2->data; zs.avail_out = size; first = 1; do { zs.next_in = buffer; zs.avail_in = sizeof (buffer); zfile_fread (buffer, sizeof (buffer), 1, z); if (first) { if (inflateInit2 (&zs, -MAX_WBITS) != Z_OK) break; first = 0; } ret = inflate (&zs, 0); } while (ret == Z_OK); inflateEnd (&zs); if (ret != Z_STREAM_END || first != 0) { zfile_fclose (z2); return z; } zfile_fclose (z); return z2; } static struct zfile *bunzip (const char *decompress, struct zfile *z) { return z; } static struct zfile *lha (struct zfile *z) { return z; } static struct zfile *dms (struct zfile *z) { int ret; struct zfile *zo; zo = zfile_fopen_empty ("zipped.dms", 1760 * 512); if (!zo) return z; ret = DMS_Process_File (z, zo, CMD_UNPACK, OPT_VERBOSE, 0, 0); if (ret == NO_PROBLEM || ret == DMS_FILE_END) { zfile_fclose (z); return zo; } return z; } #if 0 static struct zfile *dms (struct zfile *z) { char cmd[2048]; struct zfile *zi = createinputfile (z); struct zfile *zo = createoutputfile (z); if (zi && zo) { sprintf(cmd, "xdms -q u \"%s\" +\"%s\"", zi->name, zo->name); execute_command (cmd); } zfile_fclose (zi); zfile_fclose (z); return updateoutputfile (zo); } #endif static const char *ignoreextensions[] = { ".gif", ".jpg", ".png", ".xml", ".pdf", ".txt", 0 }; static const char *diskimageextensions[] = { ".adf", ".adz", ".ipf", ".fdi", 0 }; static int isdiskimage (char *name) { int i; i = 0; while (diskimageextensions[i]) { if (strlen (name) > 3 && !strcasecmp (name + strlen (name) - 4, diskimageextensions[i])) return 1; i++; } return 0; } static struct zfile *unzip (struct zfile *z) { unzFile uz; unz_file_info file_info; char filename_inzip[2048]; struct zfile *zf; unsigned int err, zipcnt, i, we_have_file = 0; int select; char tmphist[MAX_DPATH]; int first = 1; if (!zlib_test ()) return z; zf = 0; uz = unzOpen (z); if (!uz) return z; if (unzGoToFirstFile (uz) != UNZ_OK) return z; zipcnt = 1; tmphist[0] = 0; for (;;) { err = unzGetCurrentFileInfo(uz,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0); if (err != UNZ_OK) return z; if (file_info.uncompressed_size > 0) { i = 0; while (ignoreextensions[i]) { if (strlen(filename_inzip) > strlen (ignoreextensions[i]) && !strcasecmp (ignoreextensions[i], filename_inzip + strlen (filename_inzip) - strlen (ignoreextensions[i]))) break; i++; } if (!ignoreextensions[i]) { if (tmphist[0]) { DISK_history_add (tmphist, -1); tmphist[0] = 0; first = 0; } if (first) { if (isdiskimage (filename_inzip)) sprintf (tmphist,"%s/%s", z->name, filename_inzip); } else { sprintf (tmphist,"%s/%s", z->name, filename_inzip); DISK_history_add (tmphist, -1); tmphist[0] = 0; } select = 0; if (!z->zipname) select = 1; if (z->zipname && !strcasecmp (z->zipname, filename_inzip)) select = -1; if (z->zipname && z->zipname[0] == '#' && atol (z->zipname + 1) == (int)zipcnt) select = -1; if (select && !we_have_file) { unsigned int err = unzOpenCurrentFile (uz); if (err == UNZ_OK) { zf = zfile_fopen_empty (filename_inzip, file_info.uncompressed_size); if (zf) { err = unzReadCurrentFile (uz, zf->data, file_info.uncompressed_size); unzCloseCurrentFile (uz); if (err == 0 || err == file_info.uncompressed_size) { zf = zuncompress (zf); if (select < 0 || zfile_gettype (zf)) { we_have_file = 1; } } } if (!we_have_file) { zfile_fclose (zf); zf = 0; } } } } } zipcnt++; err = unzGoToNextFile (uz); if (err != UNZ_OK) break; } if (zf) { zfile_fclose (z); z = zf; } return z; } static struct zfile *zuncompress (struct zfile *z) { char *name = z->name; char *ext = strrchr (name, '.'); uae_u8 header[4]; if (ext != NULL) { ext++; if (strcasecmp (ext, "zip") == 0 && zlib_test ()) return unzip (z); if (strcasecmp (ext, "gz") == 0) return gunzip (z); if (strcasecmp (ext, "adz") == 0) return gunzip (z); if (strcasecmp (ext, "roz") == 0) return gunzip (z); if (strcasecmp (ext, "dms") == 0) return dms (z); if (strcasecmp (ext, "lha") == 0 || strcasecmp (ext, "lzh") == 0) return lha (z); memset (header, 0, sizeof (header)); zfile_fseek (z, 0, SEEK_SET); zfile_fread (header, sizeof (header), 1, z); zfile_fseek (z, 0, SEEK_SET); if (header[0] == 0x1f && header[1] == 0x8b) return gunzip (z); if (header[0] == 'P' && header[1] == 'K') return unzip (z); if (header[0] == 'D' && header[1] == 'M' && header[2] == 'S' && header[3] == '!') return dms (z); } return z; } static FILE *openzip (char *name, char *zippath) { int i; char v; i = strlen (name) - 2; if (zippath) zippath[0] = 0; while (i > 0) { if ((name[i] == '/' || name[i] == '\\') && i > 4) { v = name[i]; name[i] = 0; if (!strcasecmp (name + i - 4, ".zip")) { FILE *f = fopen (name, "rb"); if (f) { if (zippath) strcpy (zippath, name + i + 1); return f; } } name[i] = v; } i--; } return 0; } #ifdef SINGLEFILE extern uae_u8 singlefile_data[]; static struct zfile *zfile_opensinglefile (struct zfile *l) { uae_u8 *p = singlefile_data; int size, offset; char tmp[256], *s; strcpy (tmp, l->name); s = tmp + strlen (tmp) - 1; while (*s != 0 && *s != '/' && *s != '\\') s--; if (s > tmp) s++; write_log("loading from singlefile: '%s'\n", tmp); while (*p++); offset = (p[0] << 24)|(p[1] << 16)|(p[2] << 8)|(p[3] << 0); p += 4; for (;;) { size = (p[0] << 24)|(p[1] << 16)|(p[2] << 8)|(p[3] << 0); if (!size) break; if (!strcmpi (tmp, p + 4)) { l->data = singlefile_data + offset; l->size = size; write_log ("found, size %d\n", size); return l; } offset += size; p += 4; p += strlen (p) + 1; } write_log ("not found\n"); return 0; } #endif /* * fopen() for a compressed file */ struct zfile *zfile_fopen (const char *name, const char *mode) { struct zfile *l; FILE *f; char zipname[1000]; if( *name == '\0' ) return NULL; l = zfile_create (); l->name = strdup (name); #ifdef SINGLEFILE if (zfile_opensinglefile (l)) return l; #endif f = openzip (l->name, zipname); if (f) { if (strcasecmp (mode, "rb")) { zfile_fclose (l); fclose (f); return 0; } l->zipname = strdup (zipname); } if (!f) { f = fopen (name, mode); if (!f) { zfile_fclose (l); return 0; } } l->f = f; l = zuncompress (l); return l; } int zfile_exists (const char *name) { char fname[2000]; FILE *f; strcpy (fname, name); f = openzip (fname, 0); if (!f) f = fopen(name,"rb"); if (!f) return 0; fclose (f); return 1; } int zfile_iscompressed (struct zfile *z) { return z->data ? 1 : 0; } struct zfile *zfile_fopen_empty (const char *name, int size) { struct zfile *l; l = zfile_create (); l->name = strdup (name); l->data = malloc (size); l->size = size; memset (l->data, 0, size); return l; } long zfile_ftell (struct zfile *z) { if (z->data) return z->seek; return ftell (z->f); } int zfile_fseek (struct zfile *z, long offset, int mode) { if (z->data) { int old = z->seek; switch (mode) { case SEEK_SET: z->seek = offset; break; case SEEK_CUR: z->seek += offset; break; case SEEK_END: z->seek = z->size - offset; break; } if (z->seek < 0) z->seek = 0; if (z->seek > z->size) z->seek = z->size; return old; } return fseek (z->f, offset, mode); } size_t zfile_fread (void *b, size_t l1, size_t l2, struct zfile *z) { long len = l1 * l2; if (z->data) { if (z->seek + len > z->size) len = z->size - z->seek; memcpy (b, z->data + z->seek, len); z->seek += len; return len; } return fread (b, l1, l2, z->f); } size_t zfile_fwrite (const void *b, size_t l1, size_t l2, struct zfile *z) { long len = l1 * l2; if (z->data) { if (z->seek + len > z->size) len = z->size - z->seek; memcpy (z->data + z->seek, b, len); z->seek += len; return len; } return fwrite (b, l1, l2, z->f); } int zfile_zuncompress (void *dst, int dstsize, struct zfile *src, int srcsize) { z_stream zs; int v; uae_u8 inbuf[4096]; int incnt; if (!zlib_test ()) return 0; memset (&zs, 0, sizeof(zs)); if (inflateInit (&zs) != Z_OK) return 0; zs.next_out = dst; zs.avail_out = dstsize; incnt = 0; v = Z_OK; while (v == Z_OK && zs.avail_out > 0) { if (zs.avail_in == 0) { int left = srcsize - incnt; if (left == 0) break; if (left > (int)sizeof (inbuf)) left = sizeof (inbuf); zs.next_in = inbuf; zs.avail_in = zfile_fread (inbuf, 1, left, src); incnt += left; } v = inflate (&zs, 0); } inflateEnd (&zs); return 0; } int zfile_zcompress (struct zfile *f, void *src, int size) { int v; z_stream zs; uae_u8 outbuf[4096]; #ifdef WIN32 if (!is_zlib) return 0; #endif memset (&zs, 0, sizeof (zs)); if (deflateInit (&zs, Z_DEFAULT_COMPRESSION) != Z_OK) return 0; zs.next_in = src; zs.avail_in = size; v = Z_OK; while (v == Z_OK) { zs.next_out = outbuf; zs.avail_out = sizeof (outbuf); v = deflate(&zs, Z_NO_FLUSH | Z_FINISH); if (sizeof(outbuf) - zs.avail_out > 0) zfile_fwrite (outbuf, 1, sizeof (outbuf) - zs.avail_out, f); } deflateEnd (&zs); return zs.total_out; } uae_u32 zfile_crc32 (struct zfile *f) { uae_u8 *p; int pos, size; uae_u32 crc; if (!f) return 0; if (f->data) return get_crc32 (f->data, f->size); pos = zfile_ftell (f); zfile_fseek (f, 0, SEEK_END); size = zfile_ftell (f); p = xmalloc (size); if (!p) return 0; memset (p, 0, size); zfile_fseek (f, 0, SEEK_SET); zfile_fread (p, 1, size, f); zfile_fseek (f, pos, SEEK_SET); crc = get_crc32 (p, size); free (p); return crc; }