/* CP/M DISKETTE EDITOR  (c) G. Jennings 1986 1987

	No warranty is made, expressed or implied.

	3.1	4-2-87		
	3.0	2-87		Supports any type disk drives via DPH
	2.0	1-14-87		General cleaning and fixed find()
	1.0	12-22-86	Looks at CP/M sector translation vector
	initial	12-14-86	does direct BIOS calls
*/

#include "stdio.h"

#define	CPMDMA	0x80+BASE
#define	BYTES	0x4000
#define	MAXTRY	max_sectors*6
#define TPA	100+BASE
#define	READ	1
#define	WRITE	0
#define	SQ	39			/* single quote: ' */
#define	PE	46			/* period */
#define	CR	13
#define LF	10
#define	TAB	9
#define	BS	8
#define CTRLD	4
#define CTRLG	7
#define	CTRLP	16
#define CTRLR	18
#define STRLEN	16

char databuf[BYTES];
char secsave[SECSIZ];
char sparesec[SECSIZ];
char *secbuf;			/* pointer to sector buffer (DMA) */
char findstr[STRLEN];
int disk;			/* disk being used */
int ddisk;			/* default disk */
int odisk;			/* old disk */
int track;			/* current track */
int sector;			/* current sector */
int otrack;
int osector;

   /* the following globals are all set by the function: LOGDSK() */

int disk_size;		/* disk size in K */
int max_sector;		/* maximum sector number (sectors per track) 1-n */
int max_track;		/* maximum track number (tracks per disk) 0-n */
int dir_entries;	/* number of directory entries */
int res_tracks;		/* number of reserved (system) tracks */
int log_blocks;		/* number of logical blocks */
int block_size;		/* block size */
int secs_block;		/* sectors per block */
int xlate;		/* true if logical/physical sector translation */

int max_disk;		/* maximum disk number (number of disk drives) 1-n */
int min_track;		/* minimum track to access, can be changed */
int xlt;		/* pointer to translate vector address */
int buffull;		/* true if file buffer full */
int bufptr;		/* pointer to file buffer, sort of... */
int bytecnt;		/* buffer byte count */
int mask;		/* true if mask bytes to disk file */
int noput;

