//
// (C) 1993-1996  The Whiz-zards Association
//
// Fcopy C64<->PC
//
// Written by M. Edzes
//

#include <stdio.h>
#include <conio.h>
#include <bios.h>
#include <dos.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

/* Technical data for C64 Fcopy program.

	Cable:

   11 wires. 1 GND, 2*5 data lines.

   The PC printer port interface has four adresses, of which the  lowest
	two I know the usage.

   The base port adress of the LPT1..N port can be found in the BIOS  data
   space at adress 0x0400:0x0080 .. 0x0400:0x0087.
   Every 2 byte word is a port base adress. Usually, LPT1 resides at  0x378.

   The 0x378 port is the 8 bit printer output, of which the lower 5  bits
   are connected to the lower 5 input bits of port $dc01 on the C64.

	The lower 5 bits of the C64 $dc00 output port have been connected  to the
   printer error input lines, which are the upper 5 bits of 0x379,  where the
	HIGHEST BIT (bit 7) IS INVERTED!!!

   This is all.

	Now for the c64diskfile structure:

   A file consists of 683 (number of sector) * 261 (sizeof sector) +  5
     (file header size) = .... bytes.

	You may use the class c64diskfile definition for your own use,  it's
   very easy.

     In your program you can open a new file by:


   c64diskfile filevariable(accesstype mode)

	, where mode is either READ or WRITE. Now, use

   filevariable.setname(char *filename) // ONLY sets the filename. No  other
						action. Do this BEFORE open.

   filevariable.open()    // obvious. Will only open files in READ  mode if
			     the SIZE and FORMATHEADER are correct. Returns
				  true if successful. Sets the filepos at track=1,
				  sector=0.


   filevariable.close(int error)   // if error is true the file will  be
						deleted (if it was opened WRITE),
				      otherwise not.

   filevariable.exists()  // obvious. Returns true on success, does  NOT check
			     the fileheader.

   filevariable.positiontr(int trk) // Sets the current filepos to  track trk
						 , sector 0. Only neccesary if not
				       reading succesive tracks (dir)

   filevariable.getid(unsigned char id1,unsigned char id2)
				 // Gets id out of formatheader.
   filevariable.putid(unsigned char id1,unsigned char id2)
				 // Writes an id into the file, and
					 changes filepos to track=1,sector=0.
	filevariable.gettrack(int trk,unsigned char *buffer)
	filevariable.puttrack(int trk,unsigned char *buffer)
				// Read or write a C64 track from/to the file
				   trk is ONLY used to calculate the number of
					sectors to read/write. Filepos will be up-
					dated accordingly. NO PREPOSITIONING!

   accesstype filevariable.getmode()
			      // returns READ or WRITE.


*/

class Joyconnection
{

	int lptportout;
   int lptportin;
	enum dirs {FROM64,TO64};
   dirs direction;

public:
   Joyconnection(int prt) {lptportout=prt;lptportin=prt+1;};
   ~Joyconnection() {outportb(lptportout,0xff);};
	void abort() {outportb(lptportout,0xff);};
	int putblock(unsigned char *,int);
   int getblock(unsigned char *,int);
   int init();
private:
   int putbyte(unsigned char);
	int getbyte(unsigned char&);
};

#define C64FHEADER "TWA!"

struct c64fheader {
//   char hdr[sizeof(C64FHEADER)];
	unsigned char id1;
	unsigned char id2;
	};


struct c64sector {
	unsigned char track;       // actual track   (redundant)
	unsigned char sector;      // actual sector  (redundant)
	unsigned char scrncode;    // one of "+-CHI?FL" etc.
	unsigned char header;      // mostly 0x07
	unsigned char data[256];
	unsigned char chksum;      // header exored with all databytes.
	};

enum accesstype {READ,WRITE};

class c64diskfile
{

