585 lines
14 KiB
C
585 lines
14 KiB
C
|
/* ------------------------------------------------------------------------ */
|
||
|
/* */
|
||
|
/* Main file of public UNACE. */
|
||
|
/* */
|
||
|
/* ------------------------------------------------------------------------ */
|
||
|
|
||
|
|
||
|
//--------------- include general files ------------------------------------//
|
||
|
#include <ctype.h> // tolower()
|
||
|
#include <fcntl.h> // open()
|
||
|
#include <stdio.h> // printf() sprintf() remove()
|
||
|
#include <stdlib.h> // malloc()
|
||
|
#include <string.h> // str*()
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/stat.h> // S_I* AMIGA: fstat()
|
||
|
|
||
|
#define DIRSEP '\\'
|
||
|
|
||
|
#if (!defined(__EMX__) && !defined(__OS2__) && !defined(WINNT) && !defined(WIN32)) || defined(__CYGWIN__)
|
||
|
#include <sys/errno.h>
|
||
|
#endif
|
||
|
|
||
|
|
||
|
//--------------- include unace specific header files ----------------------//
|
||
|
#include "os.h"
|
||
|
|
||
|
#include "globals.h"
|
||
|
#include "portable.h"
|
||
|
#include "uac_comm.h"
|
||
|
#include "uac_crc.h"
|
||
|
#include "uac_crt.h"
|
||
|
#include "uac_dcpr.h"
|
||
|
#include "uac_sys.h"
|
||
|
|
||
|
#ifdef CRYPT
|
||
|
#include "unace_ps.h"
|
||
|
#endif /* CRYPT */
|
||
|
int files=0;
|
||
|
|
||
|
//--------------- BEGIN OF UNACE ROUTINES ----------------------------------//
|
||
|
|
||
|
int pipeit(char *format, ...) {
|
||
|
/* Do nothing ... perhaps pipe this somewhere in the future */
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void init_unace(void) // initializes unace
|
||
|
{
|
||
|
buf_rd =malloc(size_rdb * sizeof(ULONG)); // Allocate buffers: increase
|
||
|
buf =malloc(size_buf); // sizes when possible to speed
|
||
|
buf_wr =malloc(size_wrb); // up the program
|
||
|
readbuf=malloc(size_headrdb);
|
||
|
|
||
|
if (buf_rd ==NULL ||
|
||
|
buf ==NULL ||
|
||
|
buf_wr ==NULL ||
|
||
|
readbuf==NULL )
|
||
|
f_err = ERR_MEM;
|
||
|
|
||
|
make_crctable(); // initialize CRC table
|
||
|
dcpr_init(); // initialize decompression
|
||
|
|
||
|
set_handler(); // ctrl+break etc.
|
||
|
}
|
||
|
|
||
|
void done_unace(void)
|
||
|
{
|
||
|
if (buf_rd ) free(buf_rd );
|
||
|
if (buf ) free(buf );
|
||
|
if (buf_wr ) free(buf_wr );
|
||
|
if (readbuf ) free(readbuf );
|
||
|
if (dcpr_text) free(dcpr_text);
|
||
|
}
|
||
|
|
||
|
INT read_header(INT print_err) // reads any header from archive
|
||
|
{
|
||
|
USHORT rd,
|
||
|
head_size,
|
||
|
crc_ok;
|
||
|
LONG crc;
|
||
|
UCHAR *tp=readbuf;
|
||
|
|
||
|
lseek(archan, skipsize, SEEK_CUR); // skip ADDSIZE block
|
||
|
|
||
|
if (read(archan, &head, 4)<4)
|
||
|
return (0); // read CRC and header size
|
||
|
|
||
|
#ifdef HI_LO_BYTE_ORDER
|
||
|
WORDswap(&head.HEAD_CRC);
|
||
|
WORDswap(&head.HEAD_SIZE);
|
||
|
#endif
|
||
|
// read size_headrdb bytes into
|
||
|
head_size = head.HEAD_SIZE; // header structure
|
||
|
rd = (head_size > size_headrdb) ? size_headrdb : head_size;
|
||
|
if (read(archan, readbuf, rd) < rd)
|
||
|
return (0);
|
||
|
head_size -= rd;
|
||
|
crc = getcrc(CRC_MASK, readbuf, rd);
|
||
|
|
||
|
while (head_size) // skip rest of header
|
||
|
{
|
||
|
rd = (head_size > size_buf) ? size_buf : head_size;
|
||
|
if (read(archan, buf, rd) < rd)
|
||
|
return (0);
|
||
|
head_size -= rd;
|
||
|
crc = getcrc(crc, (UCHAR *)buf, rd);
|
||
|
}
|
||
|
|
||
|
head.HEAD_TYPE =*tp++; // generic buffer to head conversion
|
||
|
head.HEAD_FLAGS=BUFP2WORD(tp);
|
||
|
|
||
|
if (head.HEAD_FLAGS & ACE_ADDSIZE)
|
||
|
skipsize = head.ADDSIZE = BUF2LONG(tp); // get ADDSIZE
|
||
|
else
|
||
|
skipsize = 0;
|
||
|
|
||
|
// check header CRC
|
||
|
if (!(crc_ok = head.HEAD_CRC == (crc & 0xffff)) && print_err)
|
||
|
pipeit("\nError: archive is broken\n");
|
||
|
else
|
||
|
switch (head.HEAD_TYPE) // specific buffer to head conversion
|
||
|
{
|
||
|
case MAIN_BLK:
|
||
|
memcpy(mhead.ACESIGN, tp, acesign_len); tp+=acesign_len;
|
||
|
mhead.VER_MOD=*tp++;
|
||
|
mhead.VER_CR =*tp++;
|
||
|
mhead.HOST_CR=*tp++;
|
||
|
mhead.VOL_NUM=*tp++;
|
||
|
mhead.TIME_CR=BUFP2LONG(tp);
|
||
|
mhead.RES1 =BUFP2WORD(tp);
|
||
|
mhead.RES2 =BUFP2WORD(tp);
|
||
|
mhead.RES =BUFP2LONG(tp);
|
||
|
mhead.AV_SIZE=*tp++;
|
||
|
memcpy(mhead.AV, tp, rd-(USHORT)(tp-readbuf));
|
||
|
break;
|
||
|
case FILE_BLK:
|
||
|
fhead.PSIZE =BUFP2LONG(tp);
|
||
|
fhead.SIZE =BUFP2LONG(tp);
|
||
|
fhead.FTIME =BUFP2LONG(tp);
|
||
|
fhead.ATTR =BUFP2LONG(tp);
|
||
|
fhead.CRC32 =BUFP2LONG(tp);
|
||
|
fhead.TECH.TYPE =*tp++;
|
||
|
fhead.TECH.QUAL =*tp++;
|
||
|
fhead.TECH.PARM =BUFP2WORD(tp);
|
||
|
fhead.RESERVED =BUFP2WORD(tp);
|
||
|
fhead.FNAME_SIZE=BUFP2WORD(tp);
|
||
|
memcpy(fhead.FNAME, tp, rd-(USHORT)(tp-readbuf));
|
||
|
break;
|
||
|
// default: (REC_BLK and future things):
|
||
|
// do nothing 'cause isn't needed for extraction
|
||
|
}
|
||
|
|
||
|
return (crc_ok);
|
||
|
}
|
||
|
// maximum SFX module size
|
||
|
#define max_sfx_size 65536 // (needed by read_arc_head)
|
||
|
|
||
|
INT read_arc_head(void) // searches for the archive header and reads it
|
||
|
{
|
||
|
INT i,
|
||
|
flags,
|
||
|
buf_pos = 0;
|
||
|
LONG arc_head_pos,
|
||
|
old_fpos,
|
||
|
fpos = 0;
|
||
|
struct stat st;
|
||
|
|
||
|
fstat(archan, &st);
|
||
|
|
||
|
memset(buf, 0, size_buf);
|
||
|
|
||
|
#if !defined(__EMX__) && !defined(__OS2__)
|
||
|
while (ftell(farchan)<st.st_size && fpos < max_sfx_size)
|
||
|
#else
|
||
|
while (tell(archan)<st.st_size && fpos < max_sfx_size)
|
||
|
#endif
|
||
|
{
|
||
|
old_fpos = fpos;
|
||
|
fpos += read(archan, &buf[buf_pos], size_buf - buf_pos);
|
||
|
|
||
|
for (i = 0; i < size_buf; i++) // look for the acesign
|
||
|
{
|
||
|
if (!memcmp(acesign, &buf[i], acesign_len))
|
||
|
{
|
||
|
// seek to the probable begin
|
||
|
// of the archive
|
||
|
arc_head_pos = old_fpos + i - buf_pos - bytes_before_acesign;
|
||
|
lseek(archan, arc_head_pos, SEEK_SET);
|
||
|
if (read_header(0)) // try to read archive header
|
||
|
{
|
||
|
flags = mhead.HEAD_FLAGS;
|
||
|
adat.sol = (flags & ACE_SOLID) > 0;
|
||
|
adat.vol = (flags & ACE_MULT_VOL) > 0;
|
||
|
adat.vol_num = mhead.VOL_NUM;
|
||
|
adat.time_cr = mhead.TIME_CR;
|
||
|
return (1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// was no archive header,
|
||
|
// continue search
|
||
|
lseek(archan, fpos, SEEK_SET);
|
||
|
memcpy(buf, &buf[size_buf - 512], 512);
|
||
|
buf_pos = 512; // keep 512 old bytes
|
||
|
}
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
INT open_archive(INT print_err) // opens archive (or volume)
|
||
|
{
|
||
|
CHAR av_str[80];
|
||
|
|
||
|
|
||
|
#if defined(__OS2_) || defined(__EMX__) || defined(WIN32)
|
||
|
archan = open(aname, O_RDONLY | O_BINARY); // open file
|
||
|
#else
|
||
|
archan = open(aname, O_RDONLY); // open file
|
||
|
#endif
|
||
|
#if !defined(__EMX__) && !defined(__OS2__)
|
||
|
farchan = fdopen(archan, "rb");
|
||
|
#endif
|
||
|
if (archan == -1)
|
||
|
{
|
||
|
pipeit("\nError opening file %s", aname);
|
||
|
return (0);
|
||
|
}
|
||
|
if (!read_arc_head()) // read archive header
|
||
|
{
|
||
|
if (print_err)
|
||
|
pipeit("\nInvalid archive file: %s\n", aname);
|
||
|
#if !defined(__EMX__) && !defined(__OS2__)
|
||
|
fclose(farchan);
|
||
|
#endif
|
||
|
close(archan);
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
pipeit("\nProcessing archive: %s\n\n", aname);
|
||
|
if (head.HEAD_FLAGS & ACE_AV)
|
||
|
{
|
||
|
pipeit("Authenticity Verification:"); // print the AV
|
||
|
sprintf(av_str, "\ncreated on %d.%d.%d by ",
|
||
|
ts_day(adat.time_cr), ts_month(adat.time_cr), ts_year(adat.time_cr));
|
||
|
pipeit(av_str);
|
||
|
strncpy(av_str, (char *)mhead.AV, mhead.AV_SIZE);
|
||
|
av_str[mhead.AV_SIZE] = 0;
|
||
|
pipeit("%s\n\n", av_str);
|
||
|
}
|
||
|
comment_out("Main comment:"); // print main comment
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
void get_next_volname(void) // get file name of next volume
|
||
|
{
|
||
|
CHAR *cp;
|
||
|
INT num;
|
||
|
|
||
|
if ((cp = (CHAR *) strrchr(aname, '.')) == NULL || !*(cp + 1))
|
||
|
num = -1;
|
||
|
else
|
||
|
{
|
||
|
cp++;
|
||
|
num = (*(cp + 1) - '0') * 10 + *(cp + 2) - '0';
|
||
|
if (!in(num, 0, 99))
|
||
|
num = -1;
|
||
|
if (in(*cp, '0', '9'))
|
||
|
num += (*cp - '0') * 100;
|
||
|
}
|
||
|
num++;
|
||
|
|
||
|
if (num < 100)
|
||
|
*cp = 'C';
|
||
|
else
|
||
|
*cp = num / 100 + '0';
|
||
|
*(cp + 1) = (num / 10) % 10 + '0';
|
||
|
*(cp + 2) = num % 10 + '0';
|
||
|
}
|
||
|
|
||
|
INT proc_vol(void) // opens volume
|
||
|
{
|
||
|
INT i;
|
||
|
CHAR s[80];
|
||
|
|
||
|
if (!fileexists(aname) || !f_allvol_pr)
|
||
|
{
|
||
|
do
|
||
|
{
|
||
|
sprintf(s, "Ready to process %s?", aname);
|
||
|
#if !defined(__MINGW32__)
|
||
|
beep();
|
||
|
#else
|
||
|
beep(500,500);
|
||
|
#endif
|
||
|
i = wrask(s); // ask whether ready or not
|
||
|
f_allvol_pr = (i == 1); // "Always" --> process all volumes
|
||
|
if (i >= 2)
|
||
|
{
|
||
|
f_err = ERR_FOUND;
|
||
|
return (0);
|
||
|
}
|
||
|
}
|
||
|
while (!fileexists(aname));
|
||
|
}
|
||
|
|
||
|
if (!open_archive(1)) // open volume
|
||
|
{
|
||
|
pipeit("\nError while opening archive. File not found or archive broken.\n");
|
||
|
f_err = ERR_OPEN;
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
INT proc_next_vol(void) // opens next volume to process
|
||
|
{
|
||
|
#if !defined(__EMX__) && !defined(__OS2__)
|
||
|
fclose(farchan);
|
||
|
#endif
|
||
|
close(archan); // close handle
|
||
|
get_next_volname(); // get file name of next volume
|
||
|
|
||
|
if (!proc_vol()) // try to open volume, read archive header
|
||
|
return 0;
|
||
|
if (!read_header(1)) // read 2nd header
|
||
|
{
|
||
|
f_err=ERR_READ;
|
||
|
return 0;
|
||
|
}
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
INT read_adds_blk(CHAR * buffer, INT len) // reads part of ADD_SIZE block
|
||
|
{
|
||
|
INT rd = 0,
|
||
|
l = len;
|
||
|
LONG i;
|
||
|
|
||
|
#ifdef CRYPT
|
||
|
char *cbuffer=buffer;
|
||
|
|
||
|
if (head.HEAD_TYPE == FILE_BLK && (head.HEAD_FLAGS & ACE_PASSW))
|
||
|
len = crypt_len(len);
|
||
|
#endif /* CRYPT */
|
||
|
while (!f_err && len && skipsize)
|
||
|
{
|
||
|
i = (skipsize > len) ? len : skipsize;
|
||
|
skipsize -= i;
|
||
|
|
||
|
/* How do I check error condition when comping -mno-cygwin? */
|
||
|
#if !defined(__MINGW32__)
|
||
|
errno = 0;
|
||
|
#endif
|
||
|
rd += read(archan, buffer, i);
|
||
|
#if !defined(__MINGW32__)
|
||
|
if (errno)
|
||
|
{
|
||
|
pipeit("\nRead error\n");
|
||
|
f_err = ERR_READ;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
buffer += i;
|
||
|
len -= i;
|
||
|
|
||
|
if (!skipsize) // if block is continued on next volume
|
||
|
if (head.HEAD_FLAGS & ACE_SP_AFTER && !proc_next_vol())
|
||
|
break;
|
||
|
}
|
||
|
#ifdef CRYPT
|
||
|
if (head.HEAD_TYPE == FILE_BLK && (head.HEAD_FLAGS & ACE_PASSW))
|
||
|
decrypt(cbuffer, rd);
|
||
|
#endif /* CRYPT */
|
||
|
|
||
|
return (rd > l ? l : rd);
|
||
|
}
|
||
|
|
||
|
void crc_print(void) // checks CRC, prints message
|
||
|
{
|
||
|
INT crc_not_ok = rd_crc != fhead.CRC32; /* check CRC of file */
|
||
|
|
||
|
if (!f_err) // print message
|
||
|
{
|
||
|
pipeit(crc_not_ok ? " CRC-check error" : " CRC OK");
|
||
|
flush;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void analyze_file(void) // analyzes one file (for solid archives)
|
||
|
{
|
||
|
pipeit("\n Analyzing");
|
||
|
flush;
|
||
|
while (!cancel() && (dcpr_adds_blk(buf_wr, size_wrb))) // decompress only
|
||
|
;
|
||
|
crc_print();
|
||
|
}
|
||
|
|
||
|
void extract_file(void) // extracts one file
|
||
|
{
|
||
|
INT rd;
|
||
|
|
||
|
pipeit("\n Extracting");
|
||
|
flush; // decompress block
|
||
|
while (!cancel() && (rd = dcpr_adds_blk(buf_wr, size_wrb)))
|
||
|
{
|
||
|
if (write(wrhan, buf_wr, rd) != rd) // write block
|
||
|
{
|
||
|
pipeit("\nWrite error\n");
|
||
|
f_err = ERR_WRITE;
|
||
|
}
|
||
|
}
|
||
|
crc_print();
|
||
|
}
|
||
|
|
||
|
/* extracts or tests all files of the archive
|
||
|
*/
|
||
|
void extract_files(int nopath, int test)
|
||
|
{
|
||
|
CHAR file[PATH_MAX];
|
||
|
|
||
|
while (!cancel() && read_header(1))
|
||
|
{
|
||
|
if (head.HEAD_TYPE == FILE_BLK)
|
||
|
{
|
||
|
comment_out("File comment:"); // show file comment
|
||
|
ace_fname(file, &head, nopath); // get file name
|
||
|
pipeit("\n%s", file);
|
||
|
flush;
|
||
|
dcpr_init_file(); // initialize decompression of file
|
||
|
if (!f_err)
|
||
|
{
|
||
|
if (test ||
|
||
|
(wrhan = create_dest_file(file, (INT) fhead.ATTR))<0)
|
||
|
{
|
||
|
if (test || adat.sol)
|
||
|
analyze_file(); // analyze file
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
extract_file(); // extract it
|
||
|
#ifdef DOS // set file time
|
||
|
_dos_setftime(wrhan, (USHORT) (fhead.FTIME >> 16), (USHORT) fhead.FTIME);
|
||
|
#endif
|
||
|
close(wrhan);
|
||
|
#ifdef DOS // set file attributes
|
||
|
_dos_setfileattr(file, (UINT) fhead.ATTR);
|
||
|
#endif
|
||
|
#ifdef AMIGA
|
||
|
{ // set file date and time
|
||
|
struct DateTime dt;
|
||
|
char Date[9], Time[9];
|
||
|
ULONG tstamp=fhead.FTIME;
|
||
|
|
||
|
sprintf(Date, "%02d-%02d-%02d", ts_year(tstamp)-1900, ts_month(tstamp), ts_day(tstamp));
|
||
|
sprintf(Time, "%02d:%02d:%02d", ts_hour(tstamp), ts_min(tstamp), ts_sec(tstamp));
|
||
|
|
||
|
dt.dat_Format = FORMAT_INT;
|
||
|
dt.dat_Flags = 0;
|
||
|
dt.dat_StrDate= Date;
|
||
|
dt.dat_StrTime= Time;
|
||
|
|
||
|
if (StrToDate(&dt))
|
||
|
SetFileDate(file, &dt.dat_Stamp);
|
||
|
}
|
||
|
#endif
|
||
|
if (f_err)
|
||
|
remove(file);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
unsigned percentage(ULONG p, ULONG d)
|
||
|
{
|
||
|
return (unsigned)( d ? (d/2+p*100)/d : 100 );
|
||
|
}
|
||
|
|
||
|
void list_files(int verbose)
|
||
|
{
|
||
|
ULONG size =0,
|
||
|
psize=0,
|
||
|
tpsize;
|
||
|
CHAR file[PATH_MAX];
|
||
|
|
||
|
pipeit("Date |Time |Packed |Size |Ratio|File\n");
|
||
|
|
||
|
while (!cancel() && read_header(1))
|
||
|
{
|
||
|
if (head.HEAD_TYPE == FILE_BLK)
|
||
|
{
|
||
|
ULONG ti=fhead.FTIME;
|
||
|
ace_fname(file, &head, verbose ? 0 : 1); // get file name
|
||
|
|
||
|
size += fhead.SIZE;
|
||
|
psize +=
|
||
|
tpsize = fhead.PSIZE;
|
||
|
files++;
|
||
|
|
||
|
while (head.HEAD_FLAGS & ACE_SP_AFTER)
|
||
|
{
|
||
|
skipsize=0;
|
||
|
if (!proc_next_vol())
|
||
|
break;
|
||
|
psize += fhead.PSIZE;
|
||
|
tpsize+= fhead.PSIZE;
|
||
|
}
|
||
|
if (!f_err)
|
||
|
pipeit("%02u.%02u.%02u|%02u:%02u|%c%c%9lu|%9lu|%4u%%|%c%s\n",
|
||
|
ts_day (ti), ts_month(ti), ts_year(ti)%100,
|
||
|
ts_hour(ti), ts_min (ti),
|
||
|
fhead.HEAD_FLAGS & ACE_SP_BEF ? '<' : ' ',
|
||
|
fhead.HEAD_FLAGS & ACE_SP_AFTER ? '>' : ' ',
|
||
|
tpsize, fhead.SIZE, percentage(tpsize, fhead.SIZE),
|
||
|
fhead.HEAD_FLAGS & ACE_PASSW ? '*' : ' ',
|
||
|
file
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
if (!f_err)
|
||
|
{
|
||
|
pipeit("\n %9lu|%9lu|%4u%%| %u file%s",
|
||
|
psize,
|
||
|
size,
|
||
|
percentage(psize, size),
|
||
|
files,
|
||
|
(char*)(files == 1 ? "" : "s")
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void showhelp(void)
|
||
|
{
|
||
|
pipeit("\n"
|
||
|
"Usage: UNACE <command> <archive[.ace]>\n"
|
||
|
"\n"
|
||
|
"Where <command> is one of:\n"
|
||
|
"\n"
|
||
|
" e Extract files\n"
|
||
|
" l List archive\n"
|
||
|
" t Test archive integrity\n"
|
||
|
" v List archive (verbose)\n"
|
||
|
" x Extract files with full path"
|
||
|
);
|
||
|
}
|
||
|
|
||
|
int include_unpack(char *bleah) // processes the archive
|
||
|
{
|
||
|
CHAR *s;
|
||
|
|
||
|
strcpy(aname, bleah);
|
||
|
|
||
|
init_unace(); // initialize unace
|
||
|
|
||
|
if (!(s = (CHAR *) strrchr(aname, DIRSEP)))
|
||
|
s = aname;
|
||
|
if (!strrchr(s, '.'))
|
||
|
strcat(aname, ".ACE");
|
||
|
|
||
|
if (open_archive(1)) // open archive to process
|
||
|
{
|
||
|
if (adat.vol_num)
|
||
|
pipeit("\nFirst volume of archive required!\n");
|
||
|
else
|
||
|
list_files (0 );
|
||
|
|
||
|
#if !defined(__EMX__) && !defined(__OS2__)
|
||
|
fclose(farchan);
|
||
|
#endif
|
||
|
close(archan);
|
||
|
if (f_err)
|
||
|
{
|
||
|
pipeit("\nError occurred\n");
|
||
|
if (f_criterr)
|
||
|
pipeit("Critical error on drive %c\n", f_criterr);
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
f_err = ERR_CLINE;
|
||
|
|
||
|
done_unace();
|
||
|
return (f_err);
|
||
|
}
|
||
|
|