/*************************************************
		LHarc version 1.13b (c)Yoshi, 1988-89.
		main module : 1989/ 5/14

HTAB = 4
*************************************************/

#include <stdio.h>
#include <io.h>
#include <ctype.h>
#include <string.h>
#include <dir.h>
#include <dos.h>
#include <alloc.h>
#include <fcntl.h>
#include <process.h>
#include <stdlib.h>
#include <errno.h>

#define UNKNOWNERR	0
#define INVCMDERR	1
#define MANYPATERR	2
#define NOARCNMERR	3
#define NOFNERR		4
#define NOARCERR	5
#define RENAMEERR	6
#define MKTMPERR	7
#define DUPFNERR	8
#define TOOMANYERR	9
#define TOOLONGERR	10
#define NOFILEERR	11
#define MKFILEERR	12
#define RDERR		13
#define WTERR		14
#define MEMOVRERR	15
#define INVSWERR	16
#define CTRLBRK		17
#define NOMATCHERR	18
#define COPYERR		19
#define NOTLZH		20
#define OVERWT		21
#define MKDIR		22
#define MKDIRERR	23
#define CRCERR		24
#define RDONLY		25

#define MAXBLK		64

#define MAX_PAT	64
#define FAULT 0
#define SUCCS ~FAULT
#define getch() (bdos(0x08, 0, 0) & 0xff)

typedef unsigned char uchar;
typedef unsigned int uint;
typedef unsigned long ulong;

FILE  *file1, *file2, *file3, *infile, *outfile;
int   cmd;						/* command */
int   cmdupdate;				/* Not zero, when updating archive */
int   errorlevel = 0;			/* return value */
long  textsize;					/* file size before compression */
long  codesize;					/* file size after  compression */
long  arcpos0;					/* position pointer for archive */
uchar *basedir;					/* base directory (home-directory) */
uchar *wdir = "";				/* work directory */
uchar workdir[MAXPATH];			/* work directory */
uchar *infname;					/* input  file name (for error message) */
uchar *outfname;				/* output file name (for error message)*/
int   attr;						/* file attributes for search files */
int   Nfile;					/* number of files which was found */
int   fblft, fbsize;			/* for buffers handling file names */
uint  seg;						/* beginning segment of file name buffers */
uchar far *fbuf, huge *fbnxt;	/* pointer to file name buffers */
uchar arcname[MAXPATH];			/* archive name */
uchar pathname[MAXPATH];		/* work for file name (extracting) */
uchar filename[MAXPATH];		/* work for file name (for file in archive) */
int   patno;					/* No. of path names in command line */
uchar patfile[MAX_PAT][12];		/* file name in command line */
uchar *patpath[MAX_PAT];		/* directory name in command line */
int   patcnt[MAX_PAT];			/* counts which this name matched */
uchar patnul[] = "*.*";			/* default path name */
uchar backup1[MAXPATH], backup2[MAXPATH];
								/* temporary file names */
uchar flg_r = 0, flg_p = 0, flg_x = 0, flg_m = 0;
uchar flg_a = 0, flg_c = 0, flg_v = 0, flg_w = 0;
uchar flg_n = 0, flg_t = 0;
								/* switches */
uchar copying = 0;				/* turn on when copying temporary file */
uchar crcflg = 0;				/* force copyfile() to calculate CRC*/
uchar tstflg = 0;				/* turn on in 't' command */
uchar swchar;					/* switch character */
uchar *pager = "less";			/* utility used in 'p /v' mode */
uint  crc;						/* calculated CRC code */
uchar *keyword = "";			/* key word for auto-execution */

struct filebuf {				/* work for file name buffer */
	void far *next;
	uchar fpos;					/* position of file name */
	uchar cpos;					/* position of effective path name */
	uchar dir[MAXDIR];			/* full path name */
} fb0, fb1;

extern char  *errmes[];			/* array of pointers to error messages */
extern uchar  use[];			/* usage */
extern uchar buf2[], buf3[];	/* I/O buffers */

/* routines for handling Japanese strings */
extern uchar *j_strupr(uchar *p);
extern int    j_strcmp(char *p, char *q);
extern uchar *j_strchr(char *p, uint c);
extern uchar *j_strrchr(char *p, uint c);

extern int    getfattr(uchar *fn);			/* get file attributes */
extern void   setfattr(uchar *fn, int attr);/* set file attributes */
extern uchar  getswchar(void);				/* get switch character */
extern void   slash2yen(uchar *p);			/* convert '/' to '\' */

extern char M_NOMATCHERR[];
extern char M_COPYERR[];
extern char M_NOTLZH[];
extern char M_OVERWT[];
extern char M_MKDIR[];
extern char M_MKDIRERR[];
extern char M_CRCERR[];
extern char M_RDONLY[];

union stamp {					/* time stamp */
	struct ftime s;
	ulong u;
};

union stamp arcstamp = {0L};	/* time stamp for archive */

struct LzHead {					/* file header */
	uchar HeadSiz, HeadChk, HeadID[5];
	ulong PacSiz, OrgSiz;
	union stamp Ftime;
	uint  Attr;
	uchar Fname[MAXPATH];
} Hdr1, Hdr2;

struct exeHead {				/* EXE header for SFX */
	uint exeID, byte, page, reloc;
	uint hdrsize, minalloc, maxalloc, initSS;
	uint initSP, chksum, initIP, initCS;
	uint reloctab, overlay, dummy1, dummy2;
} ExeHdr = {
	'MZ', 0, 0, 0, 2, 0, 0xffff, 0xfff0,
	0x100, 0, 0x100, 0xfff0, 0x1e, 0, 0, 0
};

/*******************************
		display helps
*******************************/
void usage(void)
{
	fputs(use, stdout);
	exit(0);
}

/*******************************
		display a message
*******************************/

void message(uchar *p, uchar *q)
{
	printf("%s", p);
	if (q) {
		printf(" : '%s'", q);
	}
	printf("\n");
}

