/*
 *	dcopy.c    $Id: dcopy.c 1.1 1997/05/08 21:59:08 Graham Rel $
 *
 *	Copies files from Dragon DOS disk
 *
 * Originally written by:
 *
 *	Graham E. Kinns  <gekinns@iee.org>  Apr '97
 *
 * Program rewritten by Hans Petter Selasky 2002
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/types.h>

#define PROG_NAME	"dcopy"
#define VERSION		"1.0"

#define DPRINTF(x, args...) printf(x "\n" ,##args)
#define ERR(args...) fprintf(stderr, args)

/*
 * NOTE: The seek location _must_ be
 * a factor of DEV_BSIZE == 512.
 *
 * TYPICAL VALUES
 *
 * offset 0..255 | sectorsize : 256 bytes
 * sector 0..17  | sectors    : 18  sectorsizes
 * track 0..39   | tracks     : 40  sectors
 * side 0..1     | sides      : 1   tracs
 *
 */

#define D64_SEC_SIZE 256

#if DEV_BSIZE <= (256*2)
#undef DEV_BSIZE
#define DEV_BSIZE (256*18)
#endif

#define LBA(offset,sector,track,side) (offset + ((sector + ((track + (40 * side)) * 18)) * 256))

#define	MAX_DIR_ENTS	160

static int error_flag = 0;

static u_int8_t	directory [MAX_DIR_ENTS][25];

/* Local function prototypes  */

static void    disk_read      (FILE *f, u_int offset, u_int8_t *buf, u_int len);
static int32_t file_copy      (FILE *f, u_int8_t *entry, FILE *outf, u_int8_t *buf);
static void    dragon2dos_name(register u_int8_t *name, register u_int8_t *dst);

static void
disk_read(FILE *f, u_int offset, u_int8_t *buf, u_int len)
{
  static u_int8_t  last_data[DEV_BSIZE];
  static u_int32_t last_block = -1;

	while(1)
	{
	  u_int32_t block = offset / DEV_BSIZE;

	  if(last_block != block) {
	     last_block  = block;

	     error_flag = 0;

	     fseek(f, block * DEV_BSIZE, SEEK_SET);

	     memset(&last_data[0], 0xff, sizeof(last_data));

	     if(fread(&last_data[0], DEV_BSIZE, 1, f) != 1)
	     {
	       /* ignore bad sectors */
	       error_flag = 1;
	     }
	  }

	  if(len > /*=*/ DEV_BSIZE)
	  {
		bcopy(&last_data[offset % DEV_BSIZE], buf, DEV_BSIZE);

		len    -= DEV_BSIZE;
		offset += DEV_BSIZE;
		buf    += DEV_BSIZE;
	  }
	  else
	  {
		bcopy(&last_data[offset % DEV_BSIZE], buf, len);

		break;
	  }
	}

	return;
}

static void
dragon2dos_name(register u_int8_t *name, register u_int8_t *dst)
{
	static const char invalid_dos_chars[] = ":;,.\"|<>*?\\/+=[]~";
	static u_int8_t unknown = 0;
	u_int8_t c = 0xff;
	u_int8_t i;

	/* skip the first byte which
	 * is used for other purposes
	 */
	name++;

	for (i = 0; i < 11; i++)
	{
		/* add punctuation mark */
		if (i == 8)
		{
		  *dst++ = '.';
		}

		c = name[i];
		
		/* invalid charters */
		if (c <= 0x20 ||
		    c > 0x7f || strchr(invalid_dos_chars, c))
		{
		  /* end of line */
		  if (c == 0x00)
		  {
			i |= 7;
			continue;
		  }

		  c = '_';
		}

		*dst++ = c;
	}

	/* make a filename
	 * if empty
	 */
	if(c == 0xff)
	{
	  sprintf(dst, "uknown.%03d", unknown);
	  unknown ++;
	}
	else
	{
	  *dst = '\0';
	}

	return;
}

static int32_t
file_copy (FILE *f, u_int8_t *entry, FILE *outf, u_int8_t *buf)
{
	u_int8_t *file;
	u_int8_t  buffered = 0;
	u_int16_t sector_start;
	u_int16_t sector_end;
	  int32_t file_size = 0;

	/* sector info starts after filename */
	file = &entry[12];

	while(1)
	{
		do {

		  sector_start = (file[0] << 8) | file[1]; /* including */
		  sector_end   = (file[2]) + sector_start; /* excluding */

		  /* assume a sector count of 0 marks the end of the file */
		  if ((sector_start == 0x00) &&
		      (sector_start == sector_end))
		  {
		    goto done;
		  }

		  DPRINTF(" ** Reading sector %d(inclusive) - "
			  "%d(exclusive)", sector_start, sector_end);

		  /*  Read sector_count but only copy sector_count - 1  */
		  while (sector_start < sector_end)
		  {
			if (buffered)
			{
				fwrite (buf, D64_SEC_SIZE, 1, outf);
				file_size += D64_SEC_SIZE;
			}

			disk_read(f,LBA(/*OFF*/0,/*SEC*/sector_start,/*TRACK*/0,/*SIDE*/0),
				  buf, D64_SEC_SIZE);

			sector_start++;

			buffered = 1;
		  }

		  file += 3;

		} while(file <= &entry[21]);

		/* check if this entry can't ``be continued'' */
		if ((entry[0] & 0x20) == 0x00)
		{
		  goto done;
		}

		/* sanity check */
		if(entry[24] >= MAX_DIR_ENTS)
		{
		  ERR("Warning: pointer to next directory entry is out of bounds!\n");
		  goto done;
		}

		DPRINTF(" ** Next directory entry is %d", entry[24]);

		/* get next directory in chain */
		file = &directory[entry[24]][0];

		/* check if the next entry is ``a continued one'' */
		if ((file[0] & 0x01) == 0x00)
		{
		  ERR("Warning: directory entry %d is not continued!\n", entry[24]);
		  goto done;
		}

		/* store current entry */
		entry = file;
		
		/* the sector entries start at offset 1 */
		file  = &file[1];
	}
 done:

	/* byte offset 24 in the entry holding
	 * the last sector, is used as length;
	 * (0x00 == 256)
	 */

	if (buffered)
	{
		u_int16_t len = entry[24];

		if(!len) len = 256;

		DPRINTF(" ** last sector has length %d", len);

		fwrite (buf, len, 1, outf);

		file_size += len;
	}

	if(error_flag)
	{
	   file_size = -file_size;
	}

	return file_size;
}