main(argc,argv)
int argc;
char *argv[];
{
char c,*s;
int i,l,n;
int temps,tempt,tempd;
int params,other,ascii,system,follow;
char *end_secbuf;
char tempstr[6];		/* temp string, not too big... */

	follow=system=ascii=params=other=buffull=FALSE;
	findstr[0]='\0';
	noput=mask=TRUE;
	bufptr=bytecnt=0;
	secbuf=CPMDMA;
	end_secbuf=TPA;
	end_secbuf[0]=0;
	max_disk=0;
	i=getlv();			/* get login vector */
	for (l=0;l<16;l++) {		/* count high bits */
		if (i>>l&1)		/*  to determin the number */
			++max_disk;	/*  of drives on-line */
	}
	for (i=*secbuf++;i>0;i--) {	/* i=num chars after 'disked' */
		c=*secbuf++;		/*  on command line */
		switch(c) {		/* and check out any chars */
		case ' ':
		case '-':
		case '[':
		case ']':
		case '/':
		case '\\':
			break;		/* ignore any/all delimiters */
		case 'L':
			c=*secbuf++;
			if (c>='A' && c<max_disk+'A') {
				other=TRUE;
				tempd=c-'A';
			}
			else
				printf(" invalid disk <%c>\n",c);
			break;
		case 'D':
			params=TRUE;
			break;
		case 'S':
			system=TRUE;
			break;
		default:
			printf(" illegal option <%c>\n",c);
			break;
		}
	}
	secbuf=CPMDMA;
	if (other)
		disk=tempd;
	else
		disk=curdsk();
	odisk=ddisk=curdsk();

	if (logdisk(disk)==ERROR) {
		printf(" disk error");
		exit();
	}
	if (system)
		min_track=0;
	else
		min_track=res_tracks;
	track=min_track;
	sector=1;
	printf(" cp/m DISKette EDitor ver. 3.1  (c) 1987 G. Jennings\n");
	setmem(databuf,BYTES,0x1a);
	if (params)
		dparams();
	else
		commands();
	diskio(READ);
	dumpsec();

	while (1) {
		printf("\n(%04x)*",bytecnt);
		switch (c=tolower(getchar())) {
			case 'n':
			case 'b':
				(c=='n') ? bump(1) : bump(-1);
				diskio(READ);
				dumpsec();
				break;
			case '-':
			case '+':
				if (scanf("%d",&n)!=0) {
					for (i=0;i<n;i++)
						(c=='-') ? bump(-1) : bump(1);
					diskio(READ);
					dumpsec();
				}
				break;
			case 's':
				gets(tempstr);
				getarg(tempstr,&tempt,&temps);
				if (temps>max_sectors || temps<0 ||
				 (tempt!=0 && tempt<min_track) ||
				 tempt>max_tracks)
					break;
				if (temps==0 && tempt==0)
					break;
				if (temps!=0)
					sector=temps;
				if (tempt!=0)
					track=tempt;
				diskio(READ);
				dumpsec();
				break;
			case 'r':
			case CTRLR:
				if (c==CTRLR) {
					printf("^R");
					n=-1;
				}
				else
					n=1;
				range(n);
				break;
			case 'g':
			case CTRLG:
				if (buffull) {
					printf(" buffer full");
					break;
				}
				if (c==CTRLG) {
					printf("^G");
					tempd=follow; /* just save it */
					follow=TRUE;  /* dont bother */
				}		      /* with checking */
				if (scanf("%d",&n)==0) /* get arg */
					break;	      /* ret if non-dec */
				tempt=track;	      /* save positions */
				temps=sector;
				for (i=0;i<n;i++) {
					append();     /* append 'n' times */
					bump(1);
					diskio(READ);
				}
				if (!follow) {
					track=tempt;
					sector=temps;
					diskio(READ);
				}
				else {
					diskio(READ);
					dumpsec();
				}
				if (c==CTRLG)
					follow=tempd;
				break;
			case 'u':
				if (bytecnt==0)
					break;
				bytecnt-=SECSIZ;
				setmem(&databuf[bytecnt],SECSIZ,0x1a);
				buffull=FALSE;
				break;
			case 'e':
				if (bytecnt==0)
					break;
				bytecnt=0;
				buffull=FALSE;
				setmem(databuf,BYTES,0x1a);
				break;
			case 'p':
			case CTRLP:
				if (c==CTRLP) {
					printf("^P");
					tempd=mask;
					mask=FALSE;
				}
				if (bytecnt==0)
					break;
				if (putfile()!=ERROR)
					diskio(READ);
				if (c==CTRLP) mask=tempd;
				break;
			case 'd':
			case CTRLD:
				if (c==CTRLD) {
					printf("^D");
					ascii=TRUE;
				}
				if (bytecnt==0)
					break;
				if (ascii)
					dumpa();
				else
					dump();
				break;
			case 'a':
				if (buffull)
					printf(" buffer full");
				else
					append();
				break;
			case 'f':
				if (find())
					dumpsec();
				break;
			case 'l':
				tempd=tolower(getchar())-'a';
				if (tempd<0 || tempd>max_disk-1)
					break;
				disk=tempd;
				reset();
				if (logdisk(disk)==ERROR)
					printf(" disk error!");
				if (system)
					min_track=0;
				else
					min_track=res_tracks;
				track=min_track;
				sector=1;
				if (params)
					dparams();
				diskio(READ);
				dumpsec();
				break;
			case ' ':
			case '\n':
				dumpsec();
				break;
			case BS:
				dparams();
				break;
			case 'h':
				track=min_track;
				sector=1;
				diskio(READ);
				dumpsec();
				break;
			case '?':
				commands();
				break;
			case 'q':
			case 'x':
				if (other)
					seldsk(ddisk);
				exit();
				break;
			case 'z':
				gets(s);
				if (s[0]!='\0') goto label;
				printf("\nsys is %s",(system) ? "ON" : "OFF");
				printf(", disk is %s",(other) ? "ON" : "OFF");
				printf(", params is %s",(params) ? "ON" : "OFF");
				printf(", mask is %s",(mask) ? "ON" : "OFF");
				printf(", ascii is %s",(ascii) ? "ON" : "OFF");
				printf(", get is %s\n\n",(follow) ? "ON":"OFF");
				gets(s);
		label:		for (i=0;i<6;i++) {
					if (s[i]=='\0') break;
					c=tolower(s[i]);
					if (c=='s') {
						system=!system;
						if (system)
							min_track=0;
						else
							min_track=res_tracks;
					}
					if (c=='d') {
						other=!other;
						if (other)
							ddisk=odisk;
						else
							ddisk=curdsk();
					}
					if (c=='p')	params=!params;
					if (c=='m')	mask=!mask;
					if (c=='a')	ascii=!ascii;
					if (c=='g')	follow=!follow;
				}
				break;
			case 'c':
				change();
				dumpsec();
				break;
			case 'k':
				unchange();
				dumpsec();
				break;
			case 'w':
				printf(" write track ");
				printf("%d, sector %d? ",track,sector);
				if (tolower(getchar())=='y')
					diskio(WRITE);
				break;
			case '>':
				putsec();
				break;
			case '<':
				getsec();
				break;
			default:
				break;
		}
	}

}
/* Log in a disk.  Looks at the Disk Parameter Header/Block
to see whether or not there is logical to physical sector 
translation and gets the numbers for max-sectors and max-track.

	gj initial 12-86
*/