/*******************************
	process for an error
*******************************/
void error(int errcode, uchar *p)
{
	if (copying) {						/* error during copying temporary? */
		fprintf(stderr, "\n%s\n", M_COPYERR);
		fclose(file1);
		unlink(arcname);				/* erase incomplete archive */
		file1 = NULL;
	}
	fprintf(stderr, "\n%s", errmes[errcode]);
	if (p) {
		fprintf(stderr, " : '%s'", p);
	}
	fprintf(stderr, "\n");
	if (file3) {
		fclose(file3);
		if (!cmdupdate)					/* during extracting */
			unlink(pathname);           /* delete the file */
	}
	if (file1) {
		fclose(file1);
		if (cmdupdate)					/* during updating */
			rename(backup1, arcname);	/* recover old archive */
	}
	if (file2) {
		fclose(file2);
		if (!copying) {					/* if not copying */
			unlink(backup2);			/* delete temporary */
		}
	}
	if (copying)
		exit(3);
	else
		exit(2);
}

/*******************************
		handle user break
*******************************/
int userbreak(void)
{
	error(CTRLBRK, NULL);
}

/*******************************
	fopen with detecting error
*******************************/
FILE *e_fopen(uchar *fname, uchar *mode, int errID)
{
	FILE *f;

	if ((f = fopen(fname, mode)) == NULL) {
		if (errno == EACCES)
			error(RDONLY, fname);
		error(errID, fname);
	}
	return f;
}

/*******************************
	rename with detecting error
*******************************/
void e_rename(uchar *old, uchar *new)
{
	if (rename(old, new))
		error(RENAMEERR, old);
}

/*******************************
		ask 'Y' or 'N'
*******************************/
uint getyn(void)
{
	uint yn;

	do {
		yn = toupper(getch());
	} while (yn != 'Y' && yn != 'N');
	fprintf(stderr, "%c\n", yn);
	return yn;
}

/*******************************
  file name -> internal format
*******************************/
void extfn(uchar *p, uchar *pb)
{
	int i;
	uchar * q;

	if ((q = j_strrchr(p, '\\')) != NULL ||
	    (q = j_strchr(p, ':')) != NULL)
		p = q + 1;
	q = p;
	for (i = 8; i > 0; i--, pb++) {
		switch (*p) {
		case '*':
			*pb = '?';
			break;
		case '.':
		case '\0':
			*pb = ' ';
			break;
		default:
			*pb = *p++;
		}
	}
	while (+(*p != '\0' && *p++ != '.'));
	for (i = 3; i > 0; i--, pb++) {
		switch (*p) {
		case '*':
			*pb = '?';
			break;
		case '\0':
			*pb = ' ';
			break;
		default:
			*pb = *p++;
		}
	}
	*q = *pb = '\0';
}

/*******************************
  internal format -> file name
*******************************/
void packfn(uchar *p, uchar *q)
{
	int i;

	for (i = 8; i > 0; i--) {
		if (*q != ' ')
			*p++ = *q;
		q++;
	}
	*p++ = '.';
	for (i = 3; i > 0; i--) {
		if (*q != ' ')
			*p++ = *q;
		q++;
	}
	if (p[-1] == '.')
		p--;
	*p = '\0';
}

/*******************************
  get back to parent directory
*******************************/
uchar *backpath(uchar *p)
{
	uchar *q;

	if ((q = j_strrchr(p, '\\')) == NULL &&
		(q = j_strchr(p, ':')) == NULL) {
		*p = '\0';
		q = p;
	} else {
		*++q = '\0';
	}
	return q;
}

/***********************************
  whether path name was used or not
***********************************/
void tstpat(void)
{
	int i, cnt;
	uchar path[MAXPATH];

	cnt = 0;
	for (i = 0; i < patno; i++)
		cnt += patcnt[i];
	if (cnt == 0)						/* no file matched */
		error(NOFILEERR, NULL);
	for (i = 0; i < patno; i++) {
		if (patcnt[i] == 0) {			/* if any path name was not used */
			packfn(stpcpy(path, patpath[i]), patfile[i]);
			fprintf(stderr, "%s : '%s'\n", M_NOMATCHERR, path);
			errorlevel = 1;				/* display warning */
		}
	}
}

/*******************************
		make a file header
*******************************/
void sethdr(uchar *fn, uint attr, struct LzHead *h)
{
	uint l;

	memset(h, 0, sizeof(struct LzHead));
	l = strlen(fn);						/* length of file name */
	h -> Fname[0] = l;
	memcpy(h -> Fname + 1, fn, l);
	l += 20 + 2;						/* size of header */
	h -> HeadSiz = l;
	fseek(file3, 0L, SEEK_END);
	h -> OrgSiz = textsize = ftell(file3);	/* original size of a file */
	h -> PacSiz = codesize = 0;
	rewind(file3);
	getftime(fileno(file3), &(h -> Ftime.s));	/* get time stamp */
	h -> Attr = attr;					/* file attributes */
	memcpy(h -> HeadID, "-lh1-", 5);	/* at first, select the method -lh1- */
}

/*******************************
	write a file header
*******************************/
void wthdr(struct LzHead *h)
{
	arcpos0 = ftell(file2);		/* memorize this position */
	if (fwrite(h, h -> HeadSiz + 2, 1, file2) == 0)
		error(WTERR, backup2);
}

/*******************************
  calculate check-sum of header
*******************************/
uchar mksum(struct LzHead *h)
{
	uchar *p, *q;
	uchar i;

	p = (uchar *)h + 2;
	q = p + h -> HeadSiz;
	for (i = 0; p < q; p++)
		i += *p;
	return i;
}

/*******************************
	remake file header & write
*******************************/
void remkhdr(struct LzHead *h)
{
	int flg;
	long arcpos1;

	flg = 0;
	h -> PacSiz = codesize;				/* packed size of a file */
	*(uint *)(h -> Fname + *(h -> Fname) + 1) = crc;
	if (h -> OrgSiz <= codesize) {		/* if packed size >= original size */
		flg = 1;						/* select method "simple copy" */
		memcpy(h -> HeadID, "-lh0-", 5);
		h -> PacSiz = h -> OrgSiz;
	}
	h -> HeadChk = mksum(h);			/* header sum */
	arcpos1 = ftell(file2);				/* memorize this position */
	fseek(file2, arcpos0, SEEK_SET);	/* seek to header */
	fwrite(h, h -> HeadSiz + 2, 1, file2); /* rewrite header */
	if (flg) {
		rewind(file3);
		copyfile(file3, file2, h -> OrgSiz);	/* do simple copy */
	} else {
		fseek(file2, arcpos1, SEEK_SET);		/* return to the end */
	}
}