int
main (int argc, char *argv[])
{
	FILE      *f;
	FILE	  *of;
	u_int8_t   buf[256 + 32];
	u_int8_t  *file_name = &buf[256];
	u_int8_t   file_read_all = 0;
	u_int8_t   file_count;
	u_int8_t   n;
	  int32_t  file_size;
	  int32_t  tmp;

        printf(PROG_NAME " v" VERSION "; compiled on " __DATE__ "\n\n");

	if (argc < 2)
	{
	  goto usage;
	}

	f = fopen(argv[1], "r");

	if(f == NULL)
	{
	  ERR("Cannot open device %s\n", argv[1]);
	  goto usage;
	}

	if (argc > 2)
	{
	  /* set a file prefix */
	  if(!strcmp(argv[2], "read") ||
	     !strcmp(argv[2], "Read") ||
	     !strcmp(argv[2], "READ"))
	  {
	    file_read_all = 1;
	  }

	  if(!strcmp(argv[2], "image") ||
	     !strcmp(argv[2], "Image") ||
	     !strcmp(argv[2], "IMAGE"))
	  {
	    if(argc > 3)
	    {

	      printf("Creating disk image: %s\n", argv[3]);

	      of = fopen(argv[3],"w+");

	      if(of != NULL)
	      {
		int pos;

		/* only read with one head!! */
		for(pos = 0; pos < LBA(0,0,0,2); pos += LBA(0,1,0,0))
		{
		  disk_read(f, pos, &buf[0], D64_SEC_SIZE);

		  fwrite (buf, D64_SEC_SIZE, 1, of);
		}

		fclose(of);
	      }
	      else
	      {
		ERR(" * cannot create file for writing of data!\n");
	      }
	    }
	    else
	      goto usage;
	  }
	}

	/* read directory at TRACK 20 */

	disk_read(f, LBA(/*OFF*/0,/*SEC*/0,/*TRACK*/20,/*SIDE*/0), &buf[0], 256);

	/* num_tracks = buf[252];
	 * num_sectors = buf[253];
	 */

	/* check information ... */
	if ((buf[252] ^ buf[254] ^ 0xff) ||
	    (buf[253] ^ buf[255] ^ 0xff))
	{
	  printf("Not a Dragon DOS disk.\n");
	  goto usage;
	}

	printf("Dumping from Dragon DOS disk:\n\n"
	       " * %d sector\n"
	       " * %d tracks\n\n"
	       " * Listing root directory:\n\n", buf[253], buf[252]);

	/* read directory at TRACK 20 */
	for(n = 2; n < 18; n++)
	{
	  disk_read(f, LBA(/*OFF*/0,/*SEC*/n,/*TRACK*/20,/*SIDE*/0),
		    ((u_int8_t *)&directory) + (250*n) - (250*2) , 250);
	}

	file_count = 0;
	file_size = 0;

	for(n = 0; n < MAX_DIR_ENTS; n++)
	{
	  u_int8_t what = directory[n][0];

#if 0
	  /* ?? */
	  if(what & 0x08) { printf(" * unkown reason\n"); break; }
#endif

	  /* get a the right filename */
	  dragon2dos_name(&directory[n][0], file_name);

	  if((what & 0x01) == 0)
	  {
	    printf(" ** %-12s\n", file_name);

	    if((what & 0x80))
	    {
	      printf(" ** this file was deleted\n");
	    }
	  }

	  /* skip deleted and continuation entries */
	  if(what & (0x80 | 0x01)) continue;

	  /* get messages printed */
	  fflush (stdout);


	  if(file_read_all)
	  {
		of = fopen(file_name, "r");

		if(of != NULL)
		{
		  ERR(" ** file already exist (overwriting)\n");
		  
		  fclose(of);
		}

		of = fopen (file_name, "w+");

		if(of == NULL)
		{
		  ERR("Unable to create file: %s (continuing)\n", file_name);
		}
		else
		{
		  tmp = file_copy (f, &directory[n][0], of, &buf[0]);

		  if(tmp < 0)
		  {
		    printf(" ** %s file had one or more read errors (ignored)\n", file_name);
		    tmp = -tmp;
		  }

		  printf(" ** file length is %6u bytes\n", tmp);

		  file_size += tmp;

		  fclose(of);
		}

		DPRINTF("");
	  }

	  file_count++;
	}

	printf ("\n"
		" * Total Bytes read        : %u\n"
		" * Total Files in directory: %u\n", file_size, file_count);

	return (0);

 usage:

	ERR("Usage: %s /dev/fd0.0 [read] [image filename]\n\n", argv[0]);

	return (1);
}