	FILE *fpd64, *fpf64;
	char fnamed64[80],fnamef64[80];
	c64fheader fhdr;
	accesstype mode;

public:
	c64diskfile(accesstype m) {mode=m;fpd64=NULL;fpf64=NULL;};
	~c64diskfile() {
	 if(fpd64!=NULL) fclose(fpd64);
	 if(fpf64!=NULL) fclose(fpf64);
	};
	void setname(char *s) {
	 strncpy(fnamed64,s,sizeof(fnamed64)-4);
	 strncpy(fnamef64,s,sizeof(fnamef64)-4);
	 strcat(fnamed64,".D64");
	 strcat(fnamef64,".F64");
	 };
	accesstype getmode() {return mode;};
	int exists();
	int open();
	void close(int);
	void positiontr(int);
	void gettrack(int,unsigned char *);
	void puttrack(int,unsigned char *);
	void getid(unsigned char&,unsigned char&);
	void putid(unsigned char&,unsigned char&);
};



int calcsectors(int tr)
{
 int se=21;
 if(tr>17) se-=2;
 if(tr>24) se--;
 if(tr>30) se--;
 return se;
}


int Joyconnection::putblock(unsigned char *s,int n)
{
 int i;
 unsigned char chk;
 unsigned char *p;
 int err;

 do {
  i=0;
  chk='\0';
  err=0;
  p=s;
  while(i<n && !err) {
	i++;
	chk+=*p;
	err=putbyte(*(p++));
  }
  if(!err) err=putbyte(chk);
  if(!err) err=getbyte(chk);
  delay(1);
  if(!err) err=chk;
  if(err) {
	err=init();
	if(!err) err=-1;
  }
 } while((err!=0) && (err!=-2));
 return err;
}

int Joyconnection::getblock(unsigned char *s,int n)
{
 int i=0;
 unsigned char *p;
 unsigned char chk,ctmp;
 int err;

 do {
  i=0;
  chk='\0';
  err=0;
  p=s;
  while(i<n && !err) {
	i++;
	err=getbyte(ctmp);
	*(p++)=ctmp;
	chk+=ctmp;
  }
  if(!err) err=getbyte(ctmp);
  if(!err) {
	if(chk-ctmp) {
	 err=putbyte(0xff);
	 if(!err) err=-1;
	} else {
	 err=putbyte(0);
	}
  }
  if(err) {
	err=init();
	if(!err) err=-1;
  }
 } while((err!=0) && (err!=-2));
 return err;
}

int Joyconnection::putbyte(unsigned char c)
{
 unsigned char a,b;

 if(direction==FROM64) {
  while(!(inportb(lptportin)&0x80));   // Wait for starting 0 1 0 0 0
  outportb(lptportout,0x1f);
  while(inportb(lptportin)&0x40);      // Wait for 0 0 0 0 0
  direction=TO64;
 }

 a=inportb(lptportin);
 b=(c>>4);
 outportb(lptportout,b);               //     o  0   d7 d6 d5 d4
 while(inportb(lptportin)==a);         //     i  0 ~(d7 d6 d5 d4)
 if((((a=inportb(lptportin))>>3)^b)==0xf) {
  b=(c&((char)0xf))|0x10;
  outportb(lptportout,b);
  while(inportb(lptportin)==a);
  if(((inportb(lptportin)>>'\3')^b)=='\17') return 0;
 }
 return -1;
}

int Joyconnection::getbyte(unsigned char& c)
{
 unsigned char a,b;

 if(direction==TO64) {
  outportb(lptportout,0x8);     // send starting 0 1 0 0 0
  while(inportb(lptportin)&0x80);
  outportb(lptportout,0);       // send 0 0 0 0 0
  direction=FROM64;
 }
 while(!(inportb(lptportin)&0x80));   // C64 sending?
 a=inportb(lptportin);
 outportb(lptportout,(a>>3)^0xf);    // ackhi, 1 x x x x
 while(inportb(lptportin)==a);
 if(!((b=inportb(lptportin))&0x80)) {
  outportb(lptportout,(b>>3)^0xf);   // acklo, 0 x x x x
  c=((a<<1)&0xf0)|((b>>3)&0xf);
  return 0;
 }
 return -1;
}


int Joyconnection::init()
{
 unsigned char cycle;
 int chk;
 int err=0;

 do {
  if(kbhit()) {
	switch(getch()) {
	 case 'Q':
    case 'q': err=-2;
			outportb(lptportout,0xff);
	      exit(err);
	}
  }
  chk=0;
  for(cycle=0;cycle<32;cycle++) {
	outportb(lptportout,cycle);
	gotoxy(1,1);
	cprintf("!%02x!",cycle);
	delay(1);
   if(((inportb(lptportin)>>3)^0xf)!=cycle) chk=1;
  }
  if(!chk) {
	outportb(lptportout,0x1a);
   delay(1);
	if((inportb(lptportin)>>3)!=0x1f) chk=1;
  }
 } while(chk && !err);
 gotoxy(1,1);
 cprintf("    ");
 direction=TO64;
 return err;
}