/*******************************
		get a file header
*******************************/
uchar *gethdr(FILE *arc, struct LzHead *h)
{
	uchar *q, i;

	if ((h -> HeadSiz = getc(arc)) <= 0 ||
	    h -> HeadSiz > sizeof(struct LzHead) - 3 ||
	    fread(&(h -> HeadChk), h -> HeadSiz + 1, 1, arc) == 0)
	{								/* read file header */
		return NULL;
	}
	if (mksum(h) != h -> HeadChk)	/* if sum is wrong */
		return NULL;
	i = *(h -> Fname);
	strncpy(filename, h -> Fname + 1, i);
	*(filename + i) = '\0';
	if ((q = j_strrchr(filename, '\\')) == NULL &&
	    (q = j_strchr(filename, ':')) == NULL)
		q = filename;
	else
		q++;
	return q;					/* return the portion of file name */
}

/*******************************
	test match of file name
*******************************/
int matchpat(uchar *p)
{
	uchar buf[12], name[MAXPATH];
	int i, j, retcode;

	retcode = FAULT;
	strcpy(name, p);
	extfn(name, buf);
	for (i = 0; i < patno; i++) {
		if (flg_p || *patpath[i]) {		/* should compare full path ? */
			if (j_strcmp(name, patpath[i]))
				continue;
		}
		for (j = 0; j < 11; j++) {		/* compare file name */
			if (patfile[i][j] != buf[j] && patfile[i][j] != '?')
			break;
		}
		if (j == 11) {					/* if matched */
			patcnt[i]++;
			retcode = SUCCS;
		}
	}
	return retcode;
}

/*******************************
		ratio * 1000
*******************************/
uint ratio(ulong a, ulong b)
{
	int i;

	if (!b) return 0;			/* if diviser == 0 */
	for (i = 0; i < 3 && a < 0x19999999; i++) {
		a *= 10;				/* while not overflow */
	}							/* upto 1000 times */
	for (; i < 3; i++) {		/* the case of overflow */
		b /= 10;
	}
	a += b / 2;					/* for round up */
	return (a / b);				/* return (a * 1000 / b) */
}

/*******************************
		compare names
*******************************/
int cmpname(uchar *f0, uchar *f1, uchar *p0, uchar *p1)
{
	int c;

	c = j_strcmp(f0, f1);				/* compare only file names */
	if (c == 0) {
		c = strlen(p0) - strlen(p1);	/* compare lengths of path names */
		if (c == 0) {
			c = j_strcmp(p0, p1);		/* compare path names */
		}
	}
	return c;
}

/*******************************
	regist file names
*******************************/
void regfile(uchar *p, uchar *q, uchar *f)
/*
	p: full path name including base directory
	q: directory name to be registed
	f: file name
*/
{
	uchar path[MAXPATH];
	struct filebuf far *f0;
	struct filebuf far *f1;
	uchar *s;
	int c, size;

	if (strstr(f, "LHARC.)1(") || strstr(f, "LHARC.)2("))
		return;					/* temporary file ? */
	stpcpy(stpcpy(path, q), f);
	stpcpy(s = stpcpy(fb1.dir, p), f);
	fb1.fpos = s - (uchar *)&fb1;
	fb1.cpos = flg_x ? (q - p) + (fb1.dir - (uchar *)&fb1) : fb1.fpos;
	if (fbuf == NULL) {				/* for first entry */
		if (allocmem(fbsize = 0x100, &seg) != -1)
			error(MEMOVRERR, NULL);
		fbuf = (uchar far *)fbnxt = MK_FP(seg, 0);
		fblft = 0x1000 - 4;
		*(long far *)fbuf = 0;
		fbnxt += 4;
	}
	f0 = (struct filebuf far *)fbuf;
	do {					/* search position in which should be inserted*/
		f1 = f0;
		if ((f0 = f0 -> next) == NULL)
			break;
		fb0 = *f0;
		c = cmpname((uchar *)&fb0 + fb0.fpos, (uchar *)&fb1 + fb1.fpos,
			        (uchar *)&fb0 + fb0.cpos, (uchar *)&fb1 + fb1.cpos);
	} while (c < 0);

	if (f0 && c == 0 && j_strcmp(fb0.dir, fb1.dir)) {
		error(DUPFNERR, (uchar *)&fb1 + fb1.cpos);
	}						/* same registing names of different files */

	if (f0 == NULL || c) {				/* do regist */
		size = strlen(fb1.dir) +
			  (fb1.dir - (uchar *)&fb1) + 1;
		if (fblft < sizeof(struct filebuf)) {	/* if buffer is short */
			if (setblock(seg, fbsize += 0x100) != -1)
				error(TOOMANYERR, NULL);
			fblft += 0x1000;
		}
		fb1.next = f0;
		f0 = (struct filebuf far *)fbnxt;
		f1 -> next = f0;
		*f0 = fb1;
		fblft -= size;
		fbnxt += size;
	}
}

/*******************************
  recursive collection of files
*******************************/
int travel(uchar *p, uchar *q, uchar *f)
{
	struct ffblk ffb;
	static uchar buf[12];
	uchar *r, *s;
	int done, cnt, j;

	cnt = 0;
	if (flg_r == 1 || j_strrchr(q, '\\') == q + strlen(q) - 1) {
		stpcpy(s = q + strlen(q), "*.*");
	}
	done = findfirst(p, &ffb, attr);	/* search the first file */
	s = backpath(q);
	while (! done) {
		if (ffb.ff_attrib & 0x10) {		/* if this is a sub-directory */
			if (ffb.ff_name[0] != '.') {
				r = stpcpy(stpcpy(s, ffb.ff_name), "\\");
				if (r - p > MAXPATH)
					error(TOOLONGERR, p);
				cnt += travel(p, q, f);	/* search recursively */
				*s = '\0';
			}
		} else							/* if this is a file */
		if (flg_r == 2) {				/* in /r2 mode */
			cnt++;
			regfile(p, q, ffb.ff_name);			/* regist name */
		} else {						/* in /r+ mode */
			stpcpy(s, ffb.ff_name);
			extfn(s, buf);
			for (j = 0; j < 11; j++) {			/* test file names */
				if (f[j] != buf[j] && f[j] != '?')
				break;
			}
			if (j == 11) {
				cnt++;
				regfile(p, q, ffb.ff_name);		/* if match, regist */
			}
		}
		done = findnext(&ffb);
	}
	return cnt;							/* number of registed files */
}

