2019-04-01 16:04:56 +00:00
|
|
|
/*
|
|
|
|
* main.c - command line compression utility for the LZSA format
|
|
|
|
*
|
|
|
|
* Copyright (C) 2019 Emmanuel Marty
|
|
|
|
*
|
|
|
|
* This software is provided 'as-is', without any express or implied
|
|
|
|
* warranty. In no event will the authors be held liable for any damages
|
|
|
|
* arising from the use of this software.
|
|
|
|
*
|
|
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
|
|
* including commercial applications, and to alter it and redistribute it
|
|
|
|
* freely, subject to the following restrictions:
|
|
|
|
*
|
|
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
|
|
* claim that you wrote the original software. If you use this software
|
|
|
|
* in a product, an acknowledgment in the product documentation would be
|
|
|
|
* appreciated but is not required.
|
|
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
|
|
* misrepresented as being the original software.
|
|
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <sys/timeb.h>
|
|
|
|
#else
|
|
|
|
#include <sys/time.h>
|
|
|
|
#endif
|
|
|
|
#include "format.h"
|
|
|
|
#include "shrink.h"
|
|
|
|
#include "expand.h"
|
|
|
|
|
|
|
|
#define BLOCK_SIZE 65536
|
2019-05-02 09:23:57 +00:00
|
|
|
#define OPT_VERBOSE 1
|
|
|
|
#define OPT_RAW 2
|
|
|
|
#define OPT_FAVOR_RATIO 4
|
2019-04-01 16:04:56 +00:00
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
static long long lzsa_get_time() {
|
|
|
|
long long nTime;
|
|
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
struct _timeb tb;
|
|
|
|
_ftime(&tb);
|
|
|
|
|
|
|
|
nTime = ((long long)tb.time * 1000LL + (long long)tb.millitm) * 1000LL;
|
|
|
|
#else
|
|
|
|
struct timeval tm;
|
|
|
|
gettimeofday(&tm, NULL);
|
|
|
|
|
|
|
|
nTime = (long long)tm.tv_sec * 1000000LL + (long long)tm.tv_usec;
|
|
|
|
#endif
|
|
|
|
return nTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
|
2019-04-21 07:41:12 +00:00
|
|
|
static int lzsa_compress(const char *pszInFilename, const char *pszOutFilename, const unsigned int nOptions, const int nMinMatchSize) {
|
2019-04-01 16:04:56 +00:00
|
|
|
FILE *f_in, *f_out;
|
|
|
|
unsigned char *pInData, *pOutData;
|
|
|
|
lsza_compressor compressor;
|
|
|
|
long long nStartTime = 0LL, nEndTime = 0LL;
|
|
|
|
long long nOriginalSize = 0LL, nCompressedSize = 0LL;
|
2019-05-02 09:23:57 +00:00
|
|
|
int nFlags;
|
2019-04-01 16:04:56 +00:00
|
|
|
int nResult;
|
2019-04-03 11:05:10 +00:00
|
|
|
bool bError = false;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
|
|
|
f_in = fopen(pszInFilename, "rb");
|
|
|
|
if (!f_in) {
|
|
|
|
fprintf(stderr, "error opening '%s' for reading\n", pszInFilename);
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
f_out = fopen(pszOutFilename, "wb");
|
|
|
|
if (!f_out) {
|
|
|
|
fprintf(stderr, "error opening '%s' for writing\n", pszOutFilename);
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
pInData = (unsigned char*)malloc(BLOCK_SIZE * 2);
|
|
|
|
if (!pInData) {
|
|
|
|
fclose(f_out);
|
|
|
|
f_out = NULL;
|
|
|
|
|
|
|
|
fclose(f_in);
|
|
|
|
f_in = NULL;
|
|
|
|
|
|
|
|
fprintf(stderr, "out of memory\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
memset(pInData, 0, BLOCK_SIZE * 2);
|
|
|
|
|
|
|
|
pOutData = (unsigned char*)malloc(BLOCK_SIZE);
|
|
|
|
if (!pOutData) {
|
|
|
|
free(pInData);
|
|
|
|
pInData = NULL;
|
|
|
|
|
|
|
|
fclose(f_out);
|
|
|
|
f_out = NULL;
|
|
|
|
|
|
|
|
fclose(f_in);
|
|
|
|
f_in = NULL;
|
|
|
|
|
|
|
|
fprintf(stderr, "out of memory\n");
|
|
|
|
return 100;
|
|
|
|
}
|
2019-04-10 15:38:22 +00:00
|
|
|
memset(pOutData, 0, BLOCK_SIZE);
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-05-02 09:23:57 +00:00
|
|
|
nFlags = 0;
|
|
|
|
if (nOptions & OPT_FAVOR_RATIO)
|
|
|
|
nFlags |= LZSA_FLAG_FAVOR_RATIO;
|
|
|
|
if (nOptions & OPT_RAW)
|
|
|
|
nFlags |= LZSA_FLAG_RAW_BLOCK;
|
|
|
|
nResult = lzsa_compressor_init(&compressor, BLOCK_SIZE * 2, nMinMatchSize, nFlags);
|
2019-04-01 16:04:56 +00:00
|
|
|
if (nResult != 0) {
|
|
|
|
free(pOutData);
|
|
|
|
pOutData = NULL;
|
|
|
|
|
|
|
|
free(pInData);
|
|
|
|
pInData = NULL;
|
|
|
|
|
|
|
|
fclose(f_out);
|
|
|
|
f_out = NULL;
|
|
|
|
|
|
|
|
fclose(f_in);
|
|
|
|
f_in = NULL;
|
|
|
|
|
|
|
|
fprintf(stderr, "error initializing compressor\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if ((nOptions & OPT_RAW) == 0) {
|
|
|
|
unsigned char cHeader[3];
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
cHeader[0] = 0x7b; /* Magic number: 0x9e7b */
|
|
|
|
cHeader[1] = 0x9e;
|
|
|
|
cHeader[2] = 0; /* Format version 1 */
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
bError = fwrite(cHeader, 1, 3, f_out) != 3;
|
|
|
|
nCompressedSize += 3LL;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if (nOptions & OPT_VERBOSE) {
|
2019-04-01 16:04:56 +00:00
|
|
|
nStartTime = lzsa_get_time();
|
|
|
|
}
|
|
|
|
|
|
|
|
int nPreviousBlockSize = 0;
|
|
|
|
|
|
|
|
while (!feof(f_in) && !bError) {
|
|
|
|
int nInDataSize;
|
|
|
|
|
|
|
|
if (nPreviousBlockSize) {
|
|
|
|
memcpy(pInData, pInData + BLOCK_SIZE, nPreviousBlockSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
nInDataSize = (int)fread(pInData + BLOCK_SIZE, 1, BLOCK_SIZE, f_in);
|
|
|
|
if (nInDataSize > 0) {
|
2019-04-03 11:05:10 +00:00
|
|
|
if (nPreviousBlockSize && (nOptions & OPT_RAW) != 0) {
|
|
|
|
fprintf(stderr, "error: raw blocks can only be used with files <= 64 Kb\n");
|
|
|
|
bError = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-04-01 16:04:56 +00:00
|
|
|
int nOutDataSize;
|
|
|
|
|
|
|
|
nOutDataSize = lzsa_shrink_block(&compressor, pInData + BLOCK_SIZE - nPreviousBlockSize, nPreviousBlockSize, nInDataSize, pOutData, (nInDataSize >= BLOCK_SIZE) ? BLOCK_SIZE : nInDataSize);
|
|
|
|
if (nOutDataSize >= 0) {
|
|
|
|
/* Write compressed block */
|
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if ((nOptions & OPT_RAW) == 0) {
|
|
|
|
unsigned char cBlockSize[3];
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
cBlockSize[0] = nOutDataSize & 0xff;
|
|
|
|
cBlockSize[1] = (nOutDataSize >> 8) & 0xff;
|
|
|
|
cBlockSize[2] = (nOutDataSize >> 16) & 0xff;
|
2019-04-13 10:33:25 +00:00
|
|
|
nCompressedSize += 3LL;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if (fwrite(cBlockSize, 1, 3, f_out) != (size_t)3) {
|
|
|
|
bError = true;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
2019-04-03 11:05:10 +00:00
|
|
|
|
|
|
|
if (!bError) {
|
2019-04-01 16:04:56 +00:00
|
|
|
if (fwrite(pOutData, 1, (size_t)nOutDataSize, f_out) != (size_t)nOutDataSize) {
|
|
|
|
bError = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
nOriginalSize += (long long)nInDataSize;
|
2019-04-13 10:33:25 +00:00
|
|
|
nCompressedSize += (long long)nOutDataSize;
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
/* Write uncompressible, literal block */
|
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if ((nOptions & OPT_RAW) != 0) {
|
|
|
|
fprintf(stderr, "error: data is incompressible, raw blocks only support compressed data\n");
|
|
|
|
bError = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-04-01 16:04:56 +00:00
|
|
|
unsigned char cBlockSize[3];
|
|
|
|
|
|
|
|
cBlockSize[0] = nInDataSize & 0xff;
|
|
|
|
cBlockSize[1] = (nInDataSize >> 8) & 0xff;
|
|
|
|
cBlockSize[2] = ((nInDataSize >> 16) & 0x7f) | 0x80; /* Uncompressed block */
|
|
|
|
|
|
|
|
if (fwrite(cBlockSize, 1, 3, f_out) != (size_t)3) {
|
|
|
|
bError = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (fwrite(pInData + BLOCK_SIZE, 1, (size_t)nInDataSize, f_out) != (size_t)nInDataSize) {
|
|
|
|
bError = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
nOriginalSize += (long long)nInDataSize;
|
|
|
|
nCompressedSize += 3LL + (long long)nInDataSize;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
nPreviousBlockSize = nInDataSize;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!bError && !feof(f_in) && nOriginalSize >= 1024 * 1024) {
|
|
|
|
fprintf(stdout, "\r%lld => %lld (%g %%)", nOriginalSize, nCompressedSize, (double)(nCompressedSize * 100.0 / nOriginalSize));
|
2019-05-02 09:23:57 +00:00
|
|
|
fflush(stdout);
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-05 21:16:05 +00:00
|
|
|
unsigned char cFooter[4];
|
|
|
|
int nFooterSize;
|
|
|
|
|
|
|
|
if ((nOptions & OPT_RAW) != 0) {
|
2019-05-02 09:23:57 +00:00
|
|
|
nFooterSize = 0;
|
2019-04-05 21:16:05 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
cFooter[0] = 0x00; /* EOD frame */
|
|
|
|
cFooter[1] = 0x00;
|
|
|
|
cFooter[2] = 0x00;
|
|
|
|
nFooterSize = 3;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
|
|
|
|
if (!bError)
|
2019-04-05 10:13:51 +00:00
|
|
|
bError = fwrite(cFooter, 1, nFooterSize, f_out) != nFooterSize;
|
2019-04-05 21:16:05 +00:00
|
|
|
nCompressedSize += (long long)nFooterSize;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if (!bError && (nOptions & OPT_VERBOSE)) {
|
2019-04-01 16:04:56 +00:00
|
|
|
nEndTime = lzsa_get_time();
|
|
|
|
|
|
|
|
double fDelta = ((double)(nEndTime - nStartTime)) / 1000000.0;
|
|
|
|
double fSpeed = ((double)nOriginalSize / 1048576.0) / fDelta;
|
2019-04-07 13:10:17 +00:00
|
|
|
int nCommands = lzsa_compressor_get_command_count(&compressor);
|
2019-04-21 07:41:12 +00:00
|
|
|
fprintf(stdout, "\rCompressed '%s' in %g seconds, %.02g Mb/s, %d tokens (%g bytes/token), %lld into %lld bytes ==> %g %%\n",
|
|
|
|
pszInFilename, fDelta, fSpeed, nCommands, (double)nOriginalSize / (double)nCommands,
|
2019-04-07 13:10:17 +00:00
|
|
|
nOriginalSize, nCompressedSize, (double)(nCompressedSize * 100.0 / nOriginalSize));
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
lzsa_compressor_destroy(&compressor);
|
|
|
|
|
|
|
|
free(pOutData);
|
|
|
|
pOutData = NULL;
|
|
|
|
|
|
|
|
free(pInData);
|
|
|
|
pInData = NULL;
|
|
|
|
|
|
|
|
fclose(f_out);
|
|
|
|
f_out = NULL;
|
|
|
|
|
|
|
|
fclose(f_in);
|
|
|
|
f_in = NULL;
|
|
|
|
|
|
|
|
if (bError) {
|
|
|
|
fprintf(stderr, "\rcompression error for '%s'\n", pszInFilename);
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
static int lzsa_decompress(const char *pszInFilename, const char *pszOutFilename, const unsigned int nOptions) {
|
2019-04-01 16:04:56 +00:00
|
|
|
long long nStartTime = 0LL, nEndTime = 0LL;
|
|
|
|
long long nOriginalSize = 0LL;
|
2019-04-03 11:05:10 +00:00
|
|
|
unsigned int nFileSize = 0;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
|
|
|
FILE *pInFile = fopen(pszInFilename, "rb");
|
|
|
|
if (!pInFile) {
|
|
|
|
fprintf(stderr, "error opening input file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if ((nOptions & OPT_RAW) == 0) {
|
|
|
|
unsigned char cHeader[3];
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
memset(cHeader, 0, 3);
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if (fread(cHeader, 1, 3, pInFile) != 3) {
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "error reading header in input file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if (cHeader[0] != 0x7b ||
|
|
|
|
cHeader[1] != 0x9e ||
|
|
|
|
cHeader[2] != 0) {
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "invalid magic number or format version in input file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
fseek(pInFile, 0, SEEK_END);
|
|
|
|
nFileSize = (unsigned int)ftell(pInFile);
|
|
|
|
fseek(pInFile, 0, SEEK_SET);
|
|
|
|
|
2019-04-05 21:16:05 +00:00
|
|
|
if (nFileSize < 4) {
|
2019-04-03 11:05:10 +00:00
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "invalid file size for raw block mode\n");
|
|
|
|
return 100;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FILE *pOutFile = fopen(pszOutFilename, "wb");
|
|
|
|
if (!pOutFile) {
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "error opening output file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char *pInBlock;
|
|
|
|
unsigned char *pOutData;
|
|
|
|
|
|
|
|
pInBlock = (unsigned char*)malloc(BLOCK_SIZE);
|
|
|
|
if (!pInBlock) {
|
|
|
|
fclose(pOutFile);
|
|
|
|
pOutFile = NULL;
|
|
|
|
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "error opening output file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
pOutData = (unsigned char*)malloc(BLOCK_SIZE * 2);
|
|
|
|
if (!pOutData) {
|
|
|
|
free(pInBlock);
|
|
|
|
pInBlock = NULL;
|
|
|
|
|
|
|
|
fclose(pOutFile);
|
|
|
|
pOutFile = NULL;
|
|
|
|
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "error opening output file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if (nOptions & OPT_VERBOSE) {
|
2019-04-01 16:04:56 +00:00
|
|
|
nStartTime = lzsa_get_time();
|
|
|
|
}
|
|
|
|
|
|
|
|
int nDecompressionError = 0;
|
|
|
|
int nPrevDecompressedSize = 0;
|
|
|
|
|
|
|
|
while (!feof(pInFile) && !nDecompressionError) {
|
2019-04-03 11:05:10 +00:00
|
|
|
unsigned int nBlockSize = 0;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
|
|
|
if (nPrevDecompressedSize != 0) {
|
|
|
|
memcpy(pOutData + BLOCK_SIZE - nPrevDecompressedSize, pOutData + BLOCK_SIZE, nPrevDecompressedSize);
|
|
|
|
}
|
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if ((nOptions & OPT_RAW) == 0) {
|
2019-04-20 08:26:13 +00:00
|
|
|
unsigned char cBlockSize[3];
|
|
|
|
|
|
|
|
memset(cBlockSize, 0, 3);
|
2019-04-03 11:05:10 +00:00
|
|
|
if (fread(cBlockSize, 1, 3, pInFile) == 3) {
|
|
|
|
nBlockSize = ((unsigned int)cBlockSize[0]) |
|
|
|
|
(((unsigned int)cBlockSize[1]) << 8) |
|
|
|
|
(((unsigned int)cBlockSize[2]) << 16);
|
|
|
|
}
|
|
|
|
else {
|
2019-04-05 22:02:11 +00:00
|
|
|
nBlockSize = 0;
|
2019-04-03 11:05:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2019-04-20 08:26:13 +00:00
|
|
|
if (nFileSize >= 4)
|
|
|
|
nBlockSize = nFileSize - 4;
|
2019-04-05 22:02:11 +00:00
|
|
|
nFileSize = 0;
|
2019-04-03 11:05:10 +00:00
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-05 22:02:11 +00:00
|
|
|
if (nBlockSize != 0) {
|
2019-04-03 11:05:10 +00:00
|
|
|
bool bIsUncompressed = (nBlockSize & 0x800000) != 0;
|
|
|
|
int nDecompressedSize = 0;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
nBlockSize &= 0x7fffff;
|
2019-04-20 08:26:13 +00:00
|
|
|
if ((int)nBlockSize > BLOCK_SIZE) {
|
|
|
|
fprintf(stderr, "block size %d > max size %d\n", nBlockSize, BLOCK_SIZE);
|
|
|
|
break;
|
|
|
|
}
|
2019-04-03 11:05:10 +00:00
|
|
|
if (fread(pInBlock, 1, nBlockSize, pInFile) == nBlockSize) {
|
|
|
|
if (bIsUncompressed) {
|
|
|
|
memcpy(pOutData + BLOCK_SIZE, pInBlock, nBlockSize);
|
|
|
|
nDecompressedSize = nBlockSize;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
unsigned int nBlockOffs = 0;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
nDecompressedSize = lzsa_expand_block(pInBlock, nBlockSize, pOutData, BLOCK_SIZE, BLOCK_SIZE);
|
|
|
|
if (nDecompressedSize < 0) {
|
|
|
|
nDecompressionError = nDecompressedSize;
|
|
|
|
break;
|
2019-04-03 08:16:12 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-03 11:05:10 +00:00
|
|
|
|
|
|
|
if (nDecompressedSize != 0) {
|
|
|
|
nOriginalSize += (long long)nDecompressedSize;
|
|
|
|
|
|
|
|
fwrite(pOutData + BLOCK_SIZE, 1, nDecompressedSize, pOutFile);
|
|
|
|
nPrevDecompressedSize = nDecompressedSize;
|
|
|
|
nDecompressedSize = 0;
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-04-03 11:05:10 +00:00
|
|
|
else {
|
|
|
|
break;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
free(pOutData);
|
|
|
|
pOutData = NULL;
|
|
|
|
|
|
|
|
free(pInBlock);
|
|
|
|
pInBlock = NULL;
|
|
|
|
|
|
|
|
fclose(pOutFile);
|
|
|
|
pOutFile = NULL;
|
|
|
|
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
|
|
|
|
if (nDecompressionError) {
|
|
|
|
fprintf(stderr, "decompression error for '%s'\n", pszInFilename);
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
else {
|
2019-04-03 11:05:10 +00:00
|
|
|
if (nOptions & OPT_VERBOSE) {
|
2019-04-01 16:04:56 +00:00
|
|
|
nEndTime = lzsa_get_time();
|
|
|
|
double fDelta = ((double)(nEndTime - nStartTime)) / 1000000.0;
|
|
|
|
double fSpeed = ((double)nOriginalSize / 1048576.0) / fDelta;
|
|
|
|
fprintf(stdout, "Decompressed '%s' in %g seconds, %g Mb/s\n",
|
|
|
|
pszInFilename, fDelta, fSpeed);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
static int lzsa_compare(const char *pszInFilename, const char *pszOutFilename, const unsigned int nOptions) {
|
2019-04-01 16:04:56 +00:00
|
|
|
long long nStartTime = 0LL, nEndTime = 0LL;
|
|
|
|
long long nOriginalSize = 0LL;
|
|
|
|
long long nKnownGoodSize = 0LL;
|
2019-04-03 11:05:10 +00:00
|
|
|
unsigned int nFileSize = 0;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
|
|
|
FILE *pInFile = fopen(pszInFilename, "rb");
|
|
|
|
if (!pInFile) {
|
|
|
|
fprintf(stderr, "error opening compressed input file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if ((nOptions & OPT_RAW) == 0) {
|
|
|
|
unsigned char cHeader[3];
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
memset(cHeader, 0, 3);
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if (fread(cHeader, 1, 3, pInFile) != 3) {
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "error reading header in compressed input file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if (cHeader[0] != 0x7b ||
|
|
|
|
cHeader[1] != 0x9e ||
|
|
|
|
cHeader[2] != 0) {
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "invalid magic number or format version in input file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
fseek(pInFile, 0, SEEK_END);
|
|
|
|
nFileSize = (unsigned int)ftell(pInFile);
|
|
|
|
fseek(pInFile, 0, SEEK_SET);
|
|
|
|
|
2019-04-05 21:16:05 +00:00
|
|
|
if (nFileSize < 4) {
|
2019-04-03 11:05:10 +00:00
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "invalid file size for raw block mode\n");
|
|
|
|
return 100;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
FILE *pOutFile = fopen(pszOutFilename, "rb");
|
|
|
|
if (!pOutFile) {
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "error opening original uncompressed file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned char *pInBlock;
|
|
|
|
unsigned char *pOutData;
|
|
|
|
unsigned char *pCompareData;
|
|
|
|
|
|
|
|
pInBlock = (unsigned char*)malloc(BLOCK_SIZE);
|
|
|
|
if (!pInBlock) {
|
|
|
|
fclose(pOutFile);
|
|
|
|
pOutFile = NULL;
|
|
|
|
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "error opening output file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
pOutData = (unsigned char*)malloc(BLOCK_SIZE * 2);
|
|
|
|
if (!pOutData) {
|
|
|
|
free(pInBlock);
|
|
|
|
pInBlock = NULL;
|
|
|
|
|
|
|
|
fclose(pOutFile);
|
|
|
|
pOutFile = NULL;
|
|
|
|
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "error opening output file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
pCompareData = (unsigned char*)malloc(BLOCK_SIZE);
|
|
|
|
if (!pCompareData) {
|
|
|
|
free(pOutData);
|
|
|
|
pOutData = NULL;
|
|
|
|
|
|
|
|
free(pInBlock);
|
|
|
|
pInBlock = NULL;
|
|
|
|
|
|
|
|
fclose(pOutFile);
|
|
|
|
pOutFile = NULL;
|
|
|
|
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
fprintf(stderr, "error opening output file\n");
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if (nOptions & OPT_VERBOSE) {
|
2019-04-01 16:04:56 +00:00
|
|
|
nStartTime = lzsa_get_time();
|
|
|
|
}
|
|
|
|
|
|
|
|
int nDecompressionError = 0;
|
|
|
|
bool bComparisonError = false;
|
|
|
|
int nPrevDecompressedSize = 0;
|
|
|
|
|
2019-04-03 08:16:12 +00:00
|
|
|
while (!feof(pInFile) && !nDecompressionError && !bComparisonError) {
|
2019-04-03 11:05:10 +00:00
|
|
|
unsigned int nBlockSize = 0;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
|
|
|
if (nPrevDecompressedSize != 0) {
|
|
|
|
memcpy(pOutData + BLOCK_SIZE - nPrevDecompressedSize, pOutData + BLOCK_SIZE, nPrevDecompressedSize);
|
|
|
|
}
|
|
|
|
|
|
|
|
int nBytesToCompare = (int)fread(pCompareData, 1, BLOCK_SIZE, pOutFile);
|
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
if ((nOptions & OPT_RAW) == 0) {
|
|
|
|
unsigned char cBlockSize[3];
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-20 08:26:13 +00:00
|
|
|
memset(cBlockSize, 0, 3);
|
2019-04-03 11:05:10 +00:00
|
|
|
if (fread(cBlockSize, 1, 3, pInFile) == 3) {
|
|
|
|
nBlockSize = ((unsigned int)cBlockSize[0]) |
|
|
|
|
(((unsigned int)cBlockSize[1]) << 8) |
|
|
|
|
(((unsigned int)cBlockSize[2]) << 16);
|
|
|
|
}
|
|
|
|
else {
|
2019-04-05 22:02:11 +00:00
|
|
|
nBlockSize = 0;
|
2019-04-03 11:05:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2019-04-20 08:26:13 +00:00
|
|
|
if (nFileSize >= 4)
|
|
|
|
nBlockSize = nFileSize - 4;
|
2019-04-05 22:02:11 +00:00
|
|
|
nFileSize = 0;
|
2019-04-03 11:05:10 +00:00
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-05 22:02:11 +00:00
|
|
|
if (nBlockSize != 0) {
|
2019-04-03 11:05:10 +00:00
|
|
|
bool bIsUncompressed = (nBlockSize & 0x800000) != 0;
|
|
|
|
int nDecompressedSize = 0;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
nBlockSize &= 0x7fffff;
|
2019-04-20 08:26:13 +00:00
|
|
|
if ((int)nBlockSize > BLOCK_SIZE) {
|
|
|
|
fprintf(stderr, "block size %d > max size %d\n", nBlockSize, BLOCK_SIZE);
|
|
|
|
break;
|
|
|
|
}
|
2019-04-03 11:05:10 +00:00
|
|
|
if (fread(pInBlock, 1, nBlockSize, pInFile) == nBlockSize) {
|
|
|
|
if (bIsUncompressed) {
|
|
|
|
memcpy(pOutData + BLOCK_SIZE, pInBlock, nBlockSize);
|
|
|
|
nDecompressedSize = nBlockSize;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
unsigned int nBlockOffs = 0;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
2019-04-03 11:05:10 +00:00
|
|
|
nDecompressedSize = lzsa_expand_block(pInBlock, nBlockSize, pOutData, BLOCK_SIZE, BLOCK_SIZE);
|
|
|
|
if (nDecompressedSize < 0) {
|
|
|
|
nDecompressionError = nDecompressedSize;
|
2019-04-03 08:16:12 +00:00
|
|
|
break;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
2019-04-03 11:05:10 +00:00
|
|
|
|
|
|
|
if (nDecompressedSize == nBytesToCompare) {
|
|
|
|
nKnownGoodSize = nOriginalSize;
|
|
|
|
|
|
|
|
nOriginalSize += (long long)nDecompressedSize;
|
|
|
|
|
|
|
|
if (memcmp(pOutData + BLOCK_SIZE, pCompareData, nBytesToCompare))
|
|
|
|
bComparisonError = true;
|
|
|
|
nPrevDecompressedSize = nDecompressedSize;
|
|
|
|
nDecompressedSize = 0;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
else {
|
2019-04-03 11:05:10 +00:00
|
|
|
bComparisonError = true;
|
2019-04-01 16:04:56 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-04-03 11:05:10 +00:00
|
|
|
else {
|
|
|
|
break;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
free(pCompareData);
|
|
|
|
pCompareData = NULL;
|
|
|
|
|
|
|
|
free(pOutData);
|
|
|
|
pOutData = NULL;
|
|
|
|
|
|
|
|
free(pInBlock);
|
|
|
|
pInBlock = NULL;
|
|
|
|
|
|
|
|
fclose(pOutFile);
|
|
|
|
pOutFile = NULL;
|
|
|
|
|
|
|
|
fclose(pInFile);
|
|
|
|
pInFile = NULL;
|
|
|
|
|
|
|
|
if (nDecompressionError) {
|
|
|
|
fprintf(stderr, "decompression error for '%s'\n", pszInFilename);
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
else if (bComparisonError) {
|
|
|
|
fprintf(stderr, "error comparing compressed file '%s' with original '%s' starting at %lld\n", pszInFilename, pszOutFilename, nKnownGoodSize);
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
else {
|
2019-04-03 11:05:10 +00:00
|
|
|
if (nOptions & OPT_VERBOSE) {
|
2019-04-01 16:04:56 +00:00
|
|
|
nEndTime = lzsa_get_time();
|
|
|
|
double fDelta = ((double)(nEndTime - nStartTime)) / 1000000.0;
|
|
|
|
double fSpeed = ((double)nOriginalSize / 1048576.0) / fDelta;
|
|
|
|
fprintf(stdout, "Compared '%s' in %g seconds, %g Mb/s\n",
|
|
|
|
pszInFilename, fDelta, fSpeed);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
int main(int argc, char **argv) {
|
|
|
|
int i;
|
|
|
|
const char *pszInFilename = NULL;
|
|
|
|
const char *pszOutFilename = NULL;
|
|
|
|
bool bArgsError = false;
|
|
|
|
bool bCommandDefined = false;
|
|
|
|
bool bVerifyCompression = false;
|
2019-04-21 07:41:12 +00:00
|
|
|
bool bMinMatchDefined = false;
|
2019-04-01 16:04:56 +00:00
|
|
|
char cCommand = 'z';
|
2019-04-21 07:41:12 +00:00
|
|
|
int nMinMatchSize = MIN_MATCH_SIZE;
|
2019-05-02 09:23:57 +00:00
|
|
|
unsigned int nOptions = OPT_FAVOR_RATIO;
|
2019-04-01 16:04:56 +00:00
|
|
|
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
if (!strcmp(argv[i], "-d")) {
|
|
|
|
if (!bCommandDefined) {
|
|
|
|
bCommandDefined = true;
|
|
|
|
cCommand = 'd';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
|
|
|
else if (!strcmp(argv[i], "-z")) {
|
|
|
|
if (!bCommandDefined) {
|
|
|
|
bCommandDefined = true;
|
|
|
|
cCommand = 'z';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
|
|
|
else if (!strcmp(argv[i], "-c")) {
|
|
|
|
if (!bVerifyCompression) {
|
|
|
|
bVerifyCompression = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
2019-04-21 07:41:12 +00:00
|
|
|
else if (!strcmp(argv[i], "-m")) {
|
|
|
|
if (!bMinMatchDefined && (i + 1) < argc) {
|
|
|
|
char *pEnd = NULL;
|
|
|
|
nMinMatchSize = (int)strtol(argv[i + 1], &pEnd, 10);
|
2019-04-22 12:16:25 +00:00
|
|
|
if (pEnd && pEnd != argv[i + 1] && (nMinMatchSize >= MIN_MATCH_SIZE && nMinMatchSize < MATCH_RUN_LEN)) {
|
2019-04-21 07:41:12 +00:00
|
|
|
i++;
|
|
|
|
bMinMatchDefined = true;
|
2019-05-02 09:23:57 +00:00
|
|
|
nOptions &= (~OPT_FAVOR_RATIO);
|
2019-04-21 07:41:12 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
|
|
|
else if (!strncmp(argv[i], "-m", 2)) {
|
|
|
|
if (!bMinMatchDefined) {
|
|
|
|
char *pEnd = NULL;
|
|
|
|
nMinMatchSize = (int)strtol(argv[i] + 2, &pEnd, 10);
|
2019-04-22 12:16:25 +00:00
|
|
|
if (pEnd && pEnd != (argv[i]+2) && (nMinMatchSize >= MIN_MATCH_SIZE && nMinMatchSize < MATCH_RUN_LEN)) {
|
2019-04-21 07:41:12 +00:00
|
|
|
bMinMatchDefined = true;
|
2019-05-02 09:23:57 +00:00
|
|
|
nOptions &= (~OPT_FAVOR_RATIO);
|
2019-04-21 07:41:12 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
|
|
|
else if (!strcmp(argv[i], "--prefer-ratio")) {
|
|
|
|
if (!bMinMatchDefined) {
|
|
|
|
nMinMatchSize = MIN_MATCH_SIZE;
|
|
|
|
bMinMatchDefined = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
|
|
|
else if (!strcmp(argv[i], "--prefer-speed")) {
|
|
|
|
if (!bMinMatchDefined) {
|
2019-05-02 09:23:57 +00:00
|
|
|
nMinMatchSize = 3;
|
|
|
|
nOptions &= (~OPT_FAVOR_RATIO);
|
2019-04-21 07:41:12 +00:00
|
|
|
bMinMatchDefined = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
2019-04-01 16:04:56 +00:00
|
|
|
else if (!strcmp(argv[i], "-v")) {
|
2019-04-03 11:05:10 +00:00
|
|
|
if ((nOptions & OPT_VERBOSE) == 0) {
|
|
|
|
nOptions |= OPT_VERBOSE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
|
|
|
else if (!strcmp(argv[i], "-r")) {
|
|
|
|
if ((nOptions & OPT_RAW) == 0) {
|
|
|
|
nOptions |= OPT_RAW;
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (!pszInFilename)
|
|
|
|
pszInFilename = argv[i];
|
|
|
|
else {
|
|
|
|
if (!pszOutFilename)
|
|
|
|
pszOutFilename = argv[i];
|
|
|
|
else
|
|
|
|
bArgsError = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (bArgsError || !pszInFilename || !pszOutFilename) {
|
2019-04-03 11:05:10 +00:00
|
|
|
fprintf(stderr, "usage: %s [-c] [-d] [-v] [-r] <infile> <outfile>\n", argv[0]);
|
2019-04-01 16:04:56 +00:00
|
|
|
fprintf(stderr, " -c: check resulting stream after compressing\n");
|
|
|
|
fprintf(stderr, " -d: decompress (default: compress)\n");
|
|
|
|
fprintf(stderr, " -v: be verbose\n");
|
2019-04-03 11:05:10 +00:00
|
|
|
fprintf(stderr, " -r: raw block format (max. 64 Kb files)\n");
|
2019-04-21 07:41:12 +00:00
|
|
|
fprintf(stderr, " -m <value>: minimum match size (3-14) (default: 3)\n");
|
2019-05-02 09:23:57 +00:00
|
|
|
fprintf(stderr, " --prefer-ratio: favor compression ratio (default)\n");
|
|
|
|
fprintf(stderr, " --prefer-speed: favor decompression speed (same as -m3)\n");
|
2019-04-01 16:04:56 +00:00
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cCommand == 'z') {
|
2019-04-21 07:41:12 +00:00
|
|
|
int nResult = lzsa_compress(pszInFilename, pszOutFilename, nOptions, nMinMatchSize);
|
2019-04-01 16:04:56 +00:00
|
|
|
if (nResult == 0 && bVerifyCompression) {
|
2019-04-03 11:05:10 +00:00
|
|
|
nResult = lzsa_compare(pszOutFilename, pszInFilename, nOptions);
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (cCommand == 'd') {
|
2019-04-03 11:05:10 +00:00
|
|
|
return lzsa_decompress(pszInFilename, pszOutFilename, nOptions);
|
2019-04-01 16:04:56 +00:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
return 100;
|
|
|
|
}
|
|
|
|
}
|