int c64diskfile::exists()
{
 int exists=1;

 if((fpd64==NULL)&&(fpf64==NULL)) {
  exists=0;
  fpd64=fopen(fnamed64,"rb");
  if(fpd64 != NULL) {
	fclose(fpd64);
	fpd64=NULL;
	exists=1;
  } else {
	fpf64=fopen(fnamef64,"rb");
	if(fpf64 != NULL) {
	 fclose(fpf64);
	 fpf64=NULL;
	 exists=2;
	}
  }
 }
 return exists;
}

int c64diskfile::open()
{
 int success=0;
 if(fpd64==NULL) {
  if(mode==READ) {
	fpd64=fopen(fnamed64,"rb");
	if(fpd64!=NULL) {
	 fseek(fpd64,0L,SEEK_END);
	 if(ftell(fpd64)==(683L*256L)) {
	  fseek(fpd64,0L,SEEK_SET);
	  if(fpf64==NULL) {
		fhdr.id1='M';
		fhdr.id2='G';
		fpf64=fopen(fnamef64,"rb");
		if(fpf64!=NULL) {
		 fseek(fpf64,0L,SEEK_END);
		 if(ftell(fpf64)==((long)sizeof(c64fheader)+683L*3L)) {
		  fseek(fpf64,0L,SEEK_SET);
		  fread(&fhdr,sizeof(fhdr),1,fpf64);
		 } else {
		  fclose(fpf64);
		  fpf64=NULL;
		 }
		}
	  }
//   Get Disk ID from F64 file.
//	  if(strncmp(fhdr.hdr,C64FHEADER,sizeof(C64FHEADER))==0)  success=1;
	  success=1;
	 }
	}
	if(!success) {
	 fclose(fpd64);
	 fclose(fpf64);
	 fpd64=NULL;
	 fpf64=NULL;
	}
  } else {
	fpd64=fopen(fnamed64,"wb");
	if(fpd64!=NULL) {
	 fpf64=fopen(fnamef64,"wb");
	 if(fpf64!=NULL) {
	  success=1;
//	 strncpy(fhdr.hdr,C64FHEADER,sizeof(C64FHEADER));
	 fwrite(&fhdr,sizeof(fhdr),1,fpf64);
	 } else {
	  fclose(fpd64);
	  fpd64=NULL;
	 }
	}
  }
 }
 return (success);
}

void c64diskfile::close(int error)
{
 if(fpd64!=NULL) {
  fclose(fpd64);
  if(error&&(mode==WRITE)) { remove(fnamed64); }
  fpd64=NULL;
  }
 if(fpf64!=NULL) {
  fclose(fpf64);
  if(error&&(mode==WRITE)) { remove(fnamef64); }
  fpf64=NULL;
  }
}

void c64diskfile::positiontr(int track)
{
 int i=1,se=0;
 while(i<track) {
  se+=calcsectors(i++);
 }
 fseek(fpd64,256L*(long)se,SEEK_SET);
 fseek(fpf64,(long)sizeof(c64fheader)+3L*(long)se,SEEK_SET);
}

void c64diskfile::gettrack(int track,unsigned char *buf)
{
 int se;
 unsigned char chk;
 int i;

 for (se=0;se<calcsectors(track);se++) {
  *(buf++)=track;
  *(buf++)=se;
  if (fpf64 != NULL) {
    fread(buf,3,1,fpf64);
    buf+=2;
    *(buf+256)=*buf;
    }
  else {
    *(buf++)=0x2b;
    *(buf++)=0x07;
  }
  fread(buf,256,1,fpd64);
  if (fpf64 != NULL)
    buf+=257;
  else {
    chk = '\0';
    for(i=0;i<256;i++)
      chk ^= *(buf++);
    *(buf++) = chk;
  }
 }
}