/**********************************
 non-recursive collection of files
**********************************/
int findfile(uchar *p, uchar *q)
{
	struct ffblk ffb;
	int done, cnt;

	cnt = 0;
	done = findfirst(p, &ffb, attr);
	backpath(p);
	while (! done) {
		cnt++;
		regfile(p, q, ffb.ff_name);
		done = findnext(&ffb);
	}
	return cnt;
}

/*******************************
	make file lists to append
*******************************/
void mklist(void)
{
	uchar path[MAXPATH], *p, *q, *r;
	int i, cnt;

	Nfile = 0;
	if (flg_a) {						/* set attributes for search */
		attr = 0x07;
	} else {
		attr = 0;
	}
	if (flg_r) {
		attr |= 0x10;
	}
	for (i = 0; i < patno; i++) {
		p = patpath[i];
		q = path;
		if (*p && p[1] == ':') {		/* if path name includes drive */
			q = stpcpy(path, p);			/* ignore base directory */
			r = path + 2;					/* don't regist drive name */
		} else {
			q = stpcpy(r = stpcpy(path, basedir), p);
		}
		if (flg_r == 1) {				/* /r+ mode */
			cnt = travel(path, r, patfile[i]);
		} else if (flg_r > 1) {			/* /r2 mode */
			packfn(q, patfile[i]);
			cnt = travel(path, r, NULL);
		} else {						/* /r- mode */
			packfn(q, patfile[i]);
			cnt = findfile(path, r);
		}
		Nfile += patcnt[i] = cnt;
	}
}

/*******************************
		make file header
*******************************/
uchar *mkhdr(struct filebuf far *f, struct LzHead *h)
{
	int attr;

	fb0 = *f;
	attr = getfattr(fb0.dir);
	file3 = e_fopen(fb0.dir, "rb", RDERR);
	sethdr((uchar *)&fb0 + fb0.cpos, attr, h);
	return (uchar *)&fb0 + fb0.fpos;	/* position of file name */
}

uint blkcnt;
uint curcnt;
uint nxtcnt;
uint namcnt;

/*******************************
	calculate and display
		for indicator
*******************************/
void blkdisp(long l, char *s) {
	uint i;

	if (flg_n == 0) {
		fprintf(stderr, "\n  %s :  ", s);
		blkcnt = (l + 4095) / 4096;
		i = (blkcnt > MAXBLK) ? MAXBLK : blkcnt;
		while (i-- > 0) {
			putc('.', stderr);
		}
		fprintf(stderr, "\r  %s :  ", s);
		curcnt = nxtcnt = 0;
	} else {
		curcnt = 0;
		nxtcnt = -1;
	}
}

/*******************************
	let cursor back after
	displaying indicator
*******************************/
void curback(void)
{
	if (flg_n == 0) {
		fprintf(stderr, "\r  ");
	}
}

/*******************************
		freeze a file
*******************************/
void freeze(uchar *p)
{
	if (arcstamp.u < Hdr2.Ftime.u)
		arcstamp.u = Hdr2.Ftime.u;
	printf("%s ", p);
	blkdisp(Hdr2.OrgSiz, "Freezing ");
	wthdr(&Hdr2);
	setvbuf(file3, buf3, _IOFBF, 4096);
	infile = file3;
	outfile = file2;
	infname = p;
	crc = 0;
	Encode();
	remkhdr(&Hdr2);
	curback();
	printf("Frozen(%3d%%)\n", ratio(Hdr2.PacSiz, Hdr2.OrgSiz) / 10);
}

/*******************************
		Copy a file from
			old archive
*******************************/
void copyold()
{
	if (arcstamp.u < Hdr1.Ftime.u)
		arcstamp.u = Hdr1.Ftime.u;
	wthdr(&Hdr1);				/* copy from old archive */
	copyfile(file1, file2, Hdr1.PacSiz);
}

/*******************************
  	execute one of a, u, m
		commands
*******************************/
int execappend(void)
{
	struct filebuf far *f0;
	uchar *p, *q;
	int c, d;
	int cnt = 0;

	q = file1 ? gethdr(file1, &Hdr1) : NULL;	/* read header from old arc */
	if ((f0 = ((struct filebuf far *)fbuf) -> next) != NULL) {
		p = mkhdr(f0, &Hdr2);			/* make header from the file list */
	}
	while (1) {
		if (f0 == NULL) {
			d = 1;
			if (q == NULL)
				break;
		} else if (q == NULL) {
			d = -1;
		} else {
			d = cmpname(p, q, (uchar *)&fb0 + fb0.cpos, filename);
		}
		c = d;
		if (c == 0) {
			if (flg_c || Hdr1.Ftime.u < Hdr2.Ftime.u) {
				c = -1;
			} else {
				c = 1;
			}
		}
		if (c < 0) {					/* freeze a new file */
			if (d == 0) {
				fseek(file1, Hdr1.PacSiz, SEEK_CUR);
				q = gethdr(file1, &Hdr1);		/* skip a file in old */
			}									/* archive */
			freeze(fb0.dir);
			fclose(file3);
			cnt++;
			if ((f0 = fb0.next) != NULL) {		/* make header of the next */
				p = mkhdr(f0, &Hdr2);			/* file in file list */
			}
		} else {						/* copy a file from old archive */
			if (d == 0) {
				fclose(file3);
				if ((f0 = fb0.next) != NULL) {	/* make header of the next */
					p = mkhdr(f0, &Hdr2);		/* file in file list */
				}
			}
			copyold();
			q = gethdr(file1, &Hdr1);			/* get the next header */
		}										/* in old archive */
	}
	return cnt;
}

/*******************************
	delete files after
	execution of updating
	in 'm' command
*******************************/
void delfile(void)
{
	struct filebuf far *f0;
	struct filebuf fb0;

	f0 = (struct filebuf far *)fbuf;
	while ((f0 = f0 -> next) != NULL) {
		fb0 = *f0;
		unlink(fb0.dir);
	};
}

/*******************************
	open an old archive
*******************************/
void openarc1(void)
{
	uchar *p, *q;

	file1 = e_fopen(infname = arcname, "rb", NOARCERR);
	q = buf2 - 5 + fread(buf2, 1, 2048, file1);
	for (p = buf2; p < q; p++) {
		if (p[0] == '-' && p[1] == 'l' && p[4] == '-') break;
	}
	if (p >= q) {
		error(NOFILEERR, arcname);
	}
	fseek(file1, (long)(p - buf2 - 2), SEEK_SET);
}

