/*

   AUTODISK - program to copy floppy disk to RAM disk upon system
		boot, and also set the system clock.      v 2.1
   By Moshe Braner  <braner@tcgould.tn.cornell.edu>       880116
		(can be linked to "mmdummy")

FUNCTION
   This program is to be placed in the \AUTO folder on the boot disk,
   AFTER the RAMdisk program.  (It assumes the RAM disk is already
   installed.)  After asking the user for the current time and date,
   and setting the ST's two clocks accordingly, this program copies
   the whole floppy disk data, FATs, directory and all, onto the RAMdisk.
   (It first finds out which sector is the last one actually holding data,
   and copies all sectors up to that one.)
   The RAMdisk ID letter is first assumed to be M, but if no such drive
   is connected the letter is decremented to the first connected one.

HINTS
   For best results:  Freshly format a disk, make an AUTO directory,
   put in it first RAMDISK.PRG and then AUTODISK.PRG, then put on the
   disk all other files you want to load to the RAMdisk at boot time,
   but no others.  You can set these files up in folders if you want:
   first make the folders, then put the files on the disk directly into
   the folders.  For maximum speed do not make any deletions of files,
   nor copy files from the disk to itself.
   You may save the desktop (with the RAMdisk icon installed, and perhaps
   the RAMdisk's window open) on the disk, too.  Make sure the RAMdisk
   is more than big enough to hold all those files.

ACKNOWLEDGEMENTS
   This program made possible in part by Eric Terrell, who posted
   "eternal.s".  The method of setting the ST's clocks is borrowed
   from "settime", posted by Allan Pratt of Atari.

WARNINGS
   This program is for booting off a floppy disk, not hard disk.
   This program will not work with "copy protected" disk formats.
*/

#include <osbind.h>

#define SECSIZE	512	/* >>>> for now 512-byte sectors only */

#define WORD	int	/* 16 bits: 'int' in Megamax */

#define OK	0
#define READ	0
#define WRITE	1
#define RREAD	2
#define WWRITE	3

typedef struct bpb {
	WORD	recsiz;
	WORD	clsiz;
	WORD	clsizb;
	WORD	rdlen;
	WORD	fsiz;
	WORD	fatrec;
	WORD	datrec;
	WORD	numcl;
	WORD	bflags;
} BPB;

#define fixup(s) (s[s[1]+2] = '\0')	/* null-terminate a GEMDOS string */

/*
 * routine to send a one-shot media-definitely-changed
 * message to GEMDOS (via Rwabs).
 */
extern dmch();
extern drvnum();
asm{
dmch:	MOVEA.W	#0x476,A0		/* Rwabs() vector	*/
	LEA	oldvec(PC),A1
	MOVE.L	(A0),(A1)		/* save old		*/
	LEA	tmpvec(PC),A1
	MOVE.L	A1,(A0)			/* poke new		*/
	RTS
tmpvec:	MOVE.W	drvnum(PC),D0
	CMP.W	14(A7),D0		/* target drive?	*/
	BEQ.S	now
	MOVE.L	oldvec(PC),A0		/* do normal		*/
	JMP	(A0)
now:	MOVE.L	oldvec(PC),0x476	/* restore normal Rwabs	*/
	MOVEQ	#-14,D0			/* "media has changed"	*/
	RTS
oldvec:	DC.L	0
drvnum:	DC.W	0
}

/*
 * Print message and quit.
 */
error(msg)
	char msg[];
{
	Cconws(msg);
	Cconws("\007\r\n\n\tHit any key ");
	Cnecin();
	_exit(0);
}

/*
 * Print a long integer as a string.
 */
prtli(n)
	register long n;
{
	register char *p;
	char	a[20];

	p = &a[19];
	*p = 0;
	do {
		*(--p) = (n%10) + '0';
		n /= 10;
	} while (n);
	Cconws(p);
}

int
strlen(s)
	char *s;
{
	register int i=0;
	while (s[i++]);
	return (i-1);
}