void c64diskfile::puttrack(int track,unsigned char *buf)
{
 int se;

 for (se=0;se<calcsectors(track);se++) {
  buf+=2;
  fwrite(buf,2,1,fpf64);
  buf+=2;
  fwrite(buf,256,1,fpd64);
  buf+=256;
  fwrite(buf,1,1,fpf64);
  buf++;
 }
}

void c64diskfile::getid(unsigned char& id1,unsigned char& id2)
{
 id1=fhdr.id1;
 id2=fhdr.id2;
}

void c64diskfile::putid(unsigned char& id1,unsigned char& id2)
{
 fhdr.id1=id1;
 fhdr.id2=id2;
 fseek(fpf64,0L,SEEK_SET);
 fseek(fpd64,0L,SEEK_SET);
 fwrite(&fhdr,sizeof(fhdr),1,fpf64);
}

void setupscreen()
{
int i;
 clrscr();
 cputs("The C64 <-> PC Fcopy program !");
 for(i=1;i<36;i++) {
  gotoxy(i+3,2);
  cprintf("%c",i/10+'0');
  gotoxy(i+3,3);
  cprintf("%c",i-(i/10)*10+'0');
 }
 for(i=0;i<21;i++) {
  gotoxy(1,i+4);
  cprintf("%2d",i);
 }
}

int setupfile(c64diskfile& cf)
{
 int abort=0,success=0;
 char fname[80];
 int i;

 while(!abort && !success) {

  gotoxy(5,10);
  cputs("Enter PC filename : ");
  _setcursortype(_NORMALCURSOR);
  i=0;
  while((fname[i++]=getchar())!='\n');
  fname[--i]='\0';
  if(i==0) {
	abort=1;
  } else {
	cf.setname(fname);
	if(cf.getmode()==WRITE) {
	 i=1;
	 if(cf.exists()) {
	  gotoxy(5,11);
	  cputs("Filename exists. Overwrite y/n ? ");
	  i=0;
	  while(i==0) {
		switch(tolower(getche())) {
		 case 'y' : cf.close(-1);
		  i=1;
		  break;
		 case 'n' : i=2;
		  break;
		}
	  }
	 }
	 if(i==1) {
	  if(!cf.open()) {
		gotoxy(5,12);
		cprintf("Could not open file %s.D64 or %s.F64 for writing",fname,fname);
		while(!kbhit()); getch();
	  } else {
		success=1;
	  }
	 }
	} else {
	 if(!cf.open()) {
	  gotoxy(5,11);
	  cprintf("Could not open file or illegal format in %s",fname);
	  while(!kbhit()); getch();
	 } else {
	  success=1;
	 }
	}
  }
  _setcursortype(_NOCURSOR);
  gotoxy(5,12); clreol();
  gotoxy(5,11); clreol();
  gotoxy(5,10); clreol();
 }
 if(abort) cf.close(-1);
 return abort;
}

int getdisk(Joyconnection joy)
{
 int abort=0;
 unsigned char track,sector,scrncode,oldtrack,oldsector,id1,id2;
 int tr,se;
 unsigned char dbuffer[22*sizeof(c64sector)];
 unsigned char *pdb;

 c64diskfile cf(WRITE);

 setupscreen();
 abort=setupfile(cf);
 if(!abort) {
  track='S';        // scan has been modified
  joy.putblock(&track,1);

  oldtrack=0;
  oldsector=0;

  tr=1;
  while(tr<36 && !abort) {

	if(kbhit()) if(tolower(getch())=='q') abort=0xff;
	joy.putblock(&(unsigned char)abort,1);
	if(!abort) {
	 se=calcsectors(tr);
	 pdb=dbuffer;
	 sector='\0';
	 while(sector<se-1) {
	  joy.getblock(pdb,sizeof(c64sector));
	  track=*pdb;
	  sector=*(pdb+1);
	  gotoxy(track+3,sector+4);
	  scrncode=*(pdb+2);
	  cprintf("%c",scrncode);
	  if((track==oldtrack)&&(sector==oldsector)) {
		memmove(pdb-sizeof(c64sector),pdb,sizeof(c64sector));
	  } else {
		pdb+=sizeof(c64sector);
		oldtrack=track;
		oldsector=sector;
	  }
	 }
	 cf.puttrack(tr,dbuffer);
	 tr++;
	}
  }
  if(!abort) {

	joy.getblock(&id1,1);
	joy.getblock(&id2,1);
	cf.putid(id1,id2);
  }
 }
 cf.close(abort);
 return(abort);
}

