mirror of
https://github.com/MultiMC/MultiMC5.git
synced 2024-12-28 06:18:06 +00:00
558 lines
14 KiB
C
558 lines
14 KiB
C
/*
|
|
* Written in 2009 by Lloyd Hilaiel
|
|
*
|
|
* License
|
|
*
|
|
* All the cruft you find here is public domain. You don't have to credit
|
|
* anyone to use this code, but my personal request is that you mention
|
|
* Igor Pavlov for his hard, high quality work.
|
|
*
|
|
* command line elzma tool for lzma compression
|
|
*
|
|
* At time of writing, the primary purpose of this tool is to test the
|
|
* easylzma library.
|
|
*
|
|
* TODO:
|
|
* - stdin/stdout support
|
|
* - multiple file support
|
|
* - much more
|
|
*/
|
|
|
|
#include "include/compress.h"
|
|
#include "include/decompress.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#ifdef WIN32
|
|
#include <stdio.h>
|
|
#define unlink _unlink
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
int deleteFile(const char *path)
|
|
{
|
|
return unlink(path);
|
|
}
|
|
|
|
/* a utility to open a pair of files */
|
|
/* XXX: respect overwrite flag */
|
|
static int openFiles(const char *ifname, FILE **inFile, const char *ofname, FILE **outFile,
|
|
int overwrite)
|
|
{
|
|
*inFile = fopen(ifname, "rb");
|
|
if (*inFile == NULL)
|
|
{
|
|
fprintf(stderr, "couldn't open '%s' for reading\n", ifname);
|
|
return 1;
|
|
}
|
|
|
|
*outFile = fopen(ofname, "wb");
|
|
if (*outFile == NULL)
|
|
{
|
|
fprintf(stderr, "couldn't open '%s' for writing\n", ofname);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define ELZMA_COMPRESS_USAGE \
|
|
"Compress files using the LZMA algorithm (in place by default).\n" \
|
|
"\n" \
|
|
"Usage: elzma [options] [file]\n" \
|
|
" -1 .. -9 compression level, -1 is fast, -9 is best (default 5)\n" \
|
|
" -f, --force overwrite output files if they exist\n" \
|
|
" -h, --help output this message and exit\n" \
|
|
" -k, --keep don't delete input files\n" \
|
|
" --lzip compress to lzip disk format (.lz extension)\n" \
|
|
" --lzma compress to LZMA-Alone disk format (.lzma extension)\n" \
|
|
" -v, --verbose output verbose status information while compressing\n" \
|
|
" -z, --compress compress files (default when invoking elzma program)\n" \
|
|
" -d, --decompress decompress files (default when invoking unelzma program)\n" \
|
|
"\n" \
|
|
"Advanced Options:\n" \
|
|
" -s --set-max-dict (advanced) specify maximum dictionary size in bytes\n"
|
|
|
|
/* parse arguments populating output parameters, return nonzero on failure */
|
|
static int parseCompressArgs(int argc, char **argv, unsigned char *level, char **fname,
|
|
unsigned int *maxDictSize, unsigned int *verbose,
|
|
unsigned int *keep, unsigned int *overwrite,
|
|
elzma_file_format *format)
|
|
{
|
|
int i;
|
|
|
|
if (argc < 2)
|
|
return 1;
|
|
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
if (argv[i][0] == '-')
|
|
{
|
|
char *val = NULL;
|
|
char *arg = &(argv[i][1]);
|
|
if (arg[0] == '-')
|
|
arg++;
|
|
|
|
/* now see what argument this is */
|
|
if (!strcmp(arg, "h") || !strcmp(arg, "help"))
|
|
{
|
|
return 1;
|
|
}
|
|
else if (!strcmp(arg, "s") || !strcmp(arg, "set-max-dict"))
|
|
{
|
|
unsigned int j = 0;
|
|
val = argv[++i];
|
|
|
|
/* validate argument is numeric */
|
|
for (j = 0; j < strlen(val); j++)
|
|
{
|
|
if (val[j] < '0' || val[j] > '9')
|
|
return 1;
|
|
}
|
|
|
|
*maxDictSize = strtoul(val, (char **)NULL, 10);
|
|
|
|
/* don't allow dictionary sizes less than 8k */
|
|
if (*maxDictSize < (1 < 13))
|
|
*maxDictSize = 1 < 13;
|
|
else
|
|
{
|
|
/* make sure dict size is compatible with lzip,
|
|
* this will effectively collapse it to a close power
|
|
* of 2 */
|
|
*maxDictSize = elzma_get_dict_size(*maxDictSize);
|
|
}
|
|
}
|
|
else if (!strcmp(arg, "v") || !strcmp(arg, "verbose"))
|
|
{
|
|
*verbose = 1;
|
|
}
|
|
else if (!strcmp(arg, "f") || !strcmp(arg, "force"))
|
|
{
|
|
*overwrite = 1;
|
|
}
|
|
else if (!strcmp(arg, "k") || !strcmp(arg, "keep"))
|
|
{
|
|
*keep = 1;
|
|
}
|
|
else if (strlen(arg) == 1 && arg[0] >= '1' && arg[0] <= '9')
|
|
{
|
|
*level = arg[0] - '0';
|
|
}
|
|
else if (!strcmp(arg, "lzma"))
|
|
{
|
|
*format = ELZMA_lzma;
|
|
}
|
|
else if (!strcmp(arg, "lzip"))
|
|
{
|
|
*format = ELZMA_lzip;
|
|
}
|
|
else if (!strcmp(arg, "z") || !strcmp(arg, "d") || !strcmp(arg, "compress") ||
|
|
!strcmp(arg, "decompress"))
|
|
{
|
|
/* noop */
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*fname = argv[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* proper number of arguments? */
|
|
if (i != argc - 1 || *fname == NULL)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* callbacks for streamed input and output */
|
|
static size_t elzmaWriteFunc(void *ctx, const void *buf, size_t size)
|
|
{
|
|
size_t wt;
|
|
FILE *f = (FILE *)ctx;
|
|
assert(f != NULL);
|
|
|
|
wt = fwrite(buf, 1, size, f);
|
|
|
|
return wt;
|
|
}
|
|
|
|
static int elzmaReadFunc(void *ctx, void *buf, size_t *size)
|
|
{
|
|
FILE *f = (FILE *)ctx;
|
|
assert(f != NULL);
|
|
*size = fread(buf, 1, *size, f);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void printProgressHeader(void)
|
|
{
|
|
printf("|0%% 50%% 100%%|\n");
|
|
}
|
|
|
|
static void endProgress(int pCtx)
|
|
{
|
|
while (pCtx++ < 64)
|
|
{
|
|
printf(".");
|
|
}
|
|
printf("|\n");
|
|
}
|
|
|
|
static void elzmaProgressFunc(void *ctx, size_t complete, size_t total)
|
|
{
|
|
int *dots = (int *)ctx;
|
|
int wantDots = (int)(64 * (double)complete / (double)total);
|
|
if (*dots == 0)
|
|
{
|
|
printf("|");
|
|
(*dots)++;
|
|
}
|
|
while (wantDots > *dots)
|
|
{
|
|
printf(".");
|
|
(*dots)++;
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
static int doCompress(int argc, char **argv)
|
|
{
|
|
/* default compression parameters, some of which may be overridded by
|
|
* command line arguments */
|
|
unsigned char level = 5;
|
|
unsigned char lc = ELZMA_LC_DEFAULT;
|
|
unsigned char lp = ELZMA_LP_DEFAULT;
|
|
unsigned char pb = ELZMA_PB_DEFAULT;
|
|
unsigned int maxDictSize = ELZMA_DICT_SIZE_DEFAULT_MAX;
|
|
unsigned int dictSize = 0;
|
|
elzma_file_format format = ELZMA_lzma;
|
|
char *ext = ".lzma";
|
|
char *ifname = NULL;
|
|
char *ofname = NULL;
|
|
unsigned int verbose = 0;
|
|
FILE *inFile = NULL;
|
|
FILE *outFile = NULL;
|
|
elzma_compress_handle hand = NULL;
|
|
/* XXX: large file support */
|
|
unsigned int uncompressedSize = 0;
|
|
unsigned int keep = 0;
|
|
unsigned int overwrite = 0;
|
|
|
|
if (0 != parseCompressArgs(argc, argv, &level, &ifname, &maxDictSize, &verbose, &keep,
|
|
&overwrite, &format))
|
|
{
|
|
fprintf(stderr, ELZMA_COMPRESS_USAGE);
|
|
return 1;
|
|
}
|
|
|
|
/* extension switching based on compression type*/
|
|
if (format == ELZMA_lzip)
|
|
ext = ".lz";
|
|
|
|
/* generate output file name */
|
|
{
|
|
ofname = malloc(strlen(ifname) + strlen(ext) + 1);
|
|
ofname[0] = 0;
|
|
strcat(ofname, ifname);
|
|
strcat(ofname, ext);
|
|
}
|
|
|
|
/* now attempt to open input and ouput files */
|
|
/* XXX: stdin/stdout support */
|
|
if (0 != openFiles(ifname, &inFile, ofname, &outFile, overwrite))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
/* set uncompressed size */
|
|
if (0 != fseek(inFile, 0, SEEK_END) || 0 == (uncompressedSize = ftell(inFile)) ||
|
|
0 != fseek(inFile, 0, SEEK_SET))
|
|
{
|
|
fprintf(stderr, "error seeking input file (%s) - zero length?\n", ifname);
|
|
deleteFile(ofname);
|
|
return 1;
|
|
}
|
|
|
|
/* determine a reasonable dictionary size given input size */
|
|
dictSize = elzma_get_dict_size(uncompressedSize);
|
|
if (dictSize > maxDictSize)
|
|
dictSize = maxDictSize;
|
|
|
|
if (verbose)
|
|
{
|
|
printf("compressing '%s' to '%s'\n", ifname, ofname);
|
|
printf("lc/lp/pb = %u/%u/%u | dictionary size = %u bytes\n", lc, lp, pb, dictSize);
|
|
printf("input file is %u bytes\n", uncompressedSize);
|
|
}
|
|
|
|
/* allocate a compression handle */
|
|
hand = elzma_compress_alloc();
|
|
if (hand == NULL)
|
|
{
|
|
fprintf(stderr, "couldn't allocate compression object\n");
|
|
deleteFile(ofname);
|
|
return 1;
|
|
}
|
|
|
|
if (ELZMA_E_OK !=
|
|
elzma_compress_config(hand, lc, lp, pb, level, dictSize, format, uncompressedSize))
|
|
{
|
|
fprintf(stderr, "couldn't configure compression with "
|
|
"provided parameters\n");
|
|
deleteFile(ofname);
|
|
return 1;
|
|
}
|
|
|
|
{
|
|
int rv;
|
|
int pCtx = 0;
|
|
|
|
if (verbose)
|
|
printProgressHeader();
|
|
|
|
rv = elzma_compress_run(hand, elzmaReadFunc, (void *)inFile, elzmaWriteFunc,
|
|
(void *)outFile, (verbose ? elzmaProgressFunc : NULL), &pCtx);
|
|
|
|
if (verbose)
|
|
endProgress(pCtx);
|
|
|
|
if (ELZMA_E_OK != rv)
|
|
{
|
|
fprintf(stderr, "error compressing\n");
|
|
deleteFile(ofname);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/* clean up */
|
|
elzma_compress_free(&hand);
|
|
fclose(inFile);
|
|
fclose(outFile);
|
|
free(ofname);
|
|
|
|
if (!keep)
|
|
deleteFile(ifname);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define ELZMA_DECOMPRESS_USAGE \
|
|
"Decompress files compressed using the LZMA algorithm (in place by default).\n" \
|
|
"\n" \
|
|
"Usage: unelzma [options] [file]\n" \
|
|
" -f, --force overwrite output files if they exist\n" \
|
|
" -h, --help output this message and exit\n" \
|
|
" -k, --keep don't delete input files\n" \
|
|
" -v, --verbose output verbose status information while decompressing\n" \
|
|
" -z, --compress compress files (default when invoking elzma program)\n" \
|
|
" -d, --decompress decompress files (default when invoking unelzma program)\n" \
|
|
"\n"
|
|
/* parse arguments populating output parameters, return nonzero on failure */
|
|
static int parseDecompressArgs(int argc, char **argv, char **fname, unsigned int *verbose,
|
|
unsigned int *keep, unsigned int *overwrite)
|
|
{
|
|
int i;
|
|
|
|
if (argc < 2)
|
|
return 1;
|
|
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
if (argv[i][0] == '-')
|
|
{
|
|
char *arg = &(argv[i][1]);
|
|
if (arg[0] == '-')
|
|
arg++;
|
|
|
|
/* now see what argument this is */
|
|
if (!strcmp(arg, "h") || !strcmp(arg, "help"))
|
|
{
|
|
return 1;
|
|
}
|
|
else if (!strcmp(arg, "v") || !strcmp(arg, "verbose"))
|
|
{
|
|
*verbose = 1;
|
|
}
|
|
else if (!strcmp(arg, "k") || !strcmp(arg, "keep"))
|
|
{
|
|
*keep = 1;
|
|
}
|
|
else if (!strcmp(arg, "f") || !strcmp(arg, "force"))
|
|
{
|
|
*overwrite = 1;
|
|
}
|
|
else if (!strcmp(arg, "z") || !strcmp(arg, "d") || !strcmp(arg, "compress") ||
|
|
!strcmp(arg, "decompress"))
|
|
{
|
|
/* noop */
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*fname = argv[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* proper number of arguments? */
|
|
if (i != argc - 1 || *fname == NULL)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int doDecompress(int argc, char **argv)
|
|
{
|
|
char *ifname = NULL;
|
|
char *ofname = NULL;
|
|
unsigned int verbose = 0;
|
|
FILE *inFile = NULL;
|
|
FILE *outFile = NULL;
|
|
elzma_decompress_handle hand = NULL;
|
|
unsigned int overwrite = 0;
|
|
unsigned int keep = 0;
|
|
elzma_file_format format;
|
|
const char *lzmaExt = ".lzma";
|
|
const char *lzipExt = ".lz";
|
|
const char *ext = ".lz";
|
|
|
|
if (0 != parseDecompressArgs(argc, argv, &ifname, &verbose, &keep, &overwrite))
|
|
{
|
|
fprintf(stderr, ELZMA_DECOMPRESS_USAGE);
|
|
return 1;
|
|
}
|
|
|
|
/* generate output file name */
|
|
if (strlen(ifname) > strlen(lzmaExt) &&
|
|
0 == strcmp(lzmaExt, ifname + strlen(ifname) - strlen(lzmaExt)))
|
|
{
|
|
format = ELZMA_lzma;
|
|
ext = lzmaExt;
|
|
}
|
|
else if (strlen(ifname) > strlen(lzipExt) &&
|
|
0 == strcmp(lzipExt, ifname + strlen(ifname) - strlen(lzipExt)))
|
|
{
|
|
format = ELZMA_lzip;
|
|
ext = lzipExt;
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "input file extension not recognized (expected either "
|
|
"%s or %s)",
|
|
lzmaExt, lzipExt);
|
|
return 1;
|
|
}
|
|
|
|
ofname = malloc(strlen(ifname) - strlen(ext));
|
|
ofname[0] = 0;
|
|
strncat(ofname, ifname, strlen(ifname) - strlen(ext));
|
|
|
|
/* now attempt to open input and ouput files */
|
|
/* XXX: stdin/stdout support */
|
|
if (0 != openFiles(ifname, &inFile, ofname, &outFile, overwrite))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
hand = elzma_decompress_alloc();
|
|
if (hand == NULL)
|
|
{
|
|
fprintf(stderr, "couldn't allocate decompression object\n");
|
|
deleteFile(ofname);
|
|
return 1;
|
|
}
|
|
|
|
if (ELZMA_E_OK != elzma_decompress_run(hand, elzmaReadFunc, (void *)inFile, elzmaWriteFunc,
|
|
(void *)outFile, format))
|
|
{
|
|
fprintf(stderr, "error decompressing\n");
|
|
deleteFile(ofname);
|
|
return 1;
|
|
}
|
|
|
|
elzma_decompress_free(&hand);
|
|
|
|
if (!keep)
|
|
deleteFile(ifname);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
const char *unelzma = "unelzma";
|
|
const char *unelzmaLose = "unelzma.exe";
|
|
const char *elzma = "elzma";
|
|
const char *elzmaLose = "elzma.exe";
|
|
|
|
enum
|
|
{
|
|
RM_NONE,
|
|
RM_COMPRESS,
|
|
RM_DECOMPRESS
|
|
} runmode = RM_NONE;
|
|
|
|
/* first we'll determine the mode we're running in, indicated by
|
|
* the binary name (argv[0]) or by the presence of a flag:
|
|
* one of -z, -d, -compress, --decompress */
|
|
if ((strlen(argv[0]) >= strlen(unelzma) &&
|
|
!strcmp((argv[0] + strlen(argv[0]) - strlen(unelzma)), unelzma)) ||
|
|
(strlen(argv[0]) >= strlen(unelzmaLose) &&
|
|
!strcmp((argv[0] + strlen(argv[0]) - strlen(unelzmaLose)), unelzmaLose)))
|
|
{
|
|
runmode = RM_DECOMPRESS;
|
|
}
|
|
else if ((strlen(argv[0]) >= strlen(elzma) &&
|
|
!strcmp((argv[0] + strlen(argv[0]) - strlen(elzma)), elzma)) ||
|
|
(strlen(argv[0]) >= strlen(elzmaLose) &&
|
|
!strcmp((argv[0] + strlen(argv[0]) - strlen(elzmaLose)), elzmaLose)))
|
|
{
|
|
runmode = RM_COMPRESS;
|
|
}
|
|
|
|
/* allow runmode to be overridded by a command line flag, first flag
|
|
* wins */
|
|
{
|
|
int i;
|
|
for (i = 1; i < argc; i++)
|
|
{
|
|
if (!strcmp(argv[i], "-d") || !strcmp(argv[i], "--decompress"))
|
|
{
|
|
runmode = RM_DECOMPRESS;
|
|
break;
|
|
}
|
|
else if (!strcmp(argv[i], "-z") || !strcmp(argv[i], "--compress"))
|
|
{
|
|
runmode = RM_COMPRESS;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (runmode != RM_COMPRESS && runmode != RM_DECOMPRESS)
|
|
{
|
|
fprintf(stderr, "couldn't determine whether "
|
|
"you want to compress or decompress\n");
|
|
return 1;
|
|
}
|
|
|
|
if (runmode == RM_COMPRESS)
|
|
return doCompress(argc, argv);
|
|
return doDecompress(argc, argv);
|
|
}
|