/*******************************
	open an archive in rd/wt
	for testing read-only
*******************************/
void openrwarc1(void)
{
	file1 = e_fopen(arcname, "r+b", NOARCERR);
}

/*******************************
	close an old archive
	& rename to temporary
*******************************/
void openbackup1(void)
{
	fclose(file1);
	stpcpy(backpath(strcpy(backup1, arcname)), "lharc.)1(");
	e_rename(arcname, backup1);
	file1 = fopen(infname = backup1, "rb");
}

/*******************************
	open a temporary file
	for a new archive
*******************************/
void openbackup2(void)
{
	if (flg_w) {
		stpcpy(stpcpy(backup2, workdir), "lharc.)2(");
	} else {
		strcat(backpath(strcpy(backup2, arcname)), "lharc.)2(");
	}
	file2 = e_fopen(outfname = backup2, "w+b", MKTMPERR);
	setvbuf(file2, buf2, _IOFBF, 4096);
}

/*******************************
   set time & close an archive
*******************************/
void stclosearc(FILE *f)
{
	if (flg_t) {
		fflush(f);
		setftime(fileno(f), &arcstamp.s);
	}
	fclose(f);
}

/*******************************
	end-of-job process
	in making new archive
*******************************/
void endofupdate(int cnt)
{
	stclosearc(file1);
	tstpat();
	if (cnt) {					/* if any files are manipulated */
		if (file1)
			if (unlink(backup1))		/* delete an old archive */
				printf("debug : Failed in deleting '%s'.", backup1);
		if ((arcpos0 = ftell(file2)) != 0) {
			if (putc(0, file2) == EOF)
				error(WTERR, backup2);
			if (flg_w) {			/* if work directory is assigned */
				rewind(file2);
				infname = backup2;
				copying = 1;			/* copy temporary to new archive */
				file1 = e_fopen(outfname = arcname, "wb", MKFILEERR);
				printf("Copying Temp to Archive ...");
				copyfile(file2, file1, arcpos0 + 1);
				printf("\n");
				copying = 0;
				stclosearc(file1);
				fclose(file2);
				unlink(backup2);
			} else {
				stclosearc(file2);	/* else rename temporary to archive */
				rename(backup2, arcname);
			}
		} else {
			fclose(file2);
			unlink(backup2);
		}
	} else {					/* if no change was made in archive */
		fclose(file2);
		unlink(backup2);
		rename(backup1, arcname);	/* restore the old archive */
	}
}

/*******************************
		a, u, m command
*******************************/
void append(void)
{
	int cnt;

	file1 = fopen(arcname, "r+b");
	if (file1) {
		openbackup1();				/* if archive presents, rename to temp */
	} else {
		if (errno == EACCES)
			error(RDONLY, arcname);	/* read-only error */
	}
	mklist();						/* make a file list */
	if (Nfile == 0) {
		error(NOFILEERR, NULL);
	}
	if (file1) {
		message("Updating archive", arcname);
	} else {
		message("Creating archive", arcname);
	}
	openbackup2();					/* open temporary for new archive */
	cnt = execappend();				/* execute updating archive */
	endofupdate(cnt);				/* end-of-job process */
	if (cmd == 'M')
		delfile();					/* if 'm' command, delete files */
	freemem(seg);
}

/*******************************
		f command
*******************************/
void freshen(void)
{
	uchar path[MAXPATH];
	int c;
	int	cnt = 0;

	openrwarc1();						/* open an archive */
	message("Freshening archive", arcname);
	openbackup1();						/* rename the archive to temp. */
	openbackup2();						/* open temp. for a new archive */
	while (gethdr(file1, &Hdr1)) {
		c = 0;
		if (matchpat(filename)) {
			stpcpy(stpcpy(path, basedir), filename);
			if ((file3 = fopen(path, "rb")) != NULL) {
				sethdr(filename, getfattr(path), &Hdr2);
				if (flg_c || Hdr1.Ftime.u < Hdr2.Ftime.u) {
					c = 1;              /* found the file to be updated */
				}
			}
		}
		if (c) {
			freeze(path);				/* do updating */
			cnt++;
			fseek(file1, Hdr1.PacSiz, SEEK_CUR);
		} else {
			copyold();
		}
		fclose(file3);
	}
	endofupdate(cnt);					/* end-of-job process */
}

/*******************************
	test the file which
	should be melted
*******************************/
int tstdir(uchar *name)
{
	uchar path[MAXPATH], *p, yn;
	struct ffblk ffb;

	p = name;
	if (*p && p[1] == ':')				/* skip a drive name */
		p += 2;
	if (*p == '\\')						/* skip a root mark('\') */
		p++;
	yn = flg_m ? 'Y' : 'N';
	while ((p = j_strchr(p, '\\')) != NULL) {	/* skip to next '\' */
		memcpy(path, name, p - name);
		path[p - name] = '\0';
		if (findfirst(path, &ffb, 0x1f)) {	/* Is there this directory? */
			if (yn == 'N') {
				fprintf(stderr, "'%s' : %s", name, M_MKDIR);
				yn = getyn();
			}
			if (yn == 'N') {
				return FAULT;
			} else {
				if (mkdir(path)) {			/* make directory */
					error(MKDIRERR, path);
				}
			}
		} else {
			if ((ffb.ff_attrib & 0x10) == 0) {
				error(MKDIRERR, path);	/* if the name isn't directory */
			}
		}
		p++;
	}
	if (! findfirst(name, &ffb, 0x1f)) {	/* if a file has the same name */
		if (ffb.ff_attrib & 0x01 && ffb.ff_attrib != Hdr1.Attr) {
        						/* if the file is read-only, */
								/* attributes must match */
			fprintf(stderr, "'%s' %s\n", M_RDONLY);
			return FAULT;
		}
		yn = 'Y';
		if (flg_c == 0) {
			if (((ulong)ffb.ff_fdate << 16) + (ulong)ffb.ff_ftime
					< Hdr1.Ftime.u) {	/* compare time stamps */
				yn = 'Y';
			} else {
				printf("Skipped : '%s' : New or same file exists.\n", name);
				yn = 'N';
			}
		}
		if (yn == 'Y' && flg_m == 0) {
			fprintf(stderr, "'%s' : %s", name, M_OVERWT);
			yn = getyn();				/* may overwrite? */
		}
		if (yn == 'N') {
			return FAULT;
		}
		setfattr(name, 0x20);			/* reset attributes */
	}
	return SUCCS;
}

