/***********************************************************
	header.c -- handle headers
***********************************************************/
#define MSDOS
#define MYDOS 'M'

#include <stdio.h>
#include <io.h>
#include <dos.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include "lh.h"
#include "intrface.h"
#include "errmes.h"

#define HDRwork      (uchar *)(work +  0)
#define HDRsize      (uchar *)(work +  0)
#define HDRwhole     (uchar *)(work +  0)
#define HDRsum       (uchar *)(work +  1)
#define HDRmethod    (uchar *)(work +  2)
#define HDRpacked    (uchar *)(work +  7)
#define HDRoriginal  (uchar *)(work + 11)
#define HDRtime      (uchar *)(work + 15)
#define HDRattr      (uchar *)(work + 19)
#define HDRlevel     (uchar *)(work + 20)
#define HDRfnlen     (uchar *)(work + 21)
#define HDRfname     (uchar *)(work + 22)
#define HDRdos       (uchar *)(work + 23)
#define HDRextsize   (uchar *)(work + 24)

struct head hpb;

char methodID[6] = "\0\0\0\0\0\0";

static ulong  nextpos;

#ifdef MSDOS
	#define Convint(p) (*(int *)(p))
	#define Convlong(p) (*(long *)(p))
#else
	#define Convint(p) (*(p) + ((unsigned)*((p) + 1) << 8))
	#define Convlong(p) (Convint(p) + ((ulong)Convint((p) + 2) << 16))
#endif

#ifdef MSDOS
	#define Setint(p, q) (*(uint *)(p)=q)
	#define Setlong(p, q) (*(ulong *)(p)=q)
#else
	#define Setint(p, q) {uint a = q; (p)[0] = a; (p)[1] = a >> 8;}
	#define Setlong(p, q) {ulong a = q; \
							(p)[0] = a; (p)[1] = a >> 8; \
							(p)[2] = a >> 16; (p)[3] = a >> 24;}
	#define MAXNAMELEN 128
#endif

ulong inithdr(void)
{
	long pos;
	int c, err;
	char *p;

	pos = 0;
	while ((c = getc(file1)) >= 0) {
		pos++;
		if (c == '-') {
			getc(file1); getc(file1); getc(file1);
			if (getc(file1) == '-') {
				nextpos = pos - 3;
				p = gethdr(&err);
				if (p) {
					free(p);
					nextpos = pos - 3;
					return nextpos;
				}
			}
			fseek(file1, pos, SEEK_SET);
		}
	}
	nextpos = pos;
	return nextpos;
}

/*******************************
  calculate check-sum of header
*******************************/
static char calcsum(void *h)
{
	char *p, *q, i;

	p = (char *)h + 2;
	q = p + *(uchar *)h;
	for (i = 0; p < q; p++)
		i += *p;
	return i;
}

static void extheader(char *exthdr, int size)
{
	uchar *p;

	p = exthdr + 1;
	switch (*exthdr) {
	case 0:
		hpb.headcrc = Convint(p);
		hpb.crcpos = p;
		if (size > 5)
			hpb.info = *(p + 2);
		break;
	case 1:
		hpb.filename = p;
		hpb.filenlen = size - 3;
		break;
	case 2:
		hpb.pathname = p;
		hpb.dirnlen = size - 3;
		break;
	case 0x40:
		if (hpb.dos == MYDOS) {
			hpb.attr = Convint(p);
		}
		break;
	}
}


#define readarc(a,b) fread(a, 1, b, file1)