logdisk(disk)
int disk;			/* disk number, 0-N) */
{
int *dph;			/* pointer to disk parameter header */
char *dpb;			/* pointer to disk parameter block */
char bsh;

	/* note the double chars for these following DPB entries,
	   as they are  lower byte first  integers */

char spt,spt2;			/* number sectors */
char dsm,dsm2;			/* determines disk size */
char drm,drm2;			/* num directory entries */
char off,off2;			/* num reserved tracks (offset) */


int bls;			/* block size */
int i,j;

	dph=seldsk(disk);		/* get header addr */
	if (dph==0)			/* if 0, no disk-drive */
		return ERROR;
	xlt=dph[0];			/* translation addr */
	(xlt==0) ? xlate=FALSE : xlate=TRUE;
	dpb=dph[5];			/* block addr */
	spt=dpb[0];
	spt2=dpb[1];
	bsh=dpb[2];
	dsm=dpb[5];
	dsm2=dpb[6];
	drm=dpb[7];
	drm2=dpb[8];
	off=dpb[13];
	off2=dpb[14];
	log_blocks=dsm+256*dsm2;
	max_sector=spt+256*spt2;
	dir_entries=drm+256*drm2;
	res_tracks=off+256*off2;
	block_size=bls=(1024*power(2,(bsh-3)));
	secs_block=(block_size/128);
	disk_size=(log_blocks+1)*power(2,(bsh-3));
	max_track=(((log_blocks+1)*(bls/128))/max_sector)+res_tracks;
	return OK;
}
diskio(type)
int type;
{
int i;

	if (iswrite(disk)) {
		printf(" disk is read only ");
		return;
	}
	otrack=track;
	osector=sector;
	settrk(track);
	if (xlate)
		setsec(sectran(sector-1,xlt));
	else
		setsec(sector-1);
	(type==READ) ? i=lread() : i=lwrite();
	if (i) {
		printf(" read/write error");
		track=otrack;			/* restore last track */
		sector=osector;			/* and last sector */
		return;		
	}
	savesec();
}
bump(amt)		/* bump the sector and track if needed */
int amt;		/* trying not to go out of bounds */
{
	sector+=amt;
	if (sector>max_sector) {
		sector-=max_sector;
		if (++track>max_track)
			track=min_track;
	}
	else if (sector<1) {
		sector+=max_sector;
		if (--track<min_track)
			track=max_track;
	}
}
dump()		/* display the databuffer */
{
int n,b,l;

	scanf("%x",&bufptr);
	n=bufptr;
	for (l=0;l<16;l++) {
		if (l==8)
			putchar('\n');
		putchar('\n');
		outhex(bufptr);
		for (b=0;b<16;b++)
			phex(databuf[bufptr++]);
		for (b=0;b<16;b++)
			pasc(databuf[n++]);
	}
	putchar('\n');
}
dumpa()		/* display databuffer, displaying all non-printable */
{		/* characters enclosed in <> */
char c;
int n,p;
	p=n=0;
	scanf("%x",&bufptr);
	outhex(bufptr);
	putchar('\n');
	do {
		c=(databuf[bufptr++]&0x7f);
		if (p>=TWIDTH) {	/* try and keep track of */
			putchar('\n');	/* our position */
			p=0;
			++n;
		}
		if (c=='\n') {
			p=0;
			n++;
		}
		if (nprint(c)) {
			if (p>=(TWIDTH-4)) {
				putchar('\n');
				p=0;
				++n;
			}
			printf("<%02x>",c);
			p+=4;
		}
		else {
			putchar(c);
			if (c==8)
				p+=8;
			else
				++p;
		}
	} while (n<(TLENGTH-5));
	outhex(bufptr);
	putchar('\n');
}
dumpsec()		/* display sector buffer */
{
int l,i,j,t;

	i=t=0;
	printf("\n\ttrack %d, sector %d",track,sector);
	for (l=0;l<8;l++) {
		putchar('\n');
		outhex(i);
		for (j=0;j<16;j++)
			phex(secbuf[i++]);
		for (j=0;j<16;j++)
			pasc(secbuf[t++]);
	}
	putchar('\n');
}
/* Write file buffer contents (databuf[]) out to disk.  Complains and
returns -1 on error.  If global 'mask' is TRUE, then hi bit is reset
and all control characters are sent as a two byte ascii equivilent
enclosed in '<>'.  Keeps track of which disk to write to. */