int putdisk(Joyconnection joy)
{
 int abort=0;
 unsigned char track,sector,scrncode,retry,id1,id2;
 int tr,se;
 unsigned char dbuffer[sizeof(c64sector)*21];
 unsigned char *pdb;
 c64diskfile cf(READ);

 setupscreen();
 abort=setupfile(cf);

 if(!abort) {

  gotoxy(5,10);
  cputs("Insert Destination disk in the C64 drive (Space)");
  do {
	switch(tolower(getch())) {
	 case ' ':abort=0;
		  break;
	 case 'q':abort=-1;
		  break;
	 default :abort=1;
	}
  } while(abort==1);
  gotoxy(5,10); clreol();
 }
 if(!abort) {

  track='M';   // multi has been modified
  joy.putblock(&track,1);

  cf.getid(id1,id2);       // get the two id bytes
  joy.putblock(&id1,1);
  joy.putblock(&id2,1);
  tr=1;
  while(tr<36 && !abort) {

	if(kbhit()) {
	 if(tolower(getch())=='q') abort=0xff;
	}
	joy.putblock(&(unsigned char)abort,1);
	if(!abort) {
	 pdb=dbuffer;
	 se=calcsectors(tr);
	 cf.gettrack(tr,dbuffer);
	 highvideo();
	 while(se-- >0) {

	  joy.putblock(pdb,sizeof(c64sector));
	  track=*pdb;
	  sector=*(pdb+1);
	  gotoxy(track+3,sector+4);
	  scrncode=*(pdb+2);
	  cprintf("%c",scrncode);
	  pdb+=sizeof(c64sector);
	 }
	 normvideo();
	 do {
	  joy.getblock(&retry,1);
	  if(retry) {
		gotoxy(1,1);
		_setcursortype(_NORMALCURSOR);
		cprintf("Error writing track %d. Retry,Ignore or Quit ? ",tr);
		do {
		 switch(tolower(getch())) {
	case 'q': retry='Q';
		  abort=1;
		  break;
	case 'r': retry='R';
		  break;
	case 'i': retry='I';
		  break;
	default : retry=0;
		 }
		} while(retry==0);
		gotoxy(1,1);
		clreol();
		_setcursortype(_NOCURSOR);
		joy.putblock(&retry,1);
	  }
	 } while(retry);
	}
	tr++;
  }
 }
 cf.close(abort);
 return(abort);
}

void c64puts(unsigned char *s,int n)
{
 unsigned char c;
 while(n--) {
  c=*(s++);
  if(c==0xa0) c=0x20;
  if(c<0x20) c|=0x80;
  putch(c);
 }
}

void printdir(unsigned char *dbuffer)
{
 unsigned char blocks[5],*pdb,track,sector;
 int tr,se,row,i,blfree;

 static char c64filetype[][4] = {
  "DEL",
  "SEQ",
  "PRG",
  "USR",
  "REL",
  "EL?",
  "EQ?",
  "RG?",
  "SR?",
  "EL?",
  "L??",
  "Q??",
  "G?B",
  "R?L",
  "L?O",
  "H?C"
  };

 gotoxy(45,1);
 highvideo();
 cputs("      ");
 c64puts(dbuffer+0x90+4,23);
 normvideo();

// parse sectors.

 track=18;
 sector=1;
 se=21;
 row=2;
 while(track==18) {
  pdb=dbuffer+sizeof(c64sector)*sector+4;
  track=*(pdb++);
  sector=*(pdb++);
  for(i=0;i<8;i++) {
	if(row==25) {
	 while(!kbhit());
	 getch();
	 while(--row>2) {
	  gotoxy(45,row);
	  clreol();
	  }
	 }
	if(*pdb!='\0') {
	 gotoxy(45,row++);
	 cprintf("%5d ",(int) (*(unsigned int *)(pdb+0x1c)));
	 c64puts(pdb+3,16);
	 cprintf("   %s%c",c64filetype[*pdb&0xf],((*pdb&0x40)?'<':' '));
	}
	pdb+=32;
  }
  if(--se<0) track=0; // cyclic directories never stop.
 }
 blfree=0;
 for(tr=1;tr<36;tr++) {
  blfree+=(int)(*(dbuffer+4+tr*4));
  if(tr==17) tr++;
  }
 gotoxy(45,row);
 cprintf("%5d BLOCKS FREE.",blfree);
 while(!kbhit());
 getch();
}