/*******************************
	read header-ID (method)
*******************************/
int tstID(uchar *h)
{
	int m;
	static uchar IDpat[4][6] =
		{"-lz4-", "-lz5-", "-lh0-", "-lh1-"};

	m = 3;
	while (m >= 0 && memcmp(h, IDpat[m], 5)) {
		m--;
	}
	return m;
}

/*******************************
		e, x, p, t command
*******************************/
int extract(void)
{
	uchar *p, *q;
	int m;
	int cnt = 0;

	openarc1();							/* open an archive */
	setvbuf(file1, buf2, _IOFBF, 4096);
	message("Extract from", arcname);
	if (flg_v == 1)
		fprintf(file3, "Extract from '%s'\n", arcname);
	while ((p = gethdr(file1, &Hdr1)) != NULL) {
		if (matchpat(filename)) {
			arcpos0 = ftell(file1) + Hdr1.PacSiz;
			if (cmd == 'E') {			/* if extract command, */
				if (flg_x) {			/* get the destination path name */
					p = stpcpy(pathname, basedir);
					if (filename[0] == '\\') {
						p = pathname;
						if (*p && p[1] == ':') {
							p += 2;
						}
					}
					stpcpy(p, filename);
				} else {
					stpcpy(stpcpy(pathname, basedir), p);
				}
			}
			if (cmd != 'E' || tstdir(pathname)) {
				if ((m = tstID(Hdr1.HeadID)) < 0) {
					printf("Skipped : '%s' : Unknown method\n", pathname);
				} else if (cmd == 'T' && Hdr1.HeadSiz - Hdr1.Fname[0] != 22) {
					printf("Skipped : '%s' : CRC not supported\n", filename);
				} else {
					cnt++;
					p = "Melting ";
					q = "Melted ";
					switch (cmd) {
						case 'E':
							printf("%s ", pathname);
							file3 = fopen(outfname = pathname, "wb");
							break;
						case 'T':
							printf("%s ", filename);
							file3 = fopen("nul", "wb");
							p = "Testing ";
							q = "Tested ";
							break;
						case 'P':
							if (flg_v != 2)
								fprintf(file3, "<<< %s >>>\n", filename);
							if (flg_v)
								printf("%s ", filename);
							fflush(file3);
							setmode(fileno(file3), O_BINARY);
							break;
					}
					if ((ioctl(fileno(file3), 0) & 0x82) == 0x82) {
						flg_n = 1;		/* Console output ? */
					} else {
						setvbuf(file3, buf3, _IOFBF, 4096);
					}
					blkdisp(Hdr1.OrgSiz, p);
					outfile = file3;
					infile = file1;
					textsize = Hdr1.OrgSiz;
					crc = 0;
					if (m == 3) {
						Decode();		/* extract LHarc's file */
					} else if (m == 1) {
						DecodeOld();	/* extract LArc's file */
					} else {
						crcflg = 1;
						copyfile(infile, outfile, Hdr1.OrgSiz);
						crcflg = 0;		/* only stored file */
					}
					if (fflush(file3)) {
						error(WTERR, outfname);
					}
					if (cmd == 'E') {
						setftime(fileno(file3), &Hdr1.Ftime.s);
						fclose(file3);
						setfattr(pathname, Hdr1.Attr);
						file3 = NULL;
					} else if (cmd == 'T') {
						fclose(file3);
					} else {
						setmode(fileno(file3), O_TEXT);
						if (flg_v != 2)
							fprintf(file3, "\n");
					}
					curback();
					if (Hdr1.HeadSiz - Hdr1.Fname[0] == 22 &&
					    *(uint *)(&Hdr1.Fname[1] + Hdr1.Fname[0]) != crc) {
						errorlevel = 1;				/* test CRC */
						printf("CRC err\n");
					} else if (cmd != 'P' || flg_v != 0) {
						printf("%s\n", q);
					}
				}
			}
			fseek(file1, arcpos0, SEEK_SET);	/* move pointer to next file */
		} else {
			fseek(file1, Hdr1.PacSiz, SEEK_CUR);
		}
	}
	fclose(file1);
	file1 = NULL;
	return cnt;
}

/*******************************
		d command
*******************************/
void delete(void)
{
	int cnt = 0;

	openrwarc1();						/* open archive */
	if (patno == 0) {
		error(NOFNERR, NULL);
	}
	message("Updating archive", arcname);
	openbackup1();						/* rename to temporary name */
	openbackup2();						/* open another temporary file */
	while (gethdr(file1, &Hdr1)) {
		if (matchpat(filename)) {
			message("Deleting", filename);
			cnt++;
			fseek(file1, Hdr1.PacSiz, SEEK_CUR);	/* skip file */
		} else {
			copyold();
		}
	}
	endofupdate(cnt);					/* end-of-job process */
}