putfile()
{
int change,fd;
char filename[16],iobuf[BUFSIZ];
char c,h;

	change=FALSE;
	int i;
	gets(filename);
	if (filename[0]=='\0')		/* return if no filename */
		return(ERROR);
	if (iswrite(disk)) {		/* ret if disk is read-only */
		printf(" disk is read only");
		return(ERROR);
	}
	if (disk!=ddisk) {			/* not default disk? */
		change=TRUE;			/* change if not */
		seldsk(ddisk);
	}
	if (fd=open(filename,0)!=ERROR) {	/* check if exists */
		close(fd);
		printf(" exists, overwrite? ");
		if (getchar()=='y')
			unlink(filename);
		else {
			if (change) seldsk(disk);
			return(ERROR);
		}
	}
	if (fcreat(filename,iobuf)==ERROR) {	/* try to create */
		printf(" create %s error",filename);
		if (change) seldsk(disk);
		return(ERROR);
	}
	for (i=0;i<bytecnt;i++) {		/* write out loop */
		c=databuf[i];
		if (mask) {			/* do we mask? */
			if (nprint(c) && c<' ') { /* not printable? */
				putc('<',iobuf);  /* place in <>'s */
				h=(c>>4)&0x0f;
				putc((h>9) ? h+'a'-10 : h+'0',iobuf);
				h=c&0x0f;
				putc((h>9) ? h+'a'-10 : h+'0',iobuf);
				putc('>',iobuf);
			}
			else			/* else just reset msb */
				putc(c&0x7f,iobuf);
		}
		else				/* no mask, just put */
			putc(c,iobuf);
	}
	if (!mask)	/* no mask, then put out a sector full of EOFs */
		for (i=0;i<SECSIZ;i++)
			putc(CPMEOF,iobuf);
	else
		putc(CPMEOF,iobuf);
	fflush(iobuf);
	fclose(iobuf);
	if (change)		/* have we changed? */
		seldsk(disk);
	return(OK);
}
dparams()	/* display disk drive info */
{
	printf("\n\tnumber of drives: %d\n",max_disk);
	printf("\tdefault disk: %c\n",ddisk+'A');
	printf("\tcurrent disk: %c\n",disk+'A');
	printf("\tdisk size: %dK\n",disk_size);
	printf("\tsectors: %d\n",max_sector);
	printf("\ttracks: %d\n",max_track);
	printf("\treserved tracks: %d\n",res_tracks);
	printf("\tdirectory entries: %d\n",dir_entries);
	printf("\tblock size: %d\n",block_size);
	printf("\tlogical blocks: %d\n",log_blocks+1);
	printf("\tsectors/block: %d\n",secs_block);
	if (!xlate)
		printf("\tno sector translation\n");
}
/* Search for an ascii string ( converted by convert() ) on the disk,
sector by sector, asking to continue if not found after MAXSECTORS. */