char *gethdr(int *err)
/***
  *err = 0 : normal end of header
       = 1 : not a header
****/
{
	char *p;
	int namelen, extsize;
	int i;

	*err = 0;
	hpb.crcpos = NULL;
	*HDRsize = *HDRlevel = 0;
	fseek(file1, nextpos, SEEK_SET);
	if (readarc(HDRwork, 21) != 21 || *HDRsize == 0) return NULL;
	hpb.headersize = (int)*HDRsize + 2;
	strncpy(hpb.method, HDRmethod, 5);
	hpb.packed = hpb.skip = Convlong(HDRpacked);
	hpb.original = Convlong(HDRoriginal);
	hpb.level = *HDRlevel;
	hpb.attr = *HDRattr;
	hpb.dirnlen = 0;
	switch(hpb.level) {
	case 0:
	case 1:
		hpb.dostime.u = Convlong(HDRtime);
		hpb.utc = dos2unix(&hpb.dostime.s);
		if (hpb.headersize < 22) return NULL;
		readarc(HDRwork + 21, hpb.headersize - 21);
		if (calcsum(HDRwork) != *HDRsum) {
			if (*HDRwork == 0x1a) return NULL;	/* for LArc & XMODEM */
			*err = 1;
			return NULL;
		}
		namelen = *HDRfnlen;
		hpb.filenlen = namelen;
		hpb.filename = hpb.pathname = HDRfname;
		i = hpb.headersize - namelen;
		if (i >= 24) {
			hpb.filecrc = Convint(HDRfname + namelen);
		} else {
			hpb.level = -1;
		}
		if (i >= 25) {
			hpb.dos = *(HDRfname + namelen + 2);
		}
		nextpos = ftell(file1) + hpb.skip;
		if (hpb.level <= 0) {
			strncpy(hpb.pathname = e_malloc(namelen + 1), HDRfname, namelen);
			hpb.pathname[namelen] = '\0';
			hpb.filename = convdelim(hpb.pathname, DELIM);
			return hpb.pathname;
		}
		p = HDRwork + *HDRsize;
		while ((extsize = Convint(p)) != 0) {
			readarc(p + 2, extsize);
			extheader(p + 2, extsize);
			p += extsize;
		}
		i = p + 2 - HDRwork;
		hpb.packed -= i - hpb.headersize;
		hpb.headersize = i;
		break;
	case 2:
		readarc(HDRwork + 21, (hpb.headersize = Convint(HDRwhole)) - 21);
		hpb.utc = Convlong(HDRtime);
		hpb.dos = *HDRdos;
		p = HDRextsize;
		while ((extsize = Convint(p)) != 0) {
			extheader(p + 2, extsize);
			p += extsize;
		}
		hpb.filecrc = Convint(HDRfnlen);
		nextpos = ftell(file1) + hpb.skip;
		if (hpb.crcpos == NULL) {
			*err = 1;
			return NULL;
		}
		break;
	default:
		if (*HDRsize != 0x1a) *err = 1;
		return NULL;
	}
	if (hpb.crcpos) {
		Setint(hpb.crcpos, 0);
		crc = 0;
		if (calccrc(HDRwork, hpb.headersize) != hpb.headcrc) {
			*err = 1;
			return NULL;
		}
		Setint(hpb.crcpos, hpb.headcrc);
	}
	namelen = hpb.dirnlen + hpb.filenlen;
	p = e_malloc(namelen + 1);
	hpb.pathname = strncpy(p, hpb.pathname, hpb.dirnlen);
	hpb.filename = strncpy(p + hpb.dirnlen, hpb.filename, hpb.filenlen);
	*(p + namelen) = '\0';
	convdelim(hpb.pathname, DELIM);
	return hpb.pathname;
}