int
dotime(s)
	register char *s;
{
	register int  len;
	register int  hour, minute, second;
	register WORD time;

	len = strlen(s);
	if (len < 4 || len == 5 || len > 6) goto badtime;

	hour = (s[0]-'0') * 10 + (s[1]-'0');
	minute = (s[2]-'0') * 10 + (s[3]-'0');
	if (len == 6) second = (s[4]-'0') * 10 + (s[5]-'0');
	else second = 0;

	if (hour < 0 || hour > 23 ||
	    minute < 0 || minute > 59 ||
	    second < 0 || second > 59) goto badtime;

	time = (WORD)((hour << 11) | (minute << 5) | (second >> 1));

	if (Tsettime(time) == 0)
	    return (0);

badtime:
	Cconws("\r\n\tIllegal time (bad format or out of range)\r\n");
	return (1);
}

int
dodate(s)
	register char *s;
{
	register int  len;
	register int  month,day,year;
	register WORD date;

	len = strlen(s);
	if (len != 6) goto baddate;

	year =  (s[0]-'0') * 10 + (s[1]-'0') - 80;
	month = (s[2]-'0') * 10 + (s[3]-'0');
	day =   (s[4]-'0') * 10 + (s[5]-'0');

	if (year < 0 || year > 119 || 
	    month < 1 || month > 12 || 
	    day < 1 || day > 31) goto baddate;

	date = (WORD)((year << 9) | (month << 5) | day);

	if (Tsetdate(date) == 0)
		return (0);

baddate:
	Cconws("\r\n\tIllegal date (bad format or out of range)\r\n");
	return (1);
}

int ramd;		/* RAMdisk ID --- global variable */

findrd()
{
	register int mask;
	register long *p;
	p = (long *) 0x4C2;		/* _drvbits */
	mask = 1;
	ramd = 'M'-'A';	/* first assume the RAMdisk is M: */
	mask <<= ramd;
	while (ramd>2 && ((*p)&mask)==0) {
		mask >>= 1;
		ramd -= 1;
	}
}

