mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-11-16 09:11:17 +00:00
a3ac7ee081
SVN r2115 (trunk)
1678 lines
41 KiB
C
1678 lines
41 KiB
C
/*
|
|
** zipdir.c
|
|
** Copyright (C) 2008-2009 Randy Heit
|
|
**
|
|
** This program is free software; you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation; either version 2 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program; if not, write to the Free Software
|
|
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
**
|
|
****************************************************************************
|
|
**
|
|
** Usage: zipdir [-dfuq] <zip file> <directory> ...
|
|
**
|
|
** Given one or more directories, their contents are scanned recursively.
|
|
** If any files are newer than the zip file or the zip file does not exist,
|
|
** then everything in the specified directories is stored in the zip. The
|
|
** base directory names are not stored in the zip file, but subdirectories
|
|
** recursed into are stored.
|
|
*/
|
|
|
|
// HEADER FILES ------------------------------------------------------------
|
|
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#ifdef _WIN32
|
|
#include <io.h>
|
|
#define stat _stat
|
|
#else
|
|
#include <dirent.h>
|
|
#if !defined(__sun)
|
|
#include <fts.h>
|
|
#endif
|
|
#endif
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include "zlib.h"
|
|
#include "bzlib.h"
|
|
#include "LzmaEnc.h"
|
|
#include "7zVersion.h"
|
|
#ifdef PPMD
|
|
#include "../../ppmd/PPMd.h"
|
|
#endif
|
|
|
|
// MACROS ------------------------------------------------------------------
|
|
|
|
#ifndef _WIN32
|
|
#define __cdecl
|
|
#endif
|
|
|
|
#ifndef __BIG_ENDIAN__
|
|
#define MAKE_ID(a,b,c,d) ((a)|((b)<<8)|((c)<<16)|((d)<<24))
|
|
#define LittleShort(x) (x)
|
|
#define LittleLong(x) (x)
|
|
#else
|
|
#define MAKE_ID(a,b,c,d) ((d)|((c)<<8)|((b)<<16)|((a)<<24))
|
|
static unsigned short LittleShort(unsigned short x)
|
|
{
|
|
return (x>>8) | (x<<8);
|
|
}
|
|
|
|
static unsigned int LittleLong(unsigned int x)
|
|
{
|
|
return (x>>24) | ((x>>8) & 0xff00) | ((x<<8) & 0xff0000) | (x<<24);
|
|
}
|
|
#endif
|
|
|
|
#define ZIP_LOCALFILE MAKE_ID('P','K',3,4)
|
|
#define ZIP_CENTRALFILE MAKE_ID('P','K',1,2)
|
|
#define ZIP_ENDOFDIR MAKE_ID('P','K',5,6)
|
|
|
|
#define METHOD_STORED 0
|
|
#define METHOD_DEFLATE 8
|
|
#define METHOD_BZIP2 12
|
|
#define METHOD_LZMA 14
|
|
#define METHOD_PPMD 98
|
|
|
|
// Buffer size for central directory search
|
|
#define BUFREADCOMMENT (0x400)
|
|
|
|
#ifndef PATH_MAX
|
|
#define PATH_MAX 4096
|
|
#endif
|
|
|
|
// TYPES -------------------------------------------------------------------
|
|
|
|
typedef struct file_entry_s
|
|
{
|
|
struct file_entry_s *next;
|
|
time_t time_write;
|
|
unsigned int uncompressed_size;
|
|
unsigned int compressed_size;
|
|
unsigned int crc32;
|
|
unsigned int zip_offset;
|
|
short date, time;
|
|
short method;
|
|
char path[];
|
|
} file_entry_t;
|
|
|
|
typedef struct dir_tree_s
|
|
{
|
|
struct dir_tree_s *next;
|
|
file_entry_t *files;
|
|
size_t path_size;
|
|
char path[];
|
|
} dir_tree_t;
|
|
|
|
typedef struct file_sorted_s
|
|
{
|
|
file_entry_t *file;
|
|
char *path_in_zip;
|
|
} file_sorted_t;
|
|
|
|
typedef struct compressor_s
|
|
{
|
|
int (*compress)(Byte *out, unsigned int *outlen, const Byte *in, unsigned int inlen);
|
|
int method;
|
|
} compressor_t;
|
|
|
|
typedef unsigned int DWORD;
|
|
typedef unsigned short WORD;
|
|
typedef unsigned char BYTE;
|
|
|
|
// [BL] Solaris (well GCC on Solaris) doesn't seem to support pack(push/pop, 1) so we'll need to use use it
|
|
// on the whole file.
|
|
#pragma pack(1)
|
|
//#pragma pack(push,1)
|
|
typedef struct
|
|
{
|
|
DWORD Magic; // 0
|
|
BYTE VersionToExtract[2]; // 4
|
|
WORD Flags; // 6
|
|
WORD Method; // 8
|
|
WORD ModTime; // 10
|
|
WORD ModDate; // 12
|
|
DWORD CRC32; // 14
|
|
DWORD CompressedSize; // 18
|
|
DWORD UncompressedSize; // 22
|
|
WORD NameLength; // 26
|
|
WORD ExtraLength; // 28
|
|
} LocalFileHeader;
|
|
|
|
typedef struct
|
|
{
|
|
DWORD Magic;
|
|
BYTE VersionMadeBy[2];
|
|
BYTE VersionToExtract[2];
|
|
WORD Flags;
|
|
WORD Method;
|
|
WORD ModTime;
|
|
WORD ModDate;
|
|
DWORD CRC32;
|
|
DWORD CompressedSize;
|
|
DWORD UncompressedSize;
|
|
WORD NameLength;
|
|
WORD ExtraLength;
|
|
WORD CommentLength;
|
|
WORD StartingDiskNumber;
|
|
WORD InternalAttributes;
|
|
DWORD ExternalAttributes;
|
|
DWORD LocalHeaderOffset;
|
|
} CentralDirectoryEntry;
|
|
|
|
typedef struct
|
|
{
|
|
DWORD Magic;
|
|
WORD DiskNumber;
|
|
WORD FirstDisk;
|
|
WORD NumEntries;
|
|
WORD NumEntriesOnAllDisks;
|
|
DWORD DirectorySize;
|
|
DWORD DirectoryOffset;
|
|
WORD ZipCommentLength;
|
|
} EndOfCentralDirectory;
|
|
//#pragma pack(pop)
|
|
|
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
|
|
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
|
|
|
void print_usage(const char *cmdname);
|
|
dir_tree_t *alloc_dir_tree(const char *dir);
|
|
file_entry_t *alloc_file_entry(const char *prefix, const char *path, time_t last_written);
|
|
void free_dir_tree(dir_tree_t *tree);
|
|
void free_dir_trees(dir_tree_t *tree);
|
|
#ifdef _WIN32
|
|
void recurse_dir(dir_tree_t *tree, const char *dirpath);
|
|
dir_tree_t *add_dir(const char *dirpath);
|
|
#endif
|
|
dir_tree_t *add_dirs(char **argv);
|
|
int count_files(dir_tree_t *trees);
|
|
int __cdecl sort_cmp(const void *a, const void *b);
|
|
file_sorted_t *sort_files(dir_tree_t *trees, int num_files);
|
|
void write_zip(const char *zipname, dir_tree_t *trees, int update);
|
|
int append_to_zip(FILE *zip_file, file_sorted_t *file, FILE *ozip, BYTE *odir);
|
|
int write_central_dir(FILE *zip, file_sorted_t *file);
|
|
void time_to_dos(struct tm *time, short *dosdate, short *dostime);
|
|
int method_to_version(int method);
|
|
const char *method_name(int method);
|
|
int compress_lzma(Byte *out, unsigned int *outlen, const Byte *in, unsigned int inlen);
|
|
int compress_bzip2(Byte *out, unsigned int *outlen, const Byte *in, unsigned int inlen);
|
|
int compress_ppmd(Byte *out, unsigned int *outlen, const Byte *in, unsigned int inlen);
|
|
int compress_deflate(Byte *out, unsigned int *outlen, const Byte *in, unsigned int inlen);
|
|
BYTE *find_central_dir(FILE *fin);
|
|
CentralDirectoryEntry *find_file_in_zip(BYTE *dir, const char *path, unsigned int len, unsigned int crc, short date, short time);
|
|
int copy_zip_file(FILE *zip, file_entry_t *file, FILE *ozip, CentralDirectoryEntry *dirent);
|
|
|
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
|
|
|
static void *SzAlloc(void *p, size_t size) { p = p; return malloc(size); }
|
|
static void SzFree(void *p, void *address) { p = p; free(address); }
|
|
|
|
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
|
|
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
|
|
|
int DeflateOnly;
|
|
int UpdateCount;
|
|
int Quiet;
|
|
|
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
|
|
|
static int no_mem;
|
|
|
|
static ISzAlloc Alloc = { SzAlloc, SzFree };
|
|
static compressor_t Compressors[] =
|
|
{
|
|
{ compress_lzma, METHOD_LZMA },
|
|
{ compress_bzip2, METHOD_BZIP2 },
|
|
#ifdef PPMD
|
|
{ compress_ppmd, METHOD_PPMD },
|
|
#endif
|
|
{ compress_deflate, METHOD_DEFLATE },
|
|
{ NULL, 0 }
|
|
};
|
|
|
|
// CODE --------------------------------------------------------------------
|
|
|
|
//==========================================================================
|
|
//
|
|
// print_usage
|
|
//
|
|
//==========================================================================
|
|
|
|
void print_usage(const char *cmdname)
|
|
{
|
|
#ifdef _WIN32
|
|
const char *rchar = strrchr(cmdname, '\\');
|
|
if (rchar != NULL)
|
|
{
|
|
cmdname = rchar+1;
|
|
}
|
|
#endif
|
|
fprintf(stderr, "Usage: %s [options] <zip file> <directory> ...\n"
|
|
"Options: -d Use deflate compression only\n"
|
|
" -f Force creation of archive\n"
|
|
" -u Only update changed files\n"
|
|
" -q Do not list files\n", cmdname);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// alloc_dir_tree
|
|
//
|
|
//==========================================================================
|
|
|
|
dir_tree_t *alloc_dir_tree(const char *dir)
|
|
{
|
|
dir_tree_t *tree;
|
|
size_t dirlen;
|
|
|
|
dirlen = strlen(dir);
|
|
tree = malloc(sizeof(dir_tree_t) + dirlen + 2);
|
|
if (tree != NULL)
|
|
{
|
|
strcpy(tree->path, dir);
|
|
tree->path_size = dirlen;
|
|
if (dir[dirlen - 1] != '/')
|
|
{
|
|
tree->path_size++;
|
|
tree->path[dirlen] = '/';
|
|
tree->path[dirlen + 1] = '\0';
|
|
}
|
|
tree->files = NULL;
|
|
tree->next = NULL;
|
|
}
|
|
return tree;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// alloc_file_entry
|
|
//
|
|
//==========================================================================
|
|
|
|
file_entry_t *alloc_file_entry(const char *prefix, const char *path, time_t last_written)
|
|
{
|
|
file_entry_t *entry;
|
|
|
|
entry = malloc(sizeof(file_entry_t) + strlen(prefix) + strlen(path) + 1);
|
|
if (entry != NULL)
|
|
{
|
|
strcpy(entry->path, prefix);
|
|
strcat(entry->path, path);
|
|
entry->next = NULL;
|
|
entry->time_write = last_written;
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// free_dir_tree
|
|
//
|
|
//==========================================================================
|
|
|
|
void free_dir_tree(dir_tree_t *tree)
|
|
{
|
|
file_entry_t *entry, *next;
|
|
|
|
if (tree != NULL)
|
|
{
|
|
for (entry = tree->files; entry != NULL; entry = next)
|
|
{
|
|
next = entry->next;
|
|
free(entry);
|
|
}
|
|
free(tree);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// free_dir_trees
|
|
//
|
|
//==========================================================================
|
|
|
|
void free_dir_trees(dir_tree_t *tree)
|
|
{
|
|
dir_tree_t *next;
|
|
|
|
for (; tree != NULL; tree = next)
|
|
{
|
|
next = tree->next;
|
|
free_dir_tree(tree);
|
|
}
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
|
|
//==========================================================================
|
|
//
|
|
// recurse_dir
|
|
//
|
|
//==========================================================================
|
|
|
|
void recurse_dir(dir_tree_t *tree, const char *dirpath)
|
|
{
|
|
struct _finddata_t fileinfo;
|
|
intptr_t handle;
|
|
char *dirmatch;
|
|
|
|
dirmatch = malloc(strlen(dirpath) + 2);
|
|
if (dirmatch == NULL)
|
|
{
|
|
no_mem = 1;
|
|
return;
|
|
}
|
|
strcpy(dirmatch, dirpath);
|
|
strcat(dirmatch, "*");
|
|
if ((handle = _findfirst(dirmatch, &fileinfo)) == -1)
|
|
{
|
|
fprintf(stderr, "Could not scan '%s': %s\n", dirpath, strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
if (fileinfo.attrib & _A_HIDDEN)
|
|
{
|
|
// Skip hidden files and directories. (Prevents SVN bookkeeping
|
|
// info from being included.)
|
|
continue;
|
|
}
|
|
if (fileinfo.attrib & _A_SUBDIR)
|
|
{
|
|
char *newdir;
|
|
|
|
if (fileinfo.name[0] == '.' &&
|
|
(fileinfo.name[1] == '\0' ||
|
|
(fileinfo.name[1] == '.' && fileinfo.name[2] == '\0')))
|
|
{
|
|
// Do not record . and .. directories.
|
|
continue;
|
|
}
|
|
newdir = malloc(strlen(dirpath) + strlen(fileinfo.name) + 2);
|
|
strcpy(newdir, dirpath);
|
|
strcat(newdir, fileinfo.name);
|
|
strcat(newdir, "/");
|
|
recurse_dir(tree, newdir);
|
|
}
|
|
else
|
|
{
|
|
file_entry_t *entry;
|
|
|
|
if (strstr(fileinfo.name, ".orig"))
|
|
{
|
|
// .orig files are left behind by patch.exe and should never be
|
|
// added to zdoom.pk3
|
|
continue;
|
|
}
|
|
|
|
entry = alloc_file_entry(dirpath, fileinfo.name, fileinfo.time_write);
|
|
if (entry == NULL)
|
|
{
|
|
no_mem = 1;
|
|
break;
|
|
}
|
|
entry->next = tree->files;
|
|
tree->files = entry;
|
|
}
|
|
} while (_findnext(handle, &fileinfo) == 0);
|
|
_findclose(handle);
|
|
}
|
|
free(dirmatch);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// add_dir
|
|
//
|
|
//==========================================================================
|
|
|
|
dir_tree_t *add_dir(const char *dirpath)
|
|
{
|
|
dir_tree_t *tree = alloc_dir_tree(dirpath);
|
|
|
|
if (tree != NULL)
|
|
{
|
|
recurse_dir(tree, tree->path);
|
|
}
|
|
return tree;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// add_dirs
|
|
// Windows version
|
|
//
|
|
// Given NULL-terminated array of directory paths, create trees for them.
|
|
//
|
|
//==========================================================================
|
|
|
|
dir_tree_t *add_dirs(char **argv)
|
|
{
|
|
dir_tree_t *tree, *trees = NULL;
|
|
char *s;
|
|
|
|
while (*argv != NULL)
|
|
{
|
|
for (s = *argv; *s != '\0'; ++s)
|
|
{
|
|
if (*s == '\\')
|
|
{
|
|
*s = '/';
|
|
}
|
|
}
|
|
tree = add_dir(*argv);
|
|
tree->next = trees;
|
|
trees = tree;
|
|
if (no_mem)
|
|
{
|
|
break;
|
|
}
|
|
argv++;
|
|
}
|
|
return trees;
|
|
}
|
|
|
|
#elif defined(__sun)
|
|
|
|
//==========================================================================
|
|
//
|
|
// add_dirs
|
|
// Solaris version
|
|
//
|
|
// Given NULL-terminated array of directory paths, create trees for them.
|
|
//
|
|
//==========================================================================
|
|
|
|
void add_dir(dir_tree_t *tree, char* dirpath)
|
|
{
|
|
DIR *directory = opendir(dirpath);
|
|
if(directory == NULL)
|
|
return;
|
|
|
|
struct dirent *file;
|
|
while((file = readdir(directory)) != NULL)
|
|
{
|
|
if(file->d_name[0] == '.') //File is hidden or ./.. directory so ignore it.
|
|
continue;
|
|
|
|
int isDirectory = 0;
|
|
int time = 0;
|
|
|
|
char* fullFileName = malloc(strlen(dirpath) + strlen(file->d_name) + 1);
|
|
strcpy(fullFileName, dirpath);
|
|
strcat(fullFileName, file->d_name);
|
|
|
|
struct stat *fileStat;
|
|
fileStat = malloc(sizeof(struct stat));
|
|
stat(fullFileName, fileStat);
|
|
isDirectory = S_ISDIR(fileStat->st_mode);
|
|
time = fileStat->st_mtime;
|
|
free(stat);
|
|
|
|
free(fullFileName);
|
|
|
|
if(isDirectory)
|
|
{
|
|
char* newdir;
|
|
newdir = malloc(strlen(dirpath) + strlen(file->d_name) + 2);
|
|
strcpy(newdir, dirpath);
|
|
strcat(newdir, file->d_name);
|
|
strcat(newdir, "/");
|
|
add_dir(tree, newdir);
|
|
free(newdir);
|
|
continue;
|
|
}
|
|
|
|
file_entry_t *entry;
|
|
entry = alloc_file_entry(dirpath, file->d_name, time);
|
|
if (entry == NULL)
|
|
{
|
|
//no_mem = 1;
|
|
break;
|
|
}
|
|
entry->next = tree->files;
|
|
tree->files = entry;
|
|
}
|
|
|
|
closedir(directory);
|
|
}
|
|
|
|
dir_tree_t *add_dirs(char **argv)
|
|
{
|
|
dir_tree_t *tree, *trees = NULL;
|
|
|
|
int i = 0;
|
|
while(argv[i] != NULL)
|
|
{
|
|
tree = alloc_dir_tree(argv[i]);
|
|
tree->next = trees;
|
|
trees = tree;
|
|
|
|
if(tree != NULL)
|
|
{
|
|
char* dirpath = malloc(sizeof(argv[i]) + 2);
|
|
strcpy(dirpath, argv[i]);
|
|
if(dirpath[strlen(dirpath)] != '/')
|
|
strcat(dirpath, "/");
|
|
add_dir(tree, dirpath);
|
|
free(dirpath);
|
|
}
|
|
|
|
i++;
|
|
}
|
|
return trees;
|
|
}
|
|
|
|
#else
|
|
|
|
//==========================================================================
|
|
//
|
|
// add_dirs
|
|
// 4.4BSD version
|
|
//
|
|
// Given NULL-terminated array of directory paths, create trees for them.
|
|
//
|
|
//==========================================================================
|
|
|
|
dir_tree_t *add_dirs(char **argv)
|
|
{
|
|
FTS *fts;
|
|
FTSENT *ent;
|
|
dir_tree_t *tree, *trees = NULL;
|
|
file_entry_t *file;
|
|
|
|
fts = fts_open(argv, FTS_LOGICAL, NULL);
|
|
if (fts == NULL)
|
|
{
|
|
fprintf(stderr, "Failed to start directory traversal: %s\n", strerror(errno));
|
|
return NULL;
|
|
}
|
|
while ((ent = fts_read(fts)) != NULL)
|
|
{
|
|
if (ent->fts_info == FTS_D && ent->fts_name[0] == '.')
|
|
{
|
|
// Skip hidden directories. (Prevents SVN bookkeeping
|
|
// info from being included.)
|
|
fts_set(fts, ent, FTS_SKIP);
|
|
}
|
|
if (ent->fts_info == FTS_D && ent->fts_level == 0)
|
|
{
|
|
tree = alloc_dir_tree(ent->fts_path);
|
|
if (tree == NULL)
|
|
{
|
|
no_mem = 1;
|
|
break;
|
|
}
|
|
tree->next = trees;
|
|
trees = tree;
|
|
}
|
|
if (ent->fts_info != FTS_F)
|
|
{
|
|
// We're only interested in remembering files.
|
|
continue;
|
|
}
|
|
file = alloc_file_entry("", ent->fts_path, ent->fts_statp->st_mtime);
|
|
if (file == NULL)
|
|
{
|
|
no_mem = 1;
|
|
break;
|
|
}
|
|
file->next = tree->files;
|
|
tree->files = file;
|
|
}
|
|
fts_close(fts);
|
|
return trees;
|
|
}
|
|
#endif
|
|
|
|
//==========================================================================
|
|
//
|
|
// count_files
|
|
//
|
|
//==========================================================================
|
|
|
|
int count_files(dir_tree_t *trees)
|
|
{
|
|
dir_tree_t *tree;
|
|
file_entry_t *file;
|
|
int count;
|
|
|
|
for (count = 0, tree = trees; tree != NULL; tree = tree->next)
|
|
{
|
|
for (file = tree->files; file != NULL; file = file->next)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// sort_cmp
|
|
//
|
|
// Arbitrarily-selected sorting for the zip files: Files in the root
|
|
// directory sort after files in subdirectories. Otherwise, everything
|
|
// sorts by name.
|
|
//
|
|
//==========================================================================
|
|
|
|
int __cdecl sort_cmp(const void *a, const void *b)
|
|
{
|
|
const file_sorted_t *sort1 = (const file_sorted_t *)a;
|
|
const file_sorted_t *sort2 = (const file_sorted_t *)b;
|
|
int in_dir1, in_dir2;
|
|
|
|
in_dir1 = (strchr(sort1->path_in_zip, '/') != NULL);
|
|
in_dir2 = (strchr(sort2->path_in_zip, '/') != NULL);
|
|
if (in_dir1 == 1 && in_dir2 == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
if (in_dir1 == 0 && in_dir2 == 1)
|
|
{
|
|
return 1;
|
|
}
|
|
return strcmp(((const file_sorted_t *)a)->path_in_zip,
|
|
((const file_sorted_t *)b)->path_in_zip);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// sort_files
|
|
//
|
|
//==========================================================================
|
|
|
|
file_sorted_t *sort_files(dir_tree_t *trees, int num_files)
|
|
{
|
|
file_sorted_t *sorter;
|
|
dir_tree_t *tree;
|
|
file_entry_t *file;
|
|
int i;
|
|
|
|
sorter = malloc(sizeof(*sorter) * num_files);
|
|
if (sorter != NULL)
|
|
{
|
|
for (i = 0, tree = trees; tree != NULL; tree = tree->next)
|
|
{
|
|
for (file = tree->files; file != NULL; file = file->next)
|
|
{
|
|
sorter[i].file = file;
|
|
sorter[i].path_in_zip = file->path + tree->path_size;
|
|
i++;
|
|
}
|
|
}
|
|
qsort(sorter, num_files, sizeof(*sorter), sort_cmp);
|
|
}
|
|
return sorter;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// write_zip
|
|
//
|
|
//==========================================================================
|
|
|
|
void write_zip(const char *zipname, dir_tree_t *trees, int update)
|
|
{
|
|
#ifdef _WIN32
|
|
char tempname[_MAX_PATH];
|
|
#else
|
|
char tempname[PATH_MAX];
|
|
#endif
|
|
EndOfCentralDirectory dirend;
|
|
int i, num_files;
|
|
file_sorted_t *sorted;
|
|
FILE *zip, *ozip = NULL;
|
|
void *central_dir = NULL;
|
|
|
|
num_files = count_files(trees);
|
|
sorted = sort_files(trees, num_files);
|
|
if (sorted == NULL)
|
|
{
|
|
no_mem = 1;
|
|
return;
|
|
}
|
|
if (update)
|
|
{
|
|
sprintf(tempname, "%s.temp", zipname);
|
|
ozip = fopen(zipname, "rb");
|
|
if (ozip == NULL)
|
|
{
|
|
fprintf(stderr, "Could not open %s for updating: %s\n", zipname, strerror(errno));
|
|
update = 0;
|
|
}
|
|
else
|
|
{
|
|
central_dir = find_central_dir(ozip);
|
|
if (central_dir == NULL)
|
|
{
|
|
fprintf(stderr, "Could not read central directory from %s. (Is it a zipfile?)\n", zipname);
|
|
update = 0;
|
|
}
|
|
}
|
|
if (!update)
|
|
{
|
|
fprintf(stderr, "Will proceed as if -u had not been specified.\n");
|
|
}
|
|
}
|
|
if (update)
|
|
{
|
|
zip = fopen(tempname, "wb");
|
|
}
|
|
else
|
|
{
|
|
zip = fopen(zipname, "wb");
|
|
}
|
|
if (zip == NULL)
|
|
{
|
|
fprintf(stderr, "Could not open %s: %s\n", zipname, strerror(errno));
|
|
}
|
|
else
|
|
{
|
|
// Write each file.
|
|
for (i = 0; i < num_files; ++i)
|
|
{
|
|
if (append_to_zip(zip, sorted + i, ozip, central_dir))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (i == num_files)
|
|
{
|
|
// Write central directory.
|
|
dirend.DirectoryOffset = ftell(zip);
|
|
for (i = 0; i < num_files; ++i)
|
|
{
|
|
write_central_dir(zip, sorted + i);
|
|
}
|
|
// Write the directory terminator.
|
|
dirend.Magic = ZIP_ENDOFDIR;
|
|
dirend.DiskNumber = 0;
|
|
dirend.FirstDisk = 0;
|
|
dirend.NumEntriesOnAllDisks = dirend.NumEntries = LittleShort(i);
|
|
dirend.DirectorySize = LittleLong(ftell(zip) - dirend.DirectoryOffset);
|
|
dirend.DirectoryOffset = LittleLong(dirend.DirectoryOffset);
|
|
dirend.ZipCommentLength = 0;
|
|
if (fwrite(&dirend, sizeof(dirend), 1, zip) != 1)
|
|
{
|
|
fprintf(stderr, "Failed writing zip directory terminator: %s\n", strerror(errno));
|
|
}
|
|
printf("%s contains %d files (updated %d)\n", zipname, num_files, UpdateCount);
|
|
fclose(zip);
|
|
|
|
if (ozip != NULL)
|
|
{
|
|
// Delete original, and rename temp to take its place
|
|
fclose(ozip);
|
|
ozip = NULL;
|
|
if (remove(zipname))
|
|
{
|
|
fprintf(stderr, "Could not delete old zip: %s\nUpdated zip can be found at %s\n",
|
|
strerror(errno), tempname);
|
|
}
|
|
else if (rename(tempname, zipname))
|
|
{
|
|
fprintf(stderr, "Could not rename %s to %s: %s\n",
|
|
tempname, zipname, strerror(errno));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
free(sorted);
|
|
if (ozip != NULL)
|
|
{
|
|
fclose(ozip);
|
|
}
|
|
if (central_dir != NULL)
|
|
{
|
|
free(central_dir);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// append_to_zip
|
|
//
|
|
// Write a given file to the zipFile.
|
|
//
|
|
// zipfile: zip object to be written to
|
|
// file: file to read data from
|
|
//
|
|
// returns: 0 = success, 1 = error
|
|
//
|
|
//==========================================================================
|
|
|
|
int append_to_zip(FILE *zip_file, file_sorted_t *filep, FILE *ozip, BYTE *odir)
|
|
{
|
|
LocalFileHeader local;
|
|
uLong crc;
|
|
file_entry_t *file;
|
|
char *readbuf;
|
|
char *compbuf[2];
|
|
unsigned int comp_len[2];
|
|
int offset[2];
|
|
int method[2];
|
|
int best;
|
|
int slot;
|
|
FILE *lumpfile;
|
|
unsigned int readlen;
|
|
unsigned int len;
|
|
int i;
|
|
struct tm *ltime;
|
|
|
|
file = filep->file;
|
|
|
|
// try to determine local time
|
|
ltime = localtime(&file->time_write);
|
|
time_to_dos(ltime, &file->date, &file->time);
|
|
|
|
// lumpfile = source file
|
|
lumpfile = fopen(file->path, "rb");
|
|
if (lumpfile == NULL)
|
|
{
|
|
fprintf(stderr, "Could not open %s: %s\n", file->path, strerror(errno));
|
|
return 1;
|
|
}
|
|
// len = source size
|
|
fseek (lumpfile, 0, SEEK_END);
|
|
len = ftell(lumpfile);
|
|
fseek (lumpfile, 0, SEEK_SET);
|
|
|
|
// allocate a buffer for the whole source file
|
|
readbuf = malloc(len);
|
|
if (readbuf == NULL)
|
|
{
|
|
fclose(lumpfile);
|
|
fprintf(stderr, "Could not allocate %u bytes\n", (int)len);
|
|
return 1;
|
|
}
|
|
// read the whole source file into buffer
|
|
readlen = (unsigned int)fread(readbuf, 1, len, lumpfile);
|
|
fclose(lumpfile);
|
|
|
|
// if read less bytes than expected,
|
|
if (readlen != len)
|
|
{
|
|
// diagnose and return error
|
|
free(readbuf);
|
|
fprintf(stderr, "Unable to read %s\n", file->path);
|
|
return 1;
|
|
}
|
|
// file loaded
|
|
|
|
file->uncompressed_size = len;
|
|
file->compressed_size = len;
|
|
file->method = METHOD_STORED;
|
|
|
|
// Calculate CRC32 for file.
|
|
crc = crc32(0, NULL, 0);
|
|
crc = crc32(crc, readbuf, (uInt)len);
|
|
file->crc32 = LittleLong(crc);
|
|
|
|
// Can we save time and just copy the file from the old zip?
|
|
if (odir != NULL && ozip != NULL)
|
|
{
|
|
CentralDirectoryEntry *dirent;
|
|
|
|
dirent = find_file_in_zip(odir, filep->path_in_zip, len, crc, file->date, file->time);
|
|
if (dirent != NULL)
|
|
{
|
|
i = copy_zip_file(zip_file, file, ozip, dirent);
|
|
if (i > 0)
|
|
{
|
|
free(readbuf);
|
|
return 0;
|
|
}
|
|
if (i < 0)
|
|
{
|
|
free(readbuf);
|
|
fprintf(stderr, "Unable to write %s to zip\n", file->path);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!Quiet)
|
|
{
|
|
if (ozip != NULL)
|
|
{
|
|
printf("Updating %-40s", filep->path_in_zip);
|
|
}
|
|
else
|
|
{
|
|
printf("Adding %-40s", filep->path_in_zip);
|
|
}
|
|
}
|
|
UpdateCount++;
|
|
|
|
// Allocate a buffer for compression, one byte less than the source buffer.
|
|
// If it doesn't fit in that space, then skip compression and store it as-is.
|
|
compbuf[0] = malloc(len - 1);
|
|
compbuf[1] = malloc(len - 1);
|
|
best = -1; // best slot
|
|
slot = 0; // slot we are compressing to now
|
|
|
|
// Find best compression method. We have two output buffers. One to hold the
|
|
// best compression method, and the other to hold the compression we are trying
|
|
// now.
|
|
for (i = 0; Compressors[i].compress != NULL; ++i)
|
|
{
|
|
if (DeflateOnly && Compressors[i].method != METHOD_DEFLATE)
|
|
{
|
|
continue;
|
|
}
|
|
comp_len[slot] = len - 1;
|
|
method[slot] = Compressors[i].method;
|
|
offset[slot] = Compressors[i].compress(compbuf[slot], &comp_len[slot], readbuf, len);
|
|
if (offset[slot] >= 0)
|
|
{
|
|
if (best < 0 || comp_len[slot] <= comp_len[best])
|
|
{
|
|
best = slot;
|
|
slot ^= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (best >= 0)
|
|
{
|
|
file->method = method[best];
|
|
file->compressed_size = comp_len[best];
|
|
}
|
|
// printf("%s -> method %d -> slot %d\n", filep->path_in_zip, file->method, best);
|
|
|
|
// Fill in local directory header.
|
|
local.Magic = ZIP_LOCALFILE;
|
|
local.VersionToExtract[0] = method_to_version(file->method);
|
|
local.VersionToExtract[1] = 0;
|
|
local.Flags = file->method == METHOD_DEFLATE ? LittleShort(2) : 0;
|
|
local.Method = LittleShort(file->method);
|
|
local.ModTime = file->time;
|
|
local.ModDate = file->date;
|
|
local.CRC32 = file->crc32;
|
|
local.UncompressedSize = LittleLong(file->uncompressed_size);
|
|
local.CompressedSize = LittleLong(file->compressed_size);
|
|
local.NameLength = LittleShort((unsigned short)strlen(filep->path_in_zip));
|
|
local.ExtraLength = 0;
|
|
|
|
file->zip_offset = ftell(zip_file);
|
|
|
|
// Write out the header, file name, and file data.
|
|
if (fwrite(&local, sizeof(local), 1, zip_file) != 1 ||
|
|
fwrite(filep->path_in_zip, strlen(filep->path_in_zip), 1, zip_file) != 1 ||
|
|
(file->method ? fwrite(compbuf[best] + offset[best], 1, comp_len[best], zip_file) != comp_len[best] :
|
|
fwrite(readbuf, 1, len, zip_file) != len))
|
|
{
|
|
if (!Quiet)
|
|
{
|
|
printf("\n");
|
|
}
|
|
fprintf(stderr, "Unable to write %s to zip\n", file->path);
|
|
free(readbuf);
|
|
if (compbuf[0] != NULL)
|
|
{
|
|
free(compbuf[0]);
|
|
}
|
|
if (compbuf[1] != NULL)
|
|
{
|
|
free(compbuf[1]);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
// all done
|
|
free(readbuf);
|
|
if (compbuf[0] != NULL)
|
|
{
|
|
free(compbuf[0]);
|
|
}
|
|
if (compbuf[1] != NULL)
|
|
{
|
|
free(compbuf[1]);
|
|
}
|
|
if (!Quiet)
|
|
{
|
|
printf("%5.1f%% [%6u/%6u] %s\n", 100.0 - 100.0 * file->compressed_size / file->uncompressed_size,
|
|
file->compressed_size, file->uncompressed_size, method_name(file->method));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// write_central_dir
|
|
//
|
|
// Writes the central directory entry for a file.
|
|
//
|
|
//==========================================================================
|
|
|
|
int write_central_dir(FILE *zip, file_sorted_t *filep)
|
|
{
|
|
CentralDirectoryEntry dir;
|
|
file_entry_t *file;
|
|
|
|
file = filep->file;
|
|
dir.Magic = ZIP_CENTRALFILE;
|
|
dir.VersionMadeBy[0] = 20;
|
|
dir.VersionMadeBy[1] = 0;
|
|
dir.VersionToExtract[0] = method_to_version(file->method);
|
|
dir.VersionToExtract[1] = 0;
|
|
dir.Flags = file->method == METHOD_DEFLATE ? LittleShort(2) : 0;
|
|
dir.Method = LittleShort(file->method);
|
|
dir.ModTime = file->time;
|
|
dir.ModDate = file->date;
|
|
dir.CRC32 = file->crc32;
|
|
dir.CompressedSize = LittleLong(file->compressed_size);
|
|
dir.UncompressedSize = LittleLong(file->uncompressed_size);
|
|
dir.NameLength = LittleShort((unsigned short)strlen(filep->path_in_zip));
|
|
dir.ExtraLength = 0;
|
|
dir.CommentLength = 0;
|
|
dir.StartingDiskNumber = 0;
|
|
dir.InternalAttributes = 0;
|
|
dir.ExternalAttributes = 0;
|
|
dir.LocalHeaderOffset = LittleLong(file->zip_offset);
|
|
|
|
if (fwrite(&dir, sizeof(dir), 1, zip) != 1 ||
|
|
fwrite(filep->path_in_zip, strlen(filep->path_in_zip), 1, zip) != 1)
|
|
{
|
|
fprintf(stderr, "Error writing central directory header for %s: %s\n", file->path, strerror(errno));
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// time_to_dos
|
|
//
|
|
// Converts time from struct tm to the DOS format used by zip files.
|
|
//
|
|
//==========================================================================
|
|
|
|
void time_to_dos(struct tm *time, short *dosdate, short *dostime)
|
|
{
|
|
if (time == NULL || time->tm_year < 80)
|
|
{
|
|
*dosdate = *dostime = 0;
|
|
}
|
|
else
|
|
{
|
|
*dosdate = LittleShort((time->tm_year - 80) * 512 + (time->tm_mon + 1) * 32 + time->tm_mday);
|
|
*dostime = LittleShort(time->tm_hour * 2048 + time->tm_min * 32 + time->tm_sec / 2);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// method_to_version
|
|
//
|
|
// Given a compression method, returns the version of the ZIP appnote
|
|
// required to decompress it, for filling in the directory information.
|
|
//
|
|
//==========================================================================
|
|
|
|
int method_to_version(int method)
|
|
{
|
|
// Apparently, real-world programs get confused by setting the version
|
|
// to extract field to something other than 2.0.
|
|
#if 0
|
|
if (method == METHOD_LZMA || method == METHOD_PPMD)
|
|
return 63;
|
|
if (method == METHOD_BZIP2)
|
|
return 46;
|
|
#endif
|
|
// Default anything else to PKZIP 2.0.
|
|
return 20;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// method_name
|
|
//
|
|
// Returns the name of the compression method. If the method is unknown,
|
|
// this will point to a static buffer.
|
|
//
|
|
//==========================================================================
|
|
|
|
const char *method_name(int method)
|
|
{
|
|
static char unkn[16];
|
|
|
|
if (method == METHOD_STORED)
|
|
{
|
|
return "Stored";
|
|
}
|
|
if (method == METHOD_DEFLATE)
|
|
{
|
|
return "Deflate";
|
|
}
|
|
if (method == METHOD_LZMA)
|
|
{
|
|
return "LZMA";
|
|
}
|
|
if (method == METHOD_PPMD)
|
|
{
|
|
return "PPMd";
|
|
}
|
|
if (method == METHOD_BZIP2)
|
|
{
|
|
return "BZip2";
|
|
}
|
|
sprintf(unkn, "Unk:%03d", method);
|
|
return unkn;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// compress_lzma
|
|
//
|
|
// Returns non-negative offset to start of data stream on success. Barring
|
|
// any strange changes to the LZMA library in the future, success should
|
|
// always return 0.
|
|
//
|
|
//==========================================================================
|
|
|
|
int compress_lzma(Byte *out, unsigned int *outlen, const Byte *in, unsigned int inlen)
|
|
{
|
|
CLzmaEncProps lzma_props;
|
|
size_t props_size;
|
|
size_t comp_len;
|
|
int offset;
|
|
|
|
if (*outlen < 1 + 4 + LZMA_PROPS_SIZE)
|
|
{
|
|
// Not enough room for LZMA properties header + compressed data.
|
|
return -1;
|
|
}
|
|
if (out == NULL || in == NULL || inlen == 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
LzmaEncProps_Init(&lzma_props);
|
|
// lzma_props.level = 9;
|
|
props_size = LZMA_PROPS_SIZE;
|
|
comp_len = *outlen - 4 - LZMA_PROPS_SIZE;
|
|
|
|
if (SZ_OK != LzmaEncode(out + 4 + LZMA_PROPS_SIZE, &comp_len, in, inlen, &lzma_props,
|
|
out + 4, &props_size, 0, NULL, &Alloc, &Alloc))
|
|
{
|
|
return -1;
|
|
}
|
|
// Fill in LZMA properties header
|
|
offset = 0;
|
|
if (props_size != LZMA_PROPS_SIZE)
|
|
{
|
|
// Move LZMA properties to be adjacent to the compressed data, because for
|
|
// some reaseon the library didn't use all the space provided.
|
|
int i;
|
|
|
|
offset = (int)(LZMA_PROPS_SIZE - props_size);
|
|
for (i = 4 + LZMA_PROPS_SIZE - 1; i > 4 + offset; --i)
|
|
{
|
|
out[i] = out[i - offset];
|
|
}
|
|
}
|
|
out[offset] = MY_VER_MAJOR;
|
|
out[offset+1] = MY_VER_MINOR;
|
|
out[offset+2] = (Byte)props_size;
|
|
out[offset+3] = 0;
|
|
// Add header length to outlen
|
|
*outlen = (unsigned int)(comp_len + 4 + props_size);
|
|
return offset;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// compress_bzip2
|
|
//
|
|
// Returns 0 on success, negative on failure.
|
|
//
|
|
//==========================================================================
|
|
|
|
int compress_bzip2(Byte *out, unsigned int *outlen, const Byte *in, unsigned int inlen)
|
|
{
|
|
if (BZ_OK == BZ2_bzBuffToBuffCompress(out, outlen, in, inlen, 9, 0, 0))
|
|
{
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
#ifdef PPMD
|
|
//==========================================================================
|
|
//
|
|
// compress_ppmd
|
|
//
|
|
// Returns 0 on success, negative on failure.
|
|
//
|
|
// Big problem here: The zip format only allows for PPMd I rev. 1. This
|
|
// version of the code is incompatible with 64-bit processors. PPMd J rev. 1
|
|
// corrects this and also compresses slightly better, but it also changes
|
|
// the data format and is incompatible with I rev. 1. The PPMd source code
|
|
// is a tangled mass that I cannot comprehend, so fixing I rev. 1 to work
|
|
// on 64-bit processors is well beyond my means. Hence, I cannot currently
|
|
// support PPMd in zips. If the zip spec gets updated to allow J rev. 1,
|
|
// then I can, but not any sooner.
|
|
//
|
|
//==========================================================================
|
|
|
|
int compress_ppmd(Byte *out, unsigned int *outlen, const Byte *in, unsigned int inlen)
|
|
{
|
|
int maxorder = 8;
|
|
int sasize = 8;
|
|
int cutoff = 0;
|
|
|
|
_PPMD_FILE ppsin = { (char *)in, inlen, 0 };
|
|
_PPMD_FILE ppsout = { out + 2, *outlen - 2, 0};
|
|
|
|
if (!PPMd_StartSubAllocator(sasize))
|
|
{
|
|
return -1;
|
|
}
|
|
PPMd_EncodeFile(&ppsout, &ppsin, maxorder, cutoff);
|
|
PPMd_StopSubAllocator();
|
|
if (ppsout.eof)
|
|
{
|
|
return -1;
|
|
}
|
|
if (!ppsin.eof)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
*(short *)out = LittleShort((maxorder - 1) + ((sasize - 1) << 4) + (cutoff << 12));
|
|
*outlen = *outlen - ppsout.buffersize;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
//==========================================================================
|
|
//
|
|
// compress_deflate
|
|
//
|
|
// Returns 0 on success, negative on failure.
|
|
//
|
|
//==========================================================================
|
|
|
|
int compress_deflate(Byte *out, unsigned int *outlen, const Byte *in, unsigned int inlen)
|
|
{
|
|
z_stream stream;
|
|
int err;
|
|
|
|
stream.next_in = (Bytef *)in;
|
|
stream.avail_in = inlen;
|
|
stream.next_out = out;
|
|
stream.avail_out = *outlen;
|
|
stream.zalloc = (alloc_func)0;
|
|
stream.zfree = (free_func)0;
|
|
stream.opaque = (voidpf)0;
|
|
|
|
err = deflateInit2(&stream, 9, Z_DEFLATED, -15, 9, Z_DEFAULT_STRATEGY);
|
|
if (err != Z_OK) return -1;
|
|
|
|
err = deflate(&stream, Z_FINISH);
|
|
if (err != Z_STREAM_END) {
|
|
deflateEnd(&stream);
|
|
return -1;
|
|
}
|
|
*outlen = stream.total_out;
|
|
|
|
err = deflateEnd(&stream);
|
|
return err == Z_OK ? 0 : -1;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// find_central_dir
|
|
//
|
|
// Finds and loads the central directory records in the file.
|
|
// Taken from Quake3 source and modified.
|
|
//
|
|
//==========================================================================
|
|
|
|
BYTE *find_central_dir(FILE *fin)
|
|
{
|
|
unsigned char buf[BUFREADCOMMENT + 4];
|
|
EndOfCentralDirectory eod;
|
|
BYTE *dir;
|
|
long file_size;
|
|
long back_read;
|
|
long max_back; // maximum size of global comment
|
|
long pos_found = 0;
|
|
|
|
fseek(fin, 0, SEEK_END);
|
|
|
|
file_size = ftell(fin);
|
|
max_back = 0xffff > file_size ? file_size : 0xffff;
|
|
|
|
back_read = 4;
|
|
while (back_read < max_back)
|
|
{
|
|
DWORD read_size, read_pos;
|
|
int i;
|
|
if (back_read + BUFREADCOMMENT > max_back)
|
|
back_read = max_back;
|
|
else
|
|
back_read += BUFREADCOMMENT;
|
|
read_pos = file_size - back_read;
|
|
|
|
read_size = (BUFREADCOMMENT + 4) < (file_size - read_pos) ?
|
|
(BUFREADCOMMENT + 4) : (file_size - read_pos);
|
|
|
|
if (fseek(fin, read_pos, SEEK_SET) != 0)
|
|
return NULL;
|
|
|
|
if (fread(buf, 1, read_size, fin) != read_size)
|
|
return NULL;
|
|
|
|
for (i = (int)read_size - 3; (i--) > 0;)
|
|
{
|
|
if (buf[i] == 'P' && buf[i+1] == 'K' && buf[i+2] == 5 && buf[i+3] == 6)
|
|
{
|
|
pos_found = read_pos + i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (pos_found != 0)
|
|
break;
|
|
}
|
|
if (pos_found == 0 ||
|
|
fseek(fin, pos_found, SEEK_SET) != 0 ||
|
|
fread(&eod, sizeof(eod), 1, fin) != 1 ||
|
|
fseek(fin, LittleShort(eod.DirectoryOffset), SEEK_SET) != 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
dir = malloc(LittleLong(eod.DirectorySize) + 4);
|
|
if (dir == NULL)
|
|
{
|
|
no_mem = 1;
|
|
return NULL;
|
|
}
|
|
if (fread(dir, 1, LittleLong(eod.DirectorySize), fin) != LittleLong(eod.DirectorySize))
|
|
{
|
|
free(dir);
|
|
return NULL;
|
|
}
|
|
if (*(DWORD *)dir != ZIP_CENTRALFILE)
|
|
{
|
|
free(dir);
|
|
return NULL;
|
|
}
|
|
*(DWORD *)(dir + LittleLong(eod.DirectorySize)) = ZIP_ENDOFDIR;
|
|
return dir;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// find_file_in_zip
|
|
//
|
|
// Returns a pointer to a central directory entry to a file, if it was
|
|
// found in the zip's directory. Data endianness is in zip order.
|
|
//
|
|
//==========================================================================
|
|
|
|
CentralDirectoryEntry *find_file_in_zip(BYTE *dir, const char *path, unsigned int len, unsigned int crc, short date, short time)
|
|
{
|
|
int pathlen = (int)strlen(path);
|
|
CentralDirectoryEntry *ent;
|
|
int flags;
|
|
|
|
while (*(DWORD *)dir == ZIP_CENTRALFILE)
|
|
{
|
|
ent = (CentralDirectoryEntry *)dir;
|
|
if (pathlen == LittleShort(ent->NameLength) &&
|
|
strncmp((char *)(ent + 1), path, pathlen) == 0)
|
|
{
|
|
// Found something that matches by name.
|
|
break;
|
|
}
|
|
dir += sizeof(*ent) + LittleShort(ent->NameLength) + LittleShort(ent->ExtraLength) + LittleShort(ent->CommentLength);
|
|
}
|
|
if (*(DWORD *)dir != ZIP_CENTRALFILE)
|
|
{
|
|
return NULL;
|
|
}
|
|
if (crc != LittleLong(ent->CRC32))
|
|
{
|
|
return NULL;
|
|
}
|
|
if (len != LittleLong(ent->UncompressedSize))
|
|
{
|
|
return NULL;
|
|
}
|
|
// Should I check modification date and time here?
|
|
flags = LittleShort(ent->Flags);
|
|
if (flags & 1)
|
|
{ // Don't want to deal with encryption.
|
|
return NULL;
|
|
}
|
|
if (ent->ExtraLength != 0)
|
|
{ // Don't want to deal with extra data.
|
|
return NULL;
|
|
}
|
|
// Okay, looks good.
|
|
return ent;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// copy_zip_file
|
|
//
|
|
// Copies one file from ozip to zip. Returns positive on success, zero if
|
|
// the file could not be found, and negative if it failed while writing the
|
|
// file.
|
|
//
|
|
//==========================================================================
|
|
|
|
int copy_zip_file(FILE *zip, file_entry_t *file, FILE *ozip, CentralDirectoryEntry *ent)
|
|
{
|
|
LocalFileHeader lfh;
|
|
BYTE *buf;
|
|
DWORD buf_size;
|
|
|
|
if (fseek(ozip, LittleLong(ent->LocalHeaderOffset), SEEK_SET) != 0)
|
|
{
|
|
return 0;
|
|
}
|
|
if (fread(&lfh, sizeof(lfh), 1, ozip) != 1)
|
|
{
|
|
return 0;
|
|
}
|
|
// Check to make sure the local header matches the central directory.
|
|
if (lfh.Flags != ent->Flags || lfh.Method != ent->Method ||
|
|
lfh.CRC32 != ent->CRC32 || lfh.CompressedSize != ent->CompressedSize ||
|
|
lfh.UncompressedSize != ent->UncompressedSize ||
|
|
lfh.NameLength != ent->NameLength || lfh.ExtraLength != ent->ExtraLength)
|
|
{
|
|
return 0;
|
|
}
|
|
buf_size = LittleShort(lfh.NameLength) + LittleLong(lfh.CompressedSize);
|
|
buf = malloc(buf_size);
|
|
if (buf == NULL)
|
|
{
|
|
return 0;
|
|
}
|
|
if (fread(buf, 1, buf_size, ozip) != buf_size)
|
|
{
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
// Check to be sure name matches.
|
|
if (strncmp(buf, (char *)(ent + 1), LittleShort(lfh.NameLength)) != 0)
|
|
{
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
// Looks good. Let's write it in.
|
|
file->zip_offset = ftell(zip);
|
|
if (fwrite(&lfh, sizeof(lfh), 1, zip) != 1 ||
|
|
fwrite(buf, 1, buf_size, zip) != buf_size)
|
|
{
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
free(buf);
|
|
file->date = lfh.ModDate;
|
|
file->time = lfh.ModTime;
|
|
file->uncompressed_size = LittleLong(lfh.UncompressedSize);
|
|
file->compressed_size = LittleLong(lfh.CompressedSize);
|
|
file->method = LittleShort(lfh.Method);
|
|
file->crc32 = lfh.CRC32;
|
|
return 1;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// main
|
|
//
|
|
//==========================================================================
|
|
|
|
int __cdecl main (int argc, char **argv)
|
|
{
|
|
dir_tree_t *tree, *trees;
|
|
file_entry_t *file;
|
|
struct stat zipstat;
|
|
int needwrite;
|
|
int i, j, k;
|
|
int force = 0;
|
|
int update = 0;
|
|
|
|
// Find options. Options are removed from the array.
|
|
for (i = k = 1; i < argc; ++i)
|
|
{
|
|
if (argv[i][0] == '-')
|
|
{
|
|
if (argv[i][1] == '-')
|
|
{
|
|
if (argv[i][2] == '\0')
|
|
{ // -- terminates option handling for the rest of the command line
|
|
break;
|
|
}
|
|
}
|
|
for (j = 1; argv[i][j] != '\0'; ++j)
|
|
{
|
|
if (argv[i][j] == 'f')
|
|
{
|
|
force = 1;
|
|
}
|
|
else if (argv[i][j] == 'd')
|
|
{
|
|
DeflateOnly = 1;
|
|
}
|
|
else if (argv[i][j] == 'u')
|
|
{
|
|
update = 1;
|
|
}
|
|
else if (argv[i][j] == 'q')
|
|
{
|
|
Quiet = 1;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "Unknown option '%c'\n", argv[i][j]);
|
|
print_usage(argv[0]);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
argv[k++] = argv[i];
|
|
}
|
|
}
|
|
for (; i <= argc; ++i)
|
|
{
|
|
argv[k++] = argv[i];
|
|
}
|
|
argc -= i - k;
|
|
|
|
if (argc < 3)
|
|
{
|
|
print_usage(argv[0]);
|
|
return 1;
|
|
}
|
|
|
|
trees = add_dirs(&argv[2]);
|
|
if (no_mem)
|
|
{
|
|
free_dir_trees(trees);
|
|
fprintf(stderr, "Out of memory.\n");
|
|
return 1;
|
|
}
|
|
|
|
needwrite = force;
|
|
if (stat(argv[1], &zipstat) != 0)
|
|
{
|
|
if (errno == ENOENT)
|
|
{
|
|
needwrite = 1;
|
|
update = 0; // Can't update what's not there.
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "Could not stat %s: %s\n", argv[1], strerror(errno));
|
|
}
|
|
}
|
|
else if (!needwrite)
|
|
{
|
|
// Check the files in each tree. If any one of them was modified more
|
|
// recently than the zip, then it needs to be recreated.
|
|
for (tree = trees; tree != NULL; tree = tree->next)
|
|
{
|
|
for (file = tree->files; file != NULL; file = file->next)
|
|
{
|
|
if (file->time_write > zipstat.st_mtime)
|
|
{
|
|
needwrite = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (force || needwrite)
|
|
{
|
|
write_zip(argv[1], trees, update);
|
|
}
|
|
free_dir_trees(trees);
|
|
if (no_mem)
|
|
{
|
|
fprintf(stderr, "Out of memory.\n");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// bz_internal_error
|
|
//
|
|
// libbzip2 wants this, since we build it with BZ_NO_STDIO set.
|
|
//
|
|
//==========================================================================
|
|
|
|
void bz_internal_error (int errcode)
|
|
{
|
|
fprintf(stderr, "libbzip2: internal error number %d\n", errcode);
|
|
exit(3);
|
|
}
|