/*******************************
		s command
*******************************/
void self(void)
{
	uchar *p, /* *q,*/ buf[12], yn;
	int flg, i;
	long l, m, n;
	void sfx(void), sfx2(void);

	openarc1();								/* open archive */
	message("Making Sfx from archive", arcname);
	stpcpy(stpcpy(backup2, workdir), "lharc.)2(");
	file2 = e_fopen(outfname = backup2, "w+b", MKTMPERR);
											/* open temporary */
	while (gethdr(file1, &Hdr1)) {
		flg = 0;
		if (matchpat(filename)) {
			printf("Extracting '%s' ", filename);
			if (tstID(Hdr1.HeadID) < 2) {
				printf("(not supported) skipped.");
			} else {
				if (flg_x == 0 && (p = j_strrchr(filename, '\\')) != NULL) {
					p++;					/* delete directory part */
					Hdr1.Fname[0] = strlen(p);
					i = p - filename;
					Hdr1.HeadSiz -= i;
					memcpy(Hdr1.Fname + 1, Hdr1.Fname + 1 + i,
												Hdr1.Fname[0] + 2);
					Hdr1.HeadChk = mksum(&Hdr1);	/* recalculate sum */
				}
				wthdr(&Hdr1);
				copyfile(file1, file2, Hdr1.PacSiz);
				flg = 1;
			}
			printf("\n");
		}
		if (flg == 0) {
			fseek(file1, Hdr1.PacSiz, SEEK_CUR);
		}
	}
	fclose(file1);
	if (putc(0, file2) == EOF)			/* end-mark of archive */
		error(WTERR, backup2);
	if ((l = ftell(file2)) <= 1) {
		goto self9;
	}
	if (flg_x == 0) {
		m = (uchar*)sfx2 - (uchar*)sfx;		/* size of sfx routine */
	} else {
		m = (uchar*)usage - (uchar*)sfx2;
	}
	n = l + m;								/* total size of sfx file */
	rewind(file2);
	infname = backup2;
	extfn(arcname, buf);					/* make the name of sfx */
	p = stpcpy(pathname, basedir);
	strcpy(&buf[8], "COM");
	if (flg_x || n > 0xfe80ul) {
		strcpy(&buf[8], "EXE");
	}
	packfn(p, buf);
	if ((getfattr(pathname) & 0x8000) == 0) {	/* if the same name exists */
		fprintf(stderr, "'%s' : %s", pathname, M_OVERWT);
		yn = getyn();							/* may overwrite */
		if (yn == 'N') {
			goto self9;
		}
	}
	file3 = e_fopen(outfname = pathname, "wb", MKFILEERR);
	if (buf[8] == 'E') {						/* if .EXE */
		if (flg_x) {
			n = m;
		}
		n += 0x20;
		ExeHdr.page = (n + 511) / 512;
		ExeHdr.byte = n % 512;
		ExeHdr.minalloc = (flg_x ? 0x66c0 : 0x2560) / 0x10;
		if (fwrite(&ExeHdr, 0x20, 1, file3) == 0)
			error(WTERR, pathname);
	}
	movedata(_CS, (flg_x ? (unsigned)sfx2 : (unsigned)sfx),
			 _DS, (unsigned)buf2, m);
	if (flg_x) {
		memcpy(buf2 + 0x34, p, strlen(p));				/* large ver. */
	} else {
		*((uint *)buf2 + 1) = (l + m + 0x10f) / 0x10;	/* small ver. */
	}
	if (fwrite(buf2, m, 1, file3) == 0)		/* write sfx routine */
		error(WTERR, pathname);
	if (flg_x) {
		if (fwrite(keyword, strlen(keyword) + 1, 1, file3) == 0)
			error(WTERR, pathname);
	}
	copyfile(file2, file3, l);				/* write an archive */
	printf("\nCreated : '%s'\n", pathname);
	fclose(file3);
self9:
	fclose(file2);
	unlink(backup2);
}

/*******************************
		l, v command
*******************************/
void list(void)
{
	uint rt;
	uchar buf[79], *p;
	static uchar attr[7] = "ohs--a";
	int i, j, k, Fno;
	ulong Osize, Psize;

	Fno = Osize = Psize = 0;
	openarc1();										/* open archive */
	printf("Listing of archive : '%s'\n\n", arcname);
	printf("  Name          Original    Packed  Ratio"
		   "   Date     Time   Attr Type  CRC\n");
	printf("--------------  --------  -------- ------"
		   " -------- -------- ---- ----- ----\n");
	while ((p = gethdr(file1, &Hdr1)) != NULL) {
		if (matchpat(filename)) {
			rt = ratio(Hdr1.PacSiz, Hdr1.OrgSiz);
			sprintf(buf, "              %10lu%10lu %3d.%1d%% "
			        "%2d-%02d-%02d %2d:%02d:%02d ---w       %04X\n",
			        Hdr1.OrgSiz, Hdr1.PacSiz, rt / 10, rt % 10,
			        (Hdr1.Ftime.s.ft_year + 80) % 100, Hdr1.Ftime.s.ft_month,
			        Hdr1.Ftime.s.ft_day, Hdr1.Ftime.s.ft_hour,
			        Hdr1.Ftime.s.ft_min, Hdr1.Ftime.s.ft_tsec * 2,
			        *(uint *)(&Hdr1.Fname[1] + Hdr1.Fname[0]));
			memcpy(&buf[65], Hdr1.HeadID, 5);
			for (i = 0, j = 1; i < 6; i++, j <<= 1) {	/* attributes */
				if (Hdr1.Attr & j) {
					k = attr[i];
					if (i <= 2) {
						buf[63 - i] = k;
					} else {
						buf[60] = k;
					}
				}
			}
			if (Hdr1.HeadSiz - Hdr1.Fname[0] != 22) {
				memset(&buf[71], '*', 4);	/* if no CRC suppoted */
			}
			if (flg_x) {
				printf("%s\n", filename);	/* display in 2 lines */
			} else {
				if (p != filename) {		/* display in one line */
					*buf = '+';
				}
				memcpy(&buf[2], p, strlen(p));
			}
			printf(buf);
			Fno ++;
			Osize += Hdr1.OrgSiz;
			Psize += Hdr1.PacSiz;
		}
		if (fseek(file1, Hdr1.PacSiz, 1))
			break;
	}
	if (Fno) {
		printf("--------------  --------  -------- ------"
			   " -------- --------\n");
		rt = ratio(Psize, Osize);
		getftime(fileno(file1), &arcstamp.s);
		printf("   %3d files  %10lu%10lu %3d.%1d%% "
		       "%2d-%02d-%02d %2d:%02d:%02d\n",
		       Fno, Osize, Psize, rt / 10, rt % 10,
		       (arcstamp.s.ft_year + 80) % 100, arcstamp.s.ft_month,
		       arcstamp.s.ft_day, arcstamp.s.ft_hour,
		       arcstamp.s.ft_min, arcstamp.s.ft_tsec * 2);
	} else {
		printf("    no file\n");
	}
	fclose(file1);
}