find()
{
int i,j;
char tmpstr[52];			/* 4 times max string length */
					/* plus a bunch extra */
	gets(tmpstr);
	if (tmpstr[0]!='\0')		/* save the string in tmpstr */
		strcpy(findstr,tmpstr);	/* to search for the same string */
	else if (findstr[0]=='\0')	/* without having to re-type it */
		return(FALSE);
	convert(findstr);		/* convert if needed */
	j=0;
	do {
		bump(1);		/* next sector */
		diskio(READ);
		if (j++==MAXTRY) {
			printf(" %s not found, continue? ",findstr);
			if (tolower(getchar())!='y')
				return(TRUE);
			printf("\n");
			j=0;
		}
		if (kbhit()) {		/* check for abort */
			getchar();
			return TRUE;
		}
	}
	while ((i=index(secbuf,findstr))== -1);
	printf("%x",i);			/* display its' index */
	return TRUE;
}
append()	/* append a sector into the data buffer */
{
int i;
	for (i=0;i<SECSIZ;i++) {
		if (bytecnt==BYTES) {
			printf(" buffer full");
			buffull=TRUE;
			return;
		}
		databuf[bytecnt++]=secbuf[i];
	}
}
change()
{
char a,b,c;
int x;

	a=conin();
	if (a!=CR) {
		if (a<'0' || a>'7')	/* if not valid */
			a='0';		/* make it not matter */
		else
			putchar(a);		/* echo it */
		b=conin();
		if (!ishex(b))
			b='0';
		else
			putchar(b);
		x=hexbyte(a,b);
	}
	else x=0;
	putchar('\n');
	while(1) {
		c=secbuf[x];
		printf("\n%02x: %02x ",x,c);
		if (c>=' ' && c<='~')
			printf("'%c' ",c);
		else if (c<' ')
			printf("^%c  ",c+'@');
		else
			printf("    ");
		a=conin();
		if (a!=PE && a!=SQ && a!=CR && a!=LF && a!=BS && a!=TAB) {
			if (!ishex(a))
				continue;
			putchar(a);
			b=conin();
			if (!ishex(b))
				continue;
			if (b!=CR)
				putchar(b);
			secbuf[x]=hexbyte(a,b);
		}
		if (a==SQ) {
			putchar(a);
			b=conin();
			if (b>=' ') {
				putchar(b);
				secbuf[x]=b;
			}
			else if (b!=CR)
				continue;
		}
		if (a==TAB)
			x = -1;
		if (a==PE)
			return;
		if (a==BS) {
			if ((x-=2)== -2) {
				x=(SECSIZ-2);
				putchar('\n');
			}
		}
		if ((a==CR) && (x==(SECSIZ-1))) {
			x=-1;
			putchar('\n');
		}
		++x;
	}
}
int ishex(c)
char c;
{
	if (c==CR) return(TRUE);
	return((c>='0' && c<='9')||(c>='a' && c<='f')||(c>='A' && c<='F'));
}
savesec()
{
int i;
	for (i=0;i<SECSIZ;i++)
		secsave[i]=secbuf[i];
}
unchange()
{
int i;
	for (i=0;i<SECSIZ;i++)
		secbuf[i]=secsave[i];
}
getsec()
{
int i;
	if (noput) {
		printf(" no put");
		return;
	}
	for (i=0;i<SECSIZ;i++)
		secbuf[i]=sparesec[i];
}
putsec()
{
int i;
	for (i=0;i<SECSIZ;i++)
		sparesec[i]=secbuf[i];
	noput=FALSE;
}
range(n)
int n;
{
int i,l;
char c;

	i=1000;
	c='5';
	while (1) {
		for (l=0;l<i;l++) {
			if (kbhit()) {
				c=getchar();
				if (c>'0' && c<='9')
					i=(c-'0')*250;
				else if (c==' ') {
					pause();
					getchar();
					break;
				}
				else
					c=0;
			}
		}
		if (c==0)
			break;
		bump(n);
		diskio(READ);
		dumpsec();
	}
}
                                                                                          
