/* CP/M DISKETTE EDITOR  (c) G. Jennings 1986 1987

	No warranty is made, expressed or implied.

	4.0	6-87		added help
	3.1	5-87		streamlined version
	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_sector*6
#define TPA	100+BASE
#define	NDISKS	4			/* number of disks typical CP/M */
#define	READ	1
#define	WRITE	0
#define	SP	' '
#define	SQ	39			/* single quote: ' */
#define	PE	46			/* period */
#define	CR	13
#define LF	10
#define	TAB	9
#define	BS	8
#define	CTRLB	'B'-'@'
#define CTRLD	4
#define CTRLG	7
#define	CTRLN	'N'-'@'
#define	CTRLP	16
#define CTRLR	18
#define STRLEN	16

char helpbuf[0x2000];
char buffer[BUFSIZ];
char databuf[BYTES];
char secsave[SECSIZ];
char sparesec[SECSIZ];
char findstr[STRLEN];
char *secbuf;			/* pointer to sector buffer (DMA) */
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;
int ishelp;

main(argc,argv)
int argc;
char *argv[];
{
char c,*s;
int i,n;
int temps,tempt,tempd;
int other,ascii,system,follow;
char *end_secbuf;
char tempstr[16];		/* temp string, not too big... */

	follow=ascii=other=buffull=FALSE;
	findstr[0]='\0';
	noput=mask=TRUE;
	bufptr=bytecnt=0;
	end_secbuf=TPA;
	end_secbuf[0]=0;
	max_disk=0;
	secbuf=CPMDMA;

	ishelp=(ldhelp());
	reset();

	for (i=0;i<NDISKS;i++) {	/* find out how many disks there are */
		if (logdisk(i)!=ERROR)
			++max_disk;
	}
	for (i=*secbuf;i>0;i--) {	/* i=num chars after 'disked' */
		c=*++secbuf;		/* on command line */
		if (c>='A' && c<max_disk+'A') {
			other=TRUE;
			tempd=(c-'A');
		}
	}
	secbuf=CPMDMA;
	if (other)		/* keep track of which disk was */
		disk=tempd;	/* the original */
	else
		disk=curdsk();
	odisk=ddisk=curdsk();	/* old/default disk */

	if (logdisk(disk)==ERROR) {	/* select disk */
		printf(" no disk error");
		exit();
	}
	min_track=0;
	track=res_tracks;	/* set to beginning of dir area */
	sector=1;
	printf(" cp/m DISKette EDitor ver. 4.0  (c) 1987 G. Jennings\n");
	setmem(databuf,BYTES,0x1a);
	dparams();		/* display drive info */
	printf("\n\t? for help\n");
	readsec();		/* read first sector */

	while (1) {
		printf("\n(%04x)*",bytecnt);
		switch (c=tolower(getchar())) {
			case 'n':
			case 'b':
				(c=='n') ? bump(1) : bump(-1);
				readsec();
				break;
			case CTRLN:
				if (sector==max_sector)
					break;
				osector=sector;
				sector+=(max_sector-sector);
				readsec();
				break;
			case CTRLB:
				if (sector==1)
					break;
				osector=sector;
				sector-=(sector-1);
				readsec();
				break;
			case '-':
			case '+':
			case '=':
				if (scanf("%d",&n)!=0) {
					for (i=0;i<n;i++)
						(c=='-') ? bump(-1) : bump(1);
					readsec();
				}
				break;
			case 's':
				gets(tempstr);
				getarg(tempstr,&tempt,&temps);
				if (temps>max_sector || temps<0 ||
				 (tempt!=0 && tempt<min_track) ||
				 tempt>max_track)
					break;
				if (temps==0 && tempt==0)
					break;
				if (temps!=0)
					sector=temps;
				if (tempt!=0)
					track=tempt;
				readsec();
				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 */
				if (n<0) break;
				tempt=track;	      /* save positions */
				temps=sector;
				if (n==0) {	      /* an answer of 0 */
					n=max_sector; /* gets the whole */
					sector=1;     /* track */
				}
				if (n==1) {
					n=(max_sector-sector)+1;
				}
				for (i=0;i<n;i++) {
					append();     /* append 'n' times */
					if (buffull)
						break;
					bump(1);
					diskio(READ);
				}
				if (!follow) {        /* return to where */
					track=tempt;  /* we were? */
					sector=temps;
					diskio(READ); /* re-read */
				}
				else
					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");
					tempd=ascii;
					ascii=TRUE;
				}
				if (bytecnt==0)
					break;
				if (ascii)
					dumpa();
				else
					dump();
				if (c==CTRLD) ascii=tempd;
				break;
			case 'a':
				if (buffull)
					printf(" buffer full");
				else
					append();
				break;
			case 'f':
				if (find()) {
					savesec();
					dumpsec();
				}
				break;
			case 'l':
				tempd=tolower(getchar())-'a';
				if (tempd<0 || tempd>max_disk-1)
					break;
				if (logdisk(tempd)==ERROR) {
					printf(" no disk error");
					break;
				}
				disk=tempd;
				reset();
				setdsk(disk);
				track=res_tracks;
				sector=1;
				dparams();
				readsec();
				break;
			case '?':
			case '/':
				if (ishelp) {
					if ((c=getchar())!='\n') {
						help(c);
						break;
					}
				}
				commands();
				break;
			case 'z':
				gets(s);
				if (s[0]!='\0') goto label;
				printf("\n\tdisk is %s",(other) ? "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:	i=0;
				while ((c=tolower(s[i++]))!='\0') {
					if (c=='d') {
						other=!other;
						if (other)
							ddisk=odisk;
						else
							ddisk=curdsk();
					}
					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 '>':
			case '.':
				putsec();
				noput=FALSE;
				break;
			case '<':
			case ',':
				if (noput)
					printf(" no store");
				else
					getsec();
				break;
			case 'v':
				otrack=track;
				if (--track<min_track)
					track=max_track;
				readsec();
				break;
			case 'm':
				otrack=track;
				if (++track>max_track)
					track=min_track;
				readsec();
				break;
			case 'x':
				printf(" format sector ");
				printf("%d, track %d? ",sector,track);
				if (tolower(getchar())=='y')
					fmtsec();
				break;
			case '\n':
				dumpsec();
				break;
			case BS:
				dparams();
				break;
			case 'h':
				track=res_tracks;
				sector=1;
				readsec();
				break;
			case 'q':
				if (other)
					setdsk(ddisk);
				exit();
				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;

	if (!(dph=seldsk(disk)))	/* get header addr */
		return ERROR;
	setdsk(disk);
	xlt=dph[0];			/* translation addr */
	if (xlt==0)
		xlate=FALSE;
	else
		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;

	settrk(track);
	setsec( (xlate) ? sectran(sector-1,xlt) : 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;		
	}
}
readsec()
{
	diskio(READ);
	savesec();
	dumpsec();
}
bump(amt)		/* bump the sector and track if needed */
int amt;		/* trying not to go out of bounds */
{
	otrack=track;				/* save last track */
	osector=sector;				/* and sector */
	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;
	if (n>=(BYTES-256)) return;
	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);
	if (bufptr>=(BYTES-256)) return;
	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;
char c;
	i=t=0;
	printf("\n\ttrack %d, sector %d",track,sector);
	if (xlate==TRUE)
		printf(" (%d)",sectran(sector-1,xlt));
	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++) {
			c=secbuf[t++]&0x7f;
			if (c<' ') c='.';
			putchar(c);
		}
	}
	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 '<>'.  Always write to the 'default' disk unless there is a 
disk designator in the filename. */

putfile()
{
int fd;
char filename[16],iobuf[BUFSIZ];
char c,h;
int d,i,j;

	gets(filename);
	if (filename[0]=='\0')		/* return if no filename */
		return(ERROR);

	if (filename[1]==':') {
		j=tolower(filename[0])-'a';
		if (j<0 && j>max_disk) {
			printf(" bad disk designator");
			return(ERROR);
		}
		d=j;
	}
	else if (disk!=ddisk)
		d=ddisk;
	else {
		setdsk(disk);
		i=getrov();
		if (i>0) {
			printf(" disk is read only");
			return(ERROR);
		}
		d=disk;
	}
	setdsk(d);
	if (fd=open(filename,0)!=ERROR) {	/* check if exists */
		close(fd);
		printf(" exists, overwrite? ");
		if (getchar()=='y')
			unlink(filename);
		else {
			setdsk(disk);
			return(ERROR);
		}
	}
	if (fcreat(filename,iobuf)==ERROR) {	/* try to create */
		printf(" create %s error",filename);
		setdsk(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);
	setdsk(disk);
	return(OK);
}
dparams()	/* display disk drive info */
{
	printf("\n\tnumber of drives: %d\n",max_disk);
	printf("\tdefault disk: %c\t",ddisk+'A');
	printf("\tsectors/block: %d\n",secs_block);
	printf("\tcurrent disk: %c\t",disk+'A');
	printf("\tlogical blocks: %d\n",log_blocks+1);
	printf("\tdisk size: %dK\t",disk_size);
	printf("\tblock size: %d\n",block_size);
	printf("\tsectors: 1-%d\t",max_sector);
	printf("\tdirectory entries: %d\n",dir_entries+1);
	printf("\ttracks: 0-%d\t",max_track);
	printf("\treserved tracks: %d\n",res_tracks);

	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()
{
char c;
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(" not found, continue? ");
			printf("[%d,%d] ",track,sector);
			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];
		++bytecnt;
	}
}
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==SQ) {
			putchar(a);
			b=conin();
			if (b>=' ') {
				putchar(b);
				secbuf[x]=b;
			}
			else if (b!=CR)
				continue;
		}
		else if (a=='=') {
			putchar(a);
			b=conin();
			if (!ishex(b) || b==CR)
				continue;
			putchar(b);
			c=conin();
			if (!ishex(c))
				continue;
			if (c!=CR) putchar(c);
			x=(hexbyte(b,c)-1);
			if (x>0x7e) x=0x7e;
		}
		else if (a==CR || a==SP) {
			if (x==(SECSIZ-1)) {
				x=-1;
				putchar('\n');
			}
		}
		else if (a==TAB)
			x = -1;
		else if (a==PE)
			return;
		else if (a==BS) {
			if ((x-=2)== -2) {
				x=(SECSIZ-2);
				putchar('\n');
			}
		}
		else {
			if (!ishex(a))
				continue;
			putchar(a);
			b=conin();
			if (!ishex(b))
				continue;
			if (b!=CR)
				putchar(b);
			secbuf[x]=hexbyte(a,b);
		}
		++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];
}
fmtsec()
{
int i;
	for (i=0;i<SECSIZ;i++)
		secbuf[i]=0xe5;
	diskio(WRITE);
}
getsec()
{
int i;
	for (i=0;i<SECSIZ;i++)
		secbuf[i]=sparesec[i];
	dumpsec();
}
putsec()
{
int i;
	for (i=0;i<SECSIZ;i++)
		sparesec[i]=secbuf[i];
}
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);
		readsec();
	}
}
commands()
{
	printf("\n Commands:\n\n");
	printf("    'ld' log disk d\t   'a' append sector\t'cx' change\n");
	printf("  'sn,n' set track/sector  'u' unappend sector\t 'k' kill change\n");
	printf("     'n' next sector\t   'e' empty buffer\t 'w' write sector\n");
	printf("     'b' back sector\t  'dx' display buffer\t '>' store\n");
	printf("    '+n' move +n sectors  'ps' put to file s\t '<' retrieve\n");
	printf("    '-n' move -n sectors   'r' range\t\t 'h' home\n");
	printf("    'gn' get n sectors\t  'fs' find string s\t 'q' quit\n");
	printf("     'z' set params\t   'x' format sector\t '?' help\n");
}
help(c)
char c;
{
int n,s,j;
int i;
	switch (tolower(c)) {

		case 'l':
			s=1;
			break;
		case 's':
			s=2;
			break;
		case 'n':
			s=3;
			break;
		case 'b':
			s=4;
			break;
		case '+':
			s=5;
			break;
		case '-':
			s=6;
			break;
		case 'm':
			s=7;
			break;
		case 'v':
			s=8;
			break;
		case 'r':
			s=9;
			break;
		case 'w':
			s=10;
			break;
		case 'k':
			s=11;
			break;
		case '>':
			s=12;
			break;
		case '<':
			s=13;
			break;
		case 'c':
			s=14;
			break;
		case 'a':
			s=15;
			break;
		case 'u':
			s=16;
			break;
		case 'g':
			s=17;
			break;
		case 'e':
			s=18;
			break;
		case 'p':
			s=19;
			break;
		case 'd':
			s=20;
			break;
		case 'f':
			s=21;
			break;
		case 'z':
			s=22;
			break;
		case 'h':
			s=23;
			break;
		case '?':
			s=24;
			break;
		case 'q':
			s=25;
			break;
		case 'x':
			s=26;
			break;
		default:
			printf(" no help found");
			return;
			break;
	}
	j=n=0;
	while ((i=helpbuf[n++])!=0x1a) {
		if (i==12)
			++j;
		if (j==s) {
			putchar(TAB);
			while ((c=helpbuf[n])!=12) {
				++n;
				putchar(c);
			}
			return;
		}
	}
}

ldhelp()
{
int i,n;
char c;

	if (fopen("disked.hlp",buffer)==ERROR) {
		return(0);
	}
	n=0;
	while ((i=getc(buffer))!=EOF && i!=CPMEOF) {
		c=i;
		helpbuf[n++]=c;
	}
	helpbuf[n]=0x1a;
	fclose(buffer);
	return(1);
}
                                                                       