/*******************************
		get switches
*******************************/
void getsw(uchar *p)
{
	static uchar flg[] = "rpxmacntvw";
	static uchar *flgpos[] = {&flg_r, &flg_p, &flg_x, &flg_m,
							  &flg_a, &flg_c, &flg_n, &flg_t,
							  &flg_v, &flg_w};
	int i;
	uchar s;
	uchar *q;

	while ((s = *p++) != 0) {
		q = j_strchr(flg, s);		/* search switch */
		if (q) {
			i = q - flg;
			if (*p == '+') {
				*flgpos[i] = 1;
				p++;
			} else if (*p == '-') {
				*flgpos[i] = 0;
				p++;
			} else if (*p == '2') {
				*flgpos[i] = 2;
				p++;
			} else if (s == 'v' && *p) {
				if (flg_v == 0)			/* process of '/vSTRING' */
					flg_v = 1;
				pager = p;
				p = "";
			} else if (s == 'w' && *p) {
				flg_w = 1;				/* process of '/wSTRING' */
				wdir = p;
				p = "";
			} else {
				if (*flgpos[i]) {		/* flip-flop */
					*flgpos[i] = 0;
				} else {
					*flgpos[i] = 1;
				}
			}
			if (s == 'r' && flg_r > 0) {
				flg_x = 1;
			}
		} else if (s == 'k') {
			keyword = p;
			p = "";
		} else {
			if (s == '?') usage();
			error(INVSWERR, NULL);
		}
	}
}

/*******************************
		execute command
*******************************/
void executecmd()
{
	int handle;
	int cnt;

	switch (cmd) {
	case 'A':
		flg_c++;
	case 'U':
	case 'M':
		append();
		break;
	case 'F':
		freshen();
		break;
	case 'P':
		if (flg_v == 0) {
           file3 = stdout;
           goto common;
        }
		stpcpy(stpcpy(pathname, workdir), "LHARC.TMP");	/* view files */
		file3 = e_fopen(outfname = pathname, "w", MKTMPERR);
		cnt = extract();
		fclose(file3);
		if (cnt)                    	/* if any files extracted */
			stpcpy(stpcpy(stpcpy(buf2, pager), " "), pathname);
			execute(buf2);				/* execute by INT 0x2e */
#if 0
			spawnlp(P_WAIT, pager, pager, pathname, NULL);
#endif
		unlink(pathname);
		break;
	case 'T':
		tstflg = 1;
		goto common;
	case 'X':
	case 'E':
		cmd = 'E';
common:
		flg_v = 0;
		extract();
		break;
	case 'V':
		flg_x++;
	case 'L':
		list();
		break;
	case 'D':
		delete();
		break;
	case 'S':
		self();
		break;
	}
	fputc('\n', stderr);
}

int cbrk;

void recovercbrk(void) {
	setcbrk(cbrk);
}

/*******************************
		main routine
*******************************/
int main(int argc, uchar *argv[])
{
	uchar *p, *q, *env, *env9;
	int i, yn;
	extern char title[];
	struct ffblk ffb;

	ctrlbrk(userbreak);					/* set vector for '^C' */
	cbrk = getcbrk();
	setcbrk(1);
	atexit(recovercbrk);
	fputs(title, stderr);				/* output title */
	putchar('\n');
	mkcrc();							/* make CRC table */
	swchar = getswchar();				/* get the setting of switch char */
	argc--;
	argv++;
	if (argc-- == 0)
		usage();						/* if no parameter given */
	p = (argv++)[0];
	cmd = toupper(*p);
	if (strlen(p) - 1 || j_strchr("EXTDLVAUMFPS", cmd) == 0) {
		cmd = 'L';						/* if no command, assume 'L' command */
		argc++;
		argv--;
	}
	cmdupdate = (int)j_strchr("AUMFD", cmd);	/* command updating archive? */
	if ((env = getenv("TMP")) != NULL) {	/* get 'TMP' from environment */
		wdir = env;
		flg_w = 1;
	}
	if ((env = getenv("LHARC")) != NULL) {	/* get 'LHARC' from environment */
		for (p = env; *p != '\0'; p++) {
			if (*p == ' ' || *p == '\x08')
				*p = '\0';
		}
		env9 = p;
		p = env;
		while (p < env9) {
			while (*p == '\0') p++;
			if (*p == swchar || *p == '-') p++;
			getsw(p);
			while (*p) p++;
		}
	}
	patno = -1;
	basedir = NULL;
	while (argc--) {
		p = (argv++)[0];
		if (*p == swchar || *p == '-') {
			getsw(++p);
		} else {
			slash2yen(j_strupr(p));		/* convert '/' to '\' */
			if (patno < 0) {			/* get archive name */
				strcpy(arcname, p);
				if ((p = j_strrchr(arcname, '\\')) == NULL) {
					p = arcname;		/* pointer of the part of file name */
				}
				if ((q = j_strchr(p, '.')) == NULL) {
					strcat(arcname, ".LZH");	/* if no extension */
				} else if (j_strcmp(".LZH", q) && flg_m == 0 && cmdupdate) {
					fprintf(stderr, M_NOTLZH, arcname);
					yn = getyn();		/* if the extension is not '.LZH' */
					if (yn == 'N') {
						exit(1);
					}
				}
				patno++;
			} else {
				if (patno == 0 && basedir == NULL &&
				    (j_strrchr(p, '\\') == p + strlen(p) - 1 ||
					 p[strlen(p) - 1] == ':')) {
					basedir = p;	/* get base (or home) directory */
				} else if (patno >= MAX_PAT) {
					message("File table overflow. ignore", p);
				} else {
					patpath[patno] = p;
					extfn(p, patfile[patno]);
					patno++;		/* regist path names */
				}
			}
		}
	}
	if (patno < 0) {
		error(NOARCNMERR, NULL);
	}
	if (patno == 0 && cmd != 'D') {				/* if no name given */
		extfn(patpath[0] = patnul, patfile[0]);	/* '*.*' is assumed */
		patno++;
	}
	p = stpcpy(workdir, wdir) - 1;
	if (*workdir != '\0' && j_strrchr(workdir, '\\') != p && *p != ':') {
		strcat(workdir, "\\");		/* concatenate '\' after the work dir. */
	}
	if (cmdupdate) {
		if (j_strchr(arcname, '*') || j_strchr(arcname, '?')) {
			error(NOARCERR, arcname);
		}					/* when updating archive, wild cards can't used */
		executecmd();
	} else {
		if (findfirst(arcname, &ffb, 0x07)) {
			error(NOARCERR, arcname);
		}
		do {
			strcpy(backpath(arcname), ffb.ff_name);
			executecmd();
		} while (!findnext(&ffb));
		if (cmd != 'L' && cmd != 'V')
			tstpat();				/* whether all given names were used? */
	}
	return errorlevel;
}
