/* TAPSPLIT: Split a .TAP file into several +3DOS files
    Copyright (C) 1996,2000  John Elliott

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include	<stdio.h>
#include	<string.h>
#include	<sys/stat.h>
#include	<stdlib.h>


typedef unsigned char byte; /* Must be a genuine byte */

byte header1[128], header2[128], zxtflag = 0;
long taplen;

int is3dos(char *name, byte *b)
{
	FILE *fp;
	int n,m;

	if (!(fp = fopen(name,"rb"))) return 0;

	if (fread(b,1,128,fp) < 128)
	{
		fclose(fp);
		return 0;
	}
	fclose(fp);

	if (memcmp(b,"PLUS3DOS\032",9)) return 0;

	for (m=n=0;n<127;n++) m+=b[n];

	return ((m & 0xFF) == b[n]);
}




void addblk(FILE *f, char *name)
{
	byte header[21];
	unsigned int zxlen;	/* File length from the Spectrum's point of view */
	int p3off;
	FILE *fp;

	byte *to;
	char *from;

	unsigned int n,m;
	
	header[0] = 19;		/* 19 bytes, a header */
	header[1] = 0;		/* High byte of block size */
	header[2] = 0;		/* 0, for a header */

	p3off = 0;
	
	fseek(f,taplen,0);	/* Seek to correct point for block append */
	
	if (is3dos(name, header2))  /* +3DOS, convert the header */
	{
		header[3]  = header2[15];	/* Type */
		header[14] = header2[16];	/* Length */
		header[15] = header2[17];
		header[16] = header2[18];	/* Load addr */
		header[17] = header2[19];
		header[18] = header2[20];	/* Prog len */
		header[19] = header2[21];

		zxlen = header2[16] + (header2[17] << 8);

		p3off = 128;
	}
	else
	{
		struct stat st;

		if (stat(name, &st))	
		{
#ifdef CPM			/* HiTech C for CP/M has an appalling perror() */
			fprintf(stderr,"Couldn't stat %s\n",name);
#else
			perror(name);
#endif
			return;
		}
		header[3]  = 3; /* CODE */

		if (st.st_size >= 65533) zxlen = 65533; 
		else 				     zxlen = st.st_size & 0xFFFF;
		
		header[14] = zxlen & 0xFF;
		header[15] = zxlen >> 8; 

		header[16] = header[17] = 0; /* Load address */
		
	}
	to=&header[4];
	from=name;

	for (n=0; n<10; n++)
	{
		*to = *from;
		if (!(*from)) /* End of string */
			*to = ' ';		
		else
			from++;
		to++;
	}

	if (!(fp=fopen(name,"rb")))
	{
		fprintf(stderr,"Failed to open %s.\n",name);
		return;
	}
	
	
	for (m=0,n=2;n<20;n++) m^=header[n];

	header[20] = m & 0xFF; /* Header block generated & checksummed */

	if (fwrite(header,1,21,f) < 21)
	{
		fprintf(stderr,"Header write failed for %s.\n",name);
		fclose(fp);
		return;
	}
	taplen += 21;

/* Header written, do the data */

	if (fputc((zxlen + 2) & 0xFF, f) == EOF   /* Length, low byte */
	||  fputc((zxlen + 2) >> 8,   f) == EOF   /* Length, high byte */
	||  fputc(0xFF,               f) == EOF)  /* Block type (0FFh for data) */
	{
		fprintf(stderr,"Write fail while copying %s.\n",name);
		fclose(fp);
		return;
	}
	fseek(fp, p3off, 0L);
	
	m=0xFF;

	for (n=0; n<zxlen; n++)
	{
		byte c;

		c=fgetc(fp);

		if (feof(fp) || ferror (fp))
		{
			fprintf(stderr,"Unexpected End-Of-File or error on file %s.",name);
			fclose(fp);
			return;
		}

		m^=c;

		if (fputc(c,f) == EOF)
		{
			fprintf(stderr,"Write fail while copying %s.\n",name);
			fclose(fp);
			return;
		}

	}
	if (fputc(m & 0xFF,f) == EOF)
	{
		fprintf(stderr,"Write fail while copying %s.\n",name);
		fclose(fp);
		return;
	}	
	fclose(fp);
	taplen += (long)zxlen + 4L;
}





void main(int argc, char **argv)
{
	FILE *tapfile;
	char *tname;
	int n;
	
	if (argc < 3)
	{
		fprintf(stderr,"Syntax: TAPCAT { -N } tapfile member member ... \n");
		exit(1);
	}
	tname=argv[1];
	n=2;
	
	if (argv[1][0]=='-' || argv[1][0]=='[' || argv[1][0]=='/')
	  {
	  if (argv[1][1]=='N' || argv[1][1]=='n')
	  {
		tname=argv[2];
		n=3;
		if (argc < 4)
		{
			fprintf(stderr,"Syntax: TAPCAT { -N } tapfile member member ... \n");
			exit(1);
		}
		remove(tname);  /* Remove the .TAP file if it exists */
		if (!(tapfile=fopen(tname,"wb")))
		{
			fprintf(stderr,"Failed to create %s\n",tname);
			exit(1);
		} 
		fclose(tapfile);
	  }
	  else
	  {
		fprintf(stderr,"Unrecognised option: %s",argv[0]);
		exit(1);
	  }
	}
	
	/* Check for a .ZXT file */

	if (is3dos(tname, header1) && (!memcmp(header1+15,"TAPEFILE",8)))
	{
		zxtflag = 1;
		taplen = (long)header1[11] 
		      + ((long)header1[12] << 8)	/* Use the +3DOS file size, it's */
		      + ((long)header1[13] << 16)   /* a bit more exact */
		      + ((long)header1[14] << 24);
	}
	else
	{
		struct stat st;

		if (stat(tname, &st))	
		{
#ifdef CPM			/* HiTech C for CP/M has an appalling perror() */
			fprintf(stderr,"Couldn't stat %s\n",tname);
#else
			perror(tname);
#endif
			exit(2);
		}
		taplen = st.st_size;
	}

	/* Start appending the blocks */

	tapfile = fopen(tname, "r+b");
	while (argv[n])
	{
		addblk(tapfile, argv[n]);
		fprintf(stdout,"File %s appended OK\n",argv[n++]);
	}
	if (zxtflag)  /* Update +3DOS header if a .ZXT file */
	{
		int m,n;
		
		header1[11] =  taplen        & 0xFF;
		header1[12] = (taplen >> 8)  & 0xFF;
		header1[13] = (taplen >> 16) & 0xFF;
		header1[14] = (taplen >> 24) & 0xFF;

		for (n=m=0; m<127;m++) n+=header1[m];

		header1[127] = m & 0xFF;

		fseek(tapfile, 0L, 0);

		if (fwrite(header1,1,128,tapfile) < 128)
		{
			fprintf(stderr,"Failed to write .ZXT header\n");
			fclose(tapfile);
			exit(3);
		}
	}
	fclose(tapfile);
}