main()
{
	register int i;
	register char *buf;
	int	res, sec, secs;
	int	bps, spc, dir, spd, spf, spt, bpc;
	int	dat, drive, sides, chunk;
	char	boot[SECSIZE], str[10];
	int	minute, hour, day, month, year;
	WORD	date, time;
	long	stack, datime;
	long	memory, bytes, dinfo[4];
	BPB	*bpb;
	char	*msg1 = "\r\n\tError reading disk!";
	char	*msg2 = "\r\n\tError writing to RAMdisk!";

	Cconws("\033E\r\n\n\tAUTODISK 2.1\tby Moshe Braner\r\n\n");


	/* see if IKBD time is valid */
	datime = Gettime();		/* get ikbd date & time */
	date = (WORD) (datime >> 16);
	time = (WORD) (datime & 0xffff);
	minute = ((time >>  5) & 0x3F);
	hour   = ((time >> 11) & 0x1F);
	day    = ((date      ) & 0x1F);
	month  = ((date >>  5) & 0x0F);
	year   = ((date >>  9) & 0x3F);
	if (minute<60 && hour<24 && day<32
	  && day>0 && month<13 && month>0 && year>7)
		if (Tsetdate(date)==0 && Tsettime(time)==0)
			goto timedone;

	/* we want to prompt the user	*/

	str[0] = 7;	/* set up buffer for Cconrs call */
	do {
		Cconws("\r\n\tEnter the time (hhmm[ss]): ");
		Cconrs(str);
		fixup(str);
	} while (dotime(str+2));

	do {
		Cconws("\r\n\tEnter the date (yymmdd): ");
		Cconrs(str);
		fixup(str);
	} while (dodate(str+2));

	/* get GEM's time */
	datime = ((long)Tgetdate()<<16) + ((long)Tgettime()&0xffffL);
	Settime(datime);		/* update the ikbd's time */
	Cconws("\r\n");

timedone:

	Supexec(&findrd);
	if (ramd < 2)
		error("\r\n\tRAMdisk not found!");
	i = ramd + 'A';
	if (i != 'M') {
		Cconws("\r\n\tIs the RAMdisk drive ");
		Bconout(2,i);
		Cconws(" ? (y/n) ");
		i = Cnecin();
		if (i!='y' && i!='Y') {
			Cconws("\r\n\tEnter RAMdisk drive letter:");
			Cconws(" ('q' to quit) ");
			i = Cnecin();
			if (i>='a' && i<='z')
				i += ('A' - 'a');
			if (i=='Q' || i<'A' || i>'Z')
				_exit(0);
			ramd = i - 'A';
		}
	}

	/* get disk parameters from bios parameter block */

	drive = Dgetdrv();		/* use current drive		*/
	bpb = (BPB *) Getbpb(drive);
	bps = bpb->recsiz;		/* bytes per sector		*/
	spc = bpb->clsiz;		/* sectors per cluster		*/
	bpc = bpb->clsizb;		/* bytes per cluster		*/
	dir = bpb->rdlen;		/* length of dir in sectors	*/
	spf = bpb->fsiz;		/* sectors per FAT		*/
	dat = bpb->datrec;		/* no. of first data sector	*/
	spd = (bpb->numcl) * spc;	/* data sectors per disk	*/

	/* adjust RAMdisk BPB */

	bpb = (BPB *) Getbpb(ramd);	/* RAMdisk BPB area		*/
	if (bpb->recsiz != bps)
		error("\r\n\tDisk and RAMdisk incompatible!");
	if (bpb->bflags & 0x8000)	/* our own magic marker		*/
		error("\r\n\tOld RAM disk!");
	bpb->bflags |= 0x8000;
	bpb->clsiz = spc;
	bpb->clsizb = bpc;
	bpb->rdlen = dir;
	bpb->fsiz = spf;
	bpb->fatrec = spf + 1;
	bpb->datrec = dat;

	/* allocate RAM buffer */

	memory = Malloc(-1L);
	if (memory < (spf*bps + 10000)
	|| (buf = (char *)Malloc(memory)) <= 0)
		error("\r\n\tNot enough memory!");

	/* copy the data from floppy to RAM */

	Cconws("\r\n\tCopying data...\r\n");

	secs = spf;			/* read first FAT only		*/
	sec = dat - dir - 2*spf;
	if (Rwabs (READ, buf, secs, sec, 0) != OK)
		error(msg1);
	i = 3*spd;			/* >>>> 12-bit FAT entries	*/
	i = 3 + i/spc/2;
	while (buf[--i] == 0);		/* search for last used sector	*/
	i *= spc*2;
	res = i/3;			/* no. of data sectors to read	*/
	if (res > spd)			/* safety check			*/
		res = spd;
	if (spc*bpb->numcl < res)
		error("\r\n\tNot enough space in RAMdisk!");

	/* write FATs to destination */

	secs = spf;
	sec = dat - dir - 2*spf;
	if (Rwabs (WRITE, buf, secs, sec, ramd) != OK)
		error(msg2);
	sec += secs;
	if (Rwabs (WRITE, buf, secs, sec, ramd) != OK)
		error(msg2);

	/* copy rest: root directory and DATA */

	chunk = memory/bps;	/* how many sectors fit in RAM */
	sec = dat - dir;
	res += dir;
	while (res > 0) {
		if (chunk > res)
			chunk = res;
		if (Rwabs  (RREAD, buf, chunk, sec, drive) != OK)
			error(msg1);
		if (Rwabs (WWRITE, buf, chunk, sec, ramd) != OK)
			error(msg2);
		sec += chunk;
		res -= chunk;
	}

	*((int *)&drvnum) = ramd;
	Supexec(&dmch);		/* install media-HAS-changed code	*/
	Dfree(dinfo,ramd+1);	/* call it through GEMDOS		*/

	Cconws("\r\n\tRAMdisk has ");
	bytes = dinfo[0] * spc * bps;
	prtli(bytes);
	Cconws(" bytes free out of ");
	bytes = dinfo[1] * spc * bps;
	prtli(bytes);

	Cconws("\r\n\n\tAUTODISK finished, no errors\r\n\n");
	for (datime=0; datime<50000; datime++);
}
