708 lines
15 KiB
C
708 lines
15 KiB
C
/*
|
|
Hatari - zip.c
|
|
|
|
This file is distributed under the GNU General Public License, version 2
|
|
or at your option any later version. Read the file gpl.txt for details.
|
|
|
|
Zipped disk support, uses zlib
|
|
*/
|
|
const char ZIP_fileid[] = "Hatari zip.c : " __DATE__ " " __TIME__;
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <sys/types.h>
|
|
#include <zlib.h>
|
|
|
|
#include "main.h"
|
|
#include "dim.h"
|
|
#include "file.h"
|
|
#include "floppy.h"
|
|
#include "floppy_ipf.h"
|
|
#include "floppy_stx.h"
|
|
#include "log.h"
|
|
#include "msa.h"
|
|
#include "st.h"
|
|
#include "str.h"
|
|
#include "unzip.h"
|
|
#include "zip.h"
|
|
|
|
#ifdef QNX
|
|
#include <sys/dir.h>
|
|
#define dirent direct
|
|
#endif
|
|
|
|
/* #define SAVE_TO_ZIP_IMAGES */
|
|
|
|
#define ZIP_PATH_MAX 256
|
|
|
|
#if HAVE_LIBZ
|
|
|
|
/* Possible disk image extensions to scan for */
|
|
static const char * const pszDiskNameExts[] =
|
|
{
|
|
".msa",
|
|
".st",
|
|
".dim",
|
|
".ipf",
|
|
".raw",
|
|
".ctr",
|
|
".stx",
|
|
NULL
|
|
};
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Does filename end with a .ZIP extension? If so, return true.
|
|
*/
|
|
bool ZIP_FileNameIsZIP(const char *pszFileName)
|
|
{
|
|
return File_DoesFileExtensionMatch(pszFileName,".zip");
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Check if a file name contains a slash or backslash and return its position.
|
|
*/
|
|
static int Zip_FileNameHasSlash(const char *fn)
|
|
{
|
|
int i=0;
|
|
|
|
while (fn[i] != '\0')
|
|
{
|
|
if (fn[i] == '\\' || fn[i] == '/')
|
|
return i;
|
|
i++;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Returns a list of files from a zip file. returns NULL on failure,
|
|
* returns a pointer to an array of strings if successful. Sets nfiles
|
|
* to the number of files.
|
|
*/
|
|
zip_dir *ZIP_GetFiles(const char *pszFileName)
|
|
{
|
|
int nfiles;
|
|
unsigned int i;
|
|
unz_global_info gi;
|
|
int err;
|
|
unzFile uf;
|
|
char **filelist;
|
|
unz_file_info file_info;
|
|
char filename_inzip[ZIP_PATH_MAX];
|
|
zip_dir *zd = NULL;
|
|
|
|
uf = unzOpen(pszFileName);
|
|
if (uf == NULL)
|
|
{
|
|
Log_Printf(LOG_ERROR, "ZIP_GetFiles: Cannot open %s\n", pszFileName);
|
|
return NULL;
|
|
}
|
|
|
|
err = unzGetGlobalInfo(uf,&gi);
|
|
if (err != UNZ_OK)
|
|
{
|
|
Log_Printf(LOG_ERROR, "Error %d with zipfile in unzGetGlobalInfo \n",err);
|
|
return NULL;
|
|
}
|
|
|
|
/* allocate a file list */
|
|
filelist = (char **)malloc(gi.number_entry*sizeof(char *));
|
|
if (!filelist)
|
|
{
|
|
perror("ZIP_GetFiles");
|
|
unzClose(uf);
|
|
return NULL;
|
|
}
|
|
|
|
nfiles = gi.number_entry; /* set the number of files */
|
|
|
|
for (i = 0; i < gi.number_entry; i++)
|
|
{
|
|
err = unzGetCurrentFileInfo(uf, &file_info, filename_inzip, ZIP_PATH_MAX, NULL, 0, NULL, 0);
|
|
if (err != UNZ_OK)
|
|
{
|
|
Log_Printf(LOG_ERROR, "ZIP_GetFiles: Error in ZIP-file\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
filelist[i] = (char *)malloc(strlen(filename_inzip) + 1);
|
|
if (!filelist[i])
|
|
{
|
|
perror("ZIP_GetFiles");
|
|
goto cleanup;
|
|
}
|
|
|
|
strcpy(filelist[i], filename_inzip);
|
|
if ((i+1) < gi.number_entry)
|
|
{
|
|
err = unzGoToNextFile(uf);
|
|
if (err != UNZ_OK)
|
|
{
|
|
Log_Printf(LOG_ERROR, "ZIP_GetFiles: Error in ZIP-file\n");
|
|
goto cleanup;
|
|
}
|
|
}
|
|
}
|
|
|
|
zd = (zip_dir *)malloc(sizeof(zip_dir));
|
|
if (zd)
|
|
{
|
|
zd->names = filelist;
|
|
zd->nfiles = nfiles;
|
|
}
|
|
else
|
|
{
|
|
perror("ZIP_GetFiles");
|
|
}
|
|
|
|
cleanup:
|
|
unzClose(uf);
|
|
if (!zd && filelist)
|
|
{
|
|
/* deallocate memory */
|
|
for (; i > 0; i--)
|
|
free(filelist[i]);
|
|
free(filelist);
|
|
}
|
|
|
|
return zd;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Free the memory that has been allocated for a zip_dir.
|
|
*/
|
|
void ZIP_FreeZipDir(zip_dir *f_zd)
|
|
{
|
|
while (f_zd->nfiles > 0)
|
|
{
|
|
f_zd->nfiles--;
|
|
free(f_zd->names[f_zd->nfiles]);
|
|
f_zd->names[f_zd->nfiles] = NULL;
|
|
}
|
|
free(f_zd->names);
|
|
f_zd->names = NULL;
|
|
free(f_zd);
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Free the memory that has been allocated for fentries.
|
|
*/
|
|
static void ZIP_FreeFentries(struct dirent **fentries, int entries)
|
|
{
|
|
while (entries > 0)
|
|
{
|
|
entries--;
|
|
free(fentries[entries]);
|
|
}
|
|
free(fentries);
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Returns a list of files from the directory (dir) in a zip file list (zip)
|
|
* sets entries to the number of entries and returns a dirent structure, or
|
|
* NULL on failure. NOTE: only f_name is set in the dirent structures.
|
|
*/
|
|
struct dirent **ZIP_GetFilesDir(const zip_dir *zip, const char *dir, int *entries)
|
|
{
|
|
int i,j;
|
|
zip_dir *files;
|
|
char *temp;
|
|
bool flag;
|
|
int slash;
|
|
struct dirent **fentries;
|
|
|
|
files = (zip_dir *)malloc(sizeof(zip_dir));
|
|
if (!files)
|
|
{
|
|
perror("ZIP_GetFilesDir");
|
|
return NULL;
|
|
}
|
|
|
|
files->names = (char **)malloc((zip->nfiles + 1) * sizeof(char *));
|
|
if (!files->names)
|
|
{
|
|
perror("ZIP_GetFilesDir");
|
|
free(files);
|
|
return NULL;
|
|
}
|
|
|
|
/* add ".." directory */
|
|
files->nfiles = 1;
|
|
temp = (char *)malloc(4);
|
|
if (!temp)
|
|
{
|
|
ZIP_FreeZipDir(files);
|
|
return NULL;
|
|
}
|
|
temp[0] = temp[1] = '.';
|
|
temp[2] = '/';
|
|
temp[3] = '\0';
|
|
files->names[0] = temp;
|
|
|
|
for (i = 0; i < zip->nfiles; i++)
|
|
{
|
|
if (strlen(zip->names[i]) > strlen(dir))
|
|
{
|
|
if (strncasecmp(zip->names[i], dir, strlen(dir)) == 0)
|
|
{
|
|
temp = zip->names[i];
|
|
temp = (char *)(temp + strlen(dir));
|
|
if (temp[0] != '\0')
|
|
{
|
|
if ((slash=Zip_FileNameHasSlash(temp)) > 0)
|
|
{
|
|
/* file is in a subdirectory, add this subdirectory if it doesn't exist in the list */
|
|
flag = false;
|
|
for (j = files->nfiles-1; j > 0; j--)
|
|
{
|
|
if (strncasecmp(temp, files->names[j], slash+1) == 0)
|
|
flag = true;
|
|
}
|
|
if (flag == false)
|
|
{
|
|
files->names[files->nfiles] = (char *)malloc(slash+2);
|
|
if (!files->names[files->nfiles])
|
|
{
|
|
perror("ZIP_GetFilesDir");
|
|
ZIP_FreeZipDir(files);
|
|
return NULL;
|
|
}
|
|
strncpy(files->names[files->nfiles], temp, slash+1);
|
|
((char *)files->names[files->nfiles])[slash+1] = '\0';
|
|
files->nfiles++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* add a filename */
|
|
files->names[files->nfiles] = (char *)malloc(strlen(temp)+1);
|
|
if (!files->names[files->nfiles])
|
|
{
|
|
perror("ZIP_GetFilesDir");
|
|
ZIP_FreeZipDir(files);
|
|
return NULL;
|
|
}
|
|
strncpy(files->names[files->nfiles], temp, strlen(temp));
|
|
((char *)files->names[files->nfiles])[strlen(temp)] = '\0';
|
|
files->nfiles++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* copy to a dirent structure */
|
|
*entries = files->nfiles;
|
|
fentries = (struct dirent **)malloc(sizeof(struct dirent *)*files->nfiles);
|
|
if (!fentries)
|
|
{
|
|
perror("ZIP_GetFilesDir");
|
|
ZIP_FreeZipDir(files);
|
|
return NULL;
|
|
}
|
|
for (i = 0; i < files->nfiles; i++)
|
|
{
|
|
fentries[i] = (struct dirent *)malloc(sizeof(struct dirent));
|
|
if (!fentries[i])
|
|
{
|
|
perror("ZIP_GetFilesDir");
|
|
ZIP_FreeFentries(fentries, i+1);
|
|
ZIP_FreeZipDir(files);
|
|
return NULL;
|
|
}
|
|
strcpy(fentries[i]->d_name, files->names[i]);
|
|
}
|
|
|
|
ZIP_FreeZipDir(files);
|
|
|
|
return fentries;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Check an image file in the archive, return the uncompressed length
|
|
*/
|
|
static long ZIP_CheckImageFile(unzFile uf, char *filename, int namelen, int *pImageType)
|
|
{
|
|
unz_file_info file_info;
|
|
|
|
if (unzLocateFile(uf,filename, 0) != UNZ_OK)
|
|
{
|
|
Log_Printf(LOG_ERROR, "Error: File \"%s\" not found in the archive!\n", filename);
|
|
return -1;
|
|
}
|
|
|
|
if (unzGetCurrentFileInfo(uf, &file_info, filename, namelen, NULL, 0, NULL, 0) != UNZ_OK)
|
|
{
|
|
Log_Printf(LOG_ERROR, "Error with zipfile in unzGetCurrentFileInfo\n");
|
|
return -1;
|
|
}
|
|
|
|
/* check for .stx, .ipf, .msa, .dim or .st extension */
|
|
if (STX_FileNameIsSTX(filename, false))
|
|
{
|
|
*pImageType = FLOPPY_IMAGE_TYPE_STX;
|
|
return file_info.uncompressed_size;
|
|
}
|
|
|
|
if (IPF_FileNameIsIPF(filename, false))
|
|
{
|
|
*pImageType = FLOPPY_IMAGE_TYPE_IPF;
|
|
return file_info.uncompressed_size;
|
|
}
|
|
|
|
if (MSA_FileNameIsMSA(filename, false))
|
|
{
|
|
*pImageType = FLOPPY_IMAGE_TYPE_MSA;
|
|
return file_info.uncompressed_size;
|
|
}
|
|
|
|
if (ST_FileNameIsST(filename, false))
|
|
{
|
|
*pImageType = FLOPPY_IMAGE_TYPE_ST;
|
|
return file_info.uncompressed_size;
|
|
}
|
|
|
|
if (DIM_FileNameIsDIM(filename, false))
|
|
{
|
|
*pImageType = FLOPPY_IMAGE_TYPE_DIM;
|
|
return file_info.uncompressed_size;
|
|
}
|
|
|
|
Log_Printf(LOG_ERROR, "Not an .ST, .MSA, .DIM, .IPF or .STX file.\n");
|
|
return 0;
|
|
}
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Return the first matching file in a zip, or NULL on failure.
|
|
* String buffer size is ZIP_PATH_MAX
|
|
*/
|
|
static char *ZIP_FirstFile(const char *filename, const char * const ppsExts[])
|
|
{
|
|
zip_dir *files;
|
|
int i, j;
|
|
char *name;
|
|
|
|
files = ZIP_GetFiles(filename);
|
|
if (files == NULL)
|
|
return NULL;
|
|
|
|
name = malloc(ZIP_PATH_MAX);
|
|
if (!name)
|
|
{
|
|
perror("ZIP_FirstFile");
|
|
ZIP_FreeZipDir(files);
|
|
return NULL;
|
|
}
|
|
|
|
/* Do we have to scan for a certain extension? */
|
|
if (ppsExts)
|
|
{
|
|
name[0] = '\0';
|
|
for(i = files->nfiles-1; i >= 0; i--)
|
|
{
|
|
for (j = 0; ppsExts[j] != NULL; j++)
|
|
{
|
|
if (File_DoesFileExtensionMatch(files->names[i], ppsExts[j]))
|
|
{
|
|
strncpy(name, files->names[i], ZIP_PATH_MAX);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* There was no extension given -> use the very first name */
|
|
strncpy(name, files->names[0], ZIP_PATH_MAX);
|
|
}
|
|
|
|
/* free the files */
|
|
ZIP_FreeZipDir(files);
|
|
|
|
if (name[0] == '\0')
|
|
{
|
|
free(name);
|
|
return NULL;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Extract a file (filename) from a ZIP-file (uf), the number of
|
|
* bytes to uncompress is size. Returns a pointer to a buffer containing
|
|
* the uncompressed data, or NULL.
|
|
*/
|
|
static void *ZIP_ExtractFile(unzFile uf, const char *filename, uLong size)
|
|
{
|
|
int err = UNZ_OK;
|
|
char filename_inzip[ZIP_PATH_MAX];
|
|
void* buf;
|
|
uInt size_buf;
|
|
unz_file_info file_info;
|
|
|
|
|
|
if (unzLocateFile(uf,filename, 0) != UNZ_OK)
|
|
{
|
|
Log_Printf(LOG_ERROR, "ZIP_ExtractFile: could not find file in archive\n");
|
|
return NULL;
|
|
}
|
|
|
|
err = unzGetCurrentFileInfo(uf,&file_info,filename_inzip,sizeof(filename_inzip),NULL,0,NULL,0);
|
|
|
|
if (err != UNZ_OK)
|
|
{
|
|
Log_Printf(LOG_ERROR, "ZIP_ExtractFile: could not get file info\n");
|
|
return NULL;
|
|
}
|
|
|
|
size_buf = size;
|
|
buf = malloc(size_buf);
|
|
if (!buf)
|
|
{
|
|
perror("ZIP_ExtractFile");
|
|
return NULL;
|
|
}
|
|
|
|
err = unzOpenCurrentFile(uf);
|
|
if (err != UNZ_OK)
|
|
{
|
|
Log_Printf(LOG_ERROR, "ZIP_ExtractFile: could not open file\n");
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
do
|
|
{
|
|
err = unzReadCurrentFile(uf,buf,size_buf);
|
|
if (err < 0)
|
|
{
|
|
Log_Printf(LOG_ERROR, "ZIP_ExtractFile: could not read file\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
while (err > 0);
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Load disk image from a .ZIP archive into memory, set the number
|
|
* of bytes loaded into pImageSize and return the data or NULL on error.
|
|
*/
|
|
Uint8 *ZIP_ReadDisk(int Drive, const char *pszFileName, const char *pszZipPath, long *pImageSize, int *pImageType)
|
|
{
|
|
uLong ImageSize=0;
|
|
unzFile uf=NULL;
|
|
Uint8 *buf;
|
|
char *path;
|
|
Uint8 *pDiskBuffer = NULL;
|
|
|
|
*pImageSize = 0;
|
|
*pImageType = FLOPPY_IMAGE_TYPE_NONE;
|
|
|
|
uf = unzOpen(pszFileName);
|
|
if (uf == NULL)
|
|
{
|
|
Log_Printf(LOG_ERROR, "Cannot open %s\n", pszFileName);
|
|
return NULL;
|
|
}
|
|
|
|
if (pszZipPath == NULL || pszZipPath[0] == 0)
|
|
{
|
|
path = ZIP_FirstFile(pszFileName, pszDiskNameExts);
|
|
if (path == NULL)
|
|
{
|
|
Log_Printf(LOG_ERROR, "Cannot open %s\n", pszFileName);
|
|
unzClose(uf);
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
path = malloc(ZIP_PATH_MAX);
|
|
if (path == NULL)
|
|
{
|
|
perror("ZIP_ReadDisk");
|
|
unzClose(uf);
|
|
return NULL;
|
|
}
|
|
strncpy(path, pszZipPath, ZIP_PATH_MAX);
|
|
path[ZIP_PATH_MAX-1] = '\0';
|
|
}
|
|
|
|
ImageSize = ZIP_CheckImageFile(uf, path, ZIP_PATH_MAX, pImageType);
|
|
if (ImageSize <= 0)
|
|
{
|
|
unzClose(uf);
|
|
free(path);
|
|
return NULL;
|
|
}
|
|
|
|
/* extract to buf */
|
|
buf = ZIP_ExtractFile(uf, path, ImageSize);
|
|
|
|
unzCloseCurrentFile(uf);
|
|
unzClose(uf);
|
|
free(path);
|
|
path = NULL;
|
|
|
|
if (buf == NULL)
|
|
{
|
|
return NULL; /* failed extraction, return error */
|
|
}
|
|
|
|
switch(*pImageType) {
|
|
case FLOPPY_IMAGE_TYPE_IPF:
|
|
#ifndef HAVE_CAPSIMAGE
|
|
Log_AlertDlg(LOG_ERROR, "This version of Hatari was not built with IPF support, this disk image can't be handled.");
|
|
return NULL;
|
|
#else
|
|
/* return buffer */
|
|
pDiskBuffer = buf;
|
|
break;
|
|
#endif
|
|
case FLOPPY_IMAGE_TYPE_STX:
|
|
/* return buffer */
|
|
pDiskBuffer = buf;
|
|
break;
|
|
case FLOPPY_IMAGE_TYPE_MSA:
|
|
/* uncompress the MSA file */
|
|
pDiskBuffer = MSA_UnCompress(buf, (long *)&ImageSize, ImageSize);
|
|
free(buf);
|
|
buf = NULL;
|
|
break;
|
|
case FLOPPY_IMAGE_TYPE_DIM:
|
|
/* Skip DIM header */
|
|
ImageSize -= 32;
|
|
memmove(buf, buf+32, ImageSize);
|
|
/* return buffer */
|
|
pDiskBuffer = buf;
|
|
break;
|
|
case FLOPPY_IMAGE_TYPE_ST:
|
|
/* ST image => return buffer directly */
|
|
pDiskBuffer = buf;
|
|
break;
|
|
}
|
|
|
|
if (pDiskBuffer)
|
|
{
|
|
*pImageSize = ImageSize;
|
|
}
|
|
return pDiskBuffer;
|
|
}
|
|
|
|
|
|
/*-----------------------------------------------------------------------*/
|
|
/**
|
|
* Load first file from a .ZIP archive into memory, and return the number
|
|
* of bytes loaded.
|
|
*/
|
|
Uint8 *ZIP_ReadFirstFile(const char *pszFileName, long *pImageSize, const char * const ppszExts[])
|
|
{
|
|
unzFile uf = NULL;
|
|
Uint8 *pBuffer = NULL;
|
|
char *pszZipPath;
|
|
unz_file_info file_info;
|
|
|
|
*pImageSize = 0;
|
|
|
|
/* Open the ZIP file */
|
|
uf = unzOpen(pszFileName);
|
|
if (uf == NULL)
|
|
{
|
|
Log_Printf(LOG_ERROR, "Cannot open '%s'\n", pszFileName);
|
|
return NULL;
|
|
}
|
|
|
|
/* Locate the first file in the ZIP archive */
|
|
pszZipPath = ZIP_FirstFile(pszFileName, ppszExts);
|
|
if (pszZipPath == NULL)
|
|
{
|
|
Log_Printf(LOG_ERROR, "Failed to locate first file in '%s'\n", pszFileName);
|
|
unzClose(uf);
|
|
return NULL;
|
|
}
|
|
|
|
if (unzLocateFile(uf, pszZipPath, 0) != UNZ_OK)
|
|
{
|
|
Log_Printf(LOG_ERROR, "Error: Can not locate '%s' in the archive!\n", pszZipPath);
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Get file information (file size!) */
|
|
if (unzGetCurrentFileInfo(uf, &file_info, pszZipPath, ZIP_PATH_MAX, NULL, 0, NULL, 0) != UNZ_OK)
|
|
{
|
|
Log_Printf(LOG_ERROR, "Error with zipfile in unzGetCurrentFileInfo.\n");
|
|
goto cleanup;
|
|
}
|
|
|
|
/* Extract to buffer */
|
|
pBuffer = ZIP_ExtractFile(uf, pszZipPath, file_info.uncompressed_size);
|
|
if (pBuffer)
|
|
*pImageSize = file_info.uncompressed_size;
|
|
|
|
/* And close the file */
|
|
unzCloseCurrentFile(uf);
|
|
cleanup:
|
|
unzClose(uf);
|
|
free(pszZipPath);
|
|
|
|
return pBuffer;
|
|
}
|
|
|
|
#else
|
|
|
|
bool ZIP_FileNameIsZIP(const char *pszFileName)
|
|
{
|
|
return false;
|
|
}
|
|
Uint8 *ZIP_ReadDisk(int Drive, const char *name, const char *path, long *size , int *pImageType)
|
|
{
|
|
return NULL;
|
|
}
|
|
struct dirent **ZIP_GetFilesDir(const zip_dir *zip, const char *dir, int *entries)
|
|
{
|
|
return NULL;
|
|
}
|
|
zip_dir *ZIP_GetFiles(const char *pszFileName)
|
|
{
|
|
return NULL;
|
|
}
|
|
void ZIP_FreeZipDir(zip_dir *f_zd)
|
|
{
|
|
}
|
|
|
|
#endif /* HAVE_LIBZ */
|
|
|
|
/**
|
|
* Save .ZIP file from memory buffer. Returns true if all is OK.
|
|
*
|
|
* Not yet implemented.
|
|
*/
|
|
bool ZIP_WriteDisk(int Drive, const char *pszFileName,unsigned char *pBuffer,int ImageSize)
|
|
{
|
|
return false;
|
|
}
|