void makehdr(void)
{
	int namelen;
	uchar *p, *q;

	hpb.crcpos = NULL;
	memcpy(HDRmethod, hpb.method, 5);
	Setlong(HDRpacked, hpb.skip = hpb.packed);
	Setlong(HDRoriginal, hpb.original);
	*HDRattr = 0x20;
	*HDRlevel = hpb.level;
	q = hpb.filename;
	if (hpb.level == 0) q = hpb.pathname;
	namelen = strlen(q);
	p = HDRfnlen;
	if (hpb.level != 2) {
#ifndef MSDOS
		if (namelen > MAXNAMELEN) {
			*p++ = 0;
		} else
#endif
		{
			*p++ = namelen;
			strcpy(p, q);
			if (hpb.level == 0) convdelim(p, pathdelim);
			p += namelen;
		}
		hpb.dostime.s = *unix2dos(hpb.utc);
		Setlong(HDRtime, hpb.dostime.u);
	} else {
		Setlong(HDRtime, hpb.utc);
	}

	Setint(p, hpb.filecrc); /* filecrc (for SFX) */
	p += 2;
	if (hpb.level == 0) {
		*HDRsize = p - HDRmethod;
		hpb.headersize = p - HDRwork;
		*HDRattr = hpb.attr;
		*HDRsum = calcsum(HDRwork);
		return;
	}
	*p = MYDOS; p++;
	*HDRsize = p - HDRwork;

/*
|  filename header     |
|   2 ext-header size  |
|----------------------|
|   1 0x01             |
|   ? filename         |
|----------------------|
*/
	if (hpb.level == 2
#ifndef MSDOS
	|| namelen > MAXNAMELEN
#endif
	) {
		hpb.filenlen = strlen(hpb.filename);
		Setint(p, hpb.filenlen + 3); p += 2;
		*p++ = 1;
		memcpy(p, hpb.filename, hpb.filenlen); p += hpb.filenlen;
	}

/*
|  dirname header      |
|   2 ext-header size  |
|----------------------|
|   1 0x02             |
|   ? dirname          |
|----------------------|
*/
	if (hpb.pathname != hpb.filename) {
		hpb.dirnlen = hpb.filename - hpb.pathname;
		Setint(p, hpb.dirnlen + 3); p += 2;
		*p++ = 2;
		convdelim(hpb.pathname, DELIM2);
		memcpy(p, hpb.pathname, hpb.dirnlen); p += hpb.dirnlen;
		convdelim(hpb.pathname, DELIM);
	}

#ifdef MSDOS
/*
|  attribute header    |
|   2 ext-header size  |
|----------------------|
|   1 0x40             |
|   2 attribute        |
|----------------------|
*/
	if (hpb.attr != 0x20) {
		Setint(p, 5); p += 2;
		*p++ = 0x40;			/* ext-header type */
		Setint(p, hpb.attr); p += 2;
	}
#endif

/*
|  common header       |
|   2 ext-header size  |
|----------------------|
|   1 0x00             |
|   2 header crc       |
| ( 1 information )    |
|----------------------|
*/
	if (p != HDRwork + *HDRsize || hpb.info) {
		q = p; p += 2;
		*p++ = 0;					/* ext-header type */
		hpb.crcpos = p;
		Setint(p, 0); p += 2;		/* dummy crc */
		if (hpb.info) {
			*p++ = hpb.info;
		}
		Setint(q, p - q);
	}

/*  ext-header end  */
	Setint(p, 0); p += 2;

	hpb.headersize = p - HDRwork;
	if (hpb.level == 2) {
		if ((hpb.headersize & 0xff) == 0) {
			hpb.headersize++;
			*p = '\0';
		}
		Setint(HDRsize, hpb.headersize);
	}
}

void writehdr(void)
{
	hpb.currentpos = ftell(file2);
	if (fwrite(HDRwork, hpb.headersize, 1, file2) == 0)
		error(WTERR, filename2);
}

void adjusthdr(void)
{
	long lastpos;
	uchar *p;

	lastpos = ftell(file2);
	fseek(file2, hpb.currentpos, SEEK_SET);
	if (hpb.level == 1) {
		hpb.skip = hpb.headersize - *HDRsize - 2 + hpb.packed;
		Setlong(HDRpacked, hpb.skip);
	} else {
		Setlong(HDRpacked, hpb.packed);
	}
	p = HDRfnlen;
	if (hpb.level != 2) p += *p + 1;
	Setint(p, hpb.filecrc);
	memcpy(HDRmethod, hpb.method, 5);
	if (hpb.level != 2) *HDRsum = calcsum(HDRwork);
	if (hpb.crcpos) {
		crc = 0;
		Setint(hpb.crcpos, 0);
		Setint(hpb.crcpos, calccrc(HDRwork, hpb.headersize));
	}
	fwrite(HDRwork, hpb.headersize, 1, file2);
	fseek(file2, lastpos, SEEK_SET);
}