void showc64dir(Joyconnection joy)
{
 unsigned char track,sector,oldtrack,oldsector;
 int se,i,row;
 unsigned char dbuffer[sizeof(c64sector)*22];
 unsigned char *pdb;
 track='D';
 joy.putblock(&track,1);
 se=calcsectors(18);
 track='\0';
 joy.putblock(&track,1);    // No abort
 oldtrack=0;
 oldsector=0;
 pdb=dbuffer;
 sector='\0';
 while(sector<se-1) {
  joy.getblock(pdb,sizeof(c64sector));
  track=*pdb;
  sector=*(pdb+1);
  if((track==oldtrack)&&(sector==oldsector)) {
	memmove(pdb-sizeof(c64sector),pdb,sizeof(c64sector));
  } else {
	pdb+=sizeof(c64sector);
	oldtrack=track;
	oldsector=sector;
  }
 }
 printdir(dbuffer);
}

void showc64filedir()
{
 unsigned char dbuffer[sizeof(c64sector)*21];
 int abort,se;

 c64diskfile cf(READ);

 setupscreen();
 abort=setupfile(cf);
 if(!abort) {
  cf.positiontr(18);
  cf.gettrack(18,dbuffer);
  printdir(dbuffer);
 }
}

void toggleretries(int& r,Joyconnection j)
{
 unsigned char tmp='T';

 j.putblock(&tmp,1);

 if(r==1) {
  r=5;
 } else {
  r=1;
 }
}

#define BIOSLPT1ADR 0x408L

int main(int argc,char *argv[])
{
 int lpt=1;
 int port=0;
 char c;
 int quit;
 int retries=1;

 normvideo();
 while(argc--) {
  if(strnicmp(argv[0],"lpt",3)==0) {
	lpt=(*argv[3]-'0');
  }
 }
 if((lpt<5) && (lpt>0)) {
  port=*(long far*)(BIOSLPT1ADR+(lpt-1)*2);
  }
 if(!port) {
  printf("No LPT%c port found!\n",lpt+'0');
 } else {

  Joyconnection j(port);

  _setcursortype(_NOCURSOR);
  clrscr();

  gotoxy(1,3);

  cprintf("Waiting for connection...\r\n");
  cprintf("Press 'q' to quit\r\n");

  if(j.init()) {
	cprintf("abandoning program.");
  } else {
	quit=0;
	while(!quit) {
	 setupscreen();
	 gotoxy(5,5);
	 cprintf("(c) 1993 The Whiz-zards Association");
	 gotoxy(5,7);
	 cprintf("  1 - Backup C64 disk to PC\r\n");
	 gotoxy(5,8);
	 cprintf("  2 - Restore C64 disk from PC\r\n");
	 gotoxy(5,9);
	 cprintf("  3 - Show C64 directory\r\n");
	 gotoxy(5,10);
	 cprintf("  4 - Show PC directory\r\n");
	 gotoxy(5,11);
	 cprintf("  5 - Show C64 directory from PC file");
	 gotoxy(5,12);
	 cprintf("  6 - Read retries on C64 (now %d)",retries);
	 gotoxy(5,14);
	 cprintf("  Q - Quit\r\n");
	 gotoxy(5,15);
	 cprintf("Pressing 'Q' during an option aborts.");
	 gotoxy(5,16);
	 cprintf("Please enter your choice : ");
	 _setcursortype(_NORMALCURSOR);
	 c=tolower(getche());
	 _setcursortype(_NOCURSOR);
	 switch(tolower(c)) {
	  case '1': getdisk(j);
			 break;
	  case '2': putdisk(j);
			 break;
	  case '3': showc64dir(j);
			 break;
	  case '4': clrscr();
			 system("dir");
			 getch();
			 break;
	  case '5': showc64filedir();
			 break;
	  case '6': toggleretries(retries,j);
			 break;
	  case 'q': quit=1;
			 break;
	 }
	}
  }
  j.abort();
 }
 return(0);
}


