#include <exec/types.h>
#include <exec/io.h>
#include <proto/exec.h>
#include <devices/serial.h>
#include <devices/timer.h>
#include <libraries/dos.h>
#include <proto/dos.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <dos.h>

#include <dlg/dlg.h>
#include <dlg/dlgproto.h>

#include <proto/dlg.h>

#include <pragmas/dlg.h>

#include "xmodem.h"

void  SendSerChar(int);
int   ReadSerChar(int);
BOOL  XMODEM_Receive(char *);
BOOL  XMODEM_Send(char *);
void  SendMsg(char *);
void  CanTransfer(void);
BOOL  CarrierDetect(void);
BOOL  CheckForAbort(void);
BOOL  OpenTimer(void);
void  CloseTimer(void);
void  CleanUp(char *);
void  Usage(char *);
void _CXBRK(void);

struct Library     *DLGBase = NULL;
struct DLGSerInfo  *dsi     = NULL;
struct timerequest *Timer;
struct MsgPort     *Timer_Port;

int    timeout;
char   mydevice[4];
char   in_c;
char   out_c;

SHORT  BufSize = 8192;
UBYTE  numcans =   0;
BOOL   send    = TRUE;
BOOL   docheck = FALSE;
BOOL   onek    = FALSE;
BOOL   quiet   = FALSE;
BOOL   fstime;
long   ttime;

static char version[]="$VER: DLGXModem 2.0 © 1995-1998 by Digerati Dreams "__AMIGADATE__"";

main(int argc,char **argv)

{char  protocol = 'X';
 char *filename =  NULL;

 DLGBase = (struct Library *)OpenLibrary(DLGNAME, 0);
 if (!DLGBase)  CleanUp("Unable to open dlg.library");

 while(--argc>0)
      {char *s;

       s = *++argv;
       if (*s++ == '-')
          {while(*s)
                {switch(*s++)
                       {case 's':
                        case 'S': send = TRUE;
                                  break;

                        case 'r':
                        case 'R': send = FALSE;
                                  break;

                        case 'c':
                        case 'C': docheck = TRUE;
                                  break;

                        case 'k':
                        case 'K': onek = TRUE;
                                  break;

                        case 'q':
                        case 'Q': quiet = TRUE;
                                  break;

                        case 'p':
                        case 'P': if (!--argc) break;
                                  protocol=*(*++argv);
                                  break;

                        case 'f':
                        case 'F': if (!--argc) break;
                                  filename=*++argv;
                                  break;

                         default: Usage("Bad switch");
                                  break;
                       }
                }
          }
      }

 if (!filename)                                 Usage("You must specify a file name");
 if (GetDevName(mydevice)==-1)                  CleanUp("Unable to get device name");
 if (OpenTimer()==0)                            CleanUp("Unable to open timer");
 if ((dsi=DLGGetSer(mydevice,protocol))==NULL)  CleanUp("Unable to open serial.device");

 dsi->write->IOSer.io_Command = CMD_WRITE;
 dsi->write->IOSer.io_Length  =    1;
 dsi->write->IOSer.io_Data    = (APTR)&out_c;

 dsi->read->IOSer.io_Command  = CMD_READ;
 dsi->read->IOSer.io_Length   =    1;
 dsi->read->IOSer.io_Data     = (APTR)&in_c;

 BeginIO((struct IORequest *)dsi->read);

 if (send)
    {if (!XMODEM_Send(filename))     CleanUp("Send Failed");
    }
  else
     if (!XMODEM_Receive(filename))  CleanUp("Receive Failed");

 CleanUp(NULL);
}


void SendSerChar(int ch)

{out_c = ch & 0xFF;
 DoIO((struct IORequest *)dsi->write);
}


int ReadSerChar(int mode)

{int rd,ch;

 Timer->tr_time.tv_secs  = ttime;
 Timer->tr_time.tv_micro =   0;
 SendIO((struct IORequest *)Timer);

 rd = FALSE;
 while(rd == FALSE)
      {Wait((1 << dsi->read->IOSer.io_Message.mn_ReplyPort->mp_SigBit) | (1 << Timer_Port->mp_SigBit));

       if (CheckIO((struct IORequest *)dsi->read))
          {WaitIO((struct IORequest *)dsi->read);
           ch = in_c;
           rd = TRUE;
           BeginIO((struct IORequest *)dsi->read);
          }

       if (rd == FALSE && CheckIO((struct IORequest *)Timer))
          {if (!CarrierDetect())
              {SendMsg("Carrier Lost...Aborting");
               timeout = NOCARRIER;
               return(0);
              }

           SendMsg("Timeout");
           timeout = TIMEOUT;
           return(0);
          }
      }

 AbortIO((struct IORequest *)Timer);
 Wait   (1 << Timer_Port->mp_SigBit);

 ch &= 0xFF;

 if (mode && (ch==CAN))
    {numcans++;
     if (numcans==3)
        {SendMsg("User aborted transfer");
         timeout = USERABORT;
         return(0);
        }
    }

 timeout = GOODREAD;
 return(ch);
}


BOOL XMODEM_Receive(char *file)
{
  int firstchar, sectnum, sectcurr, sectcomp, errors, errorflag, secsiz=128;
  int fd;
  unsigned short checksum, incheck, j, i, bufptr;
  char bufr[8192], *memptr;
  UBYTE numcs;
  long bytes_xferred;

  bytes_xferred = 0;
  ttime = TTIME_SHORT;

  if((fd = creat(file, 0)) < 0) {
    SendMsg("Cannot Open File");
    return(FALSE);
  }
  else SendMsg("Receiving File");

  sectnum=errors=bufptr=0;
  firstchar = 0;
  fstime=TRUE;
  numcs=0;

  if(docheck) SendSerChar(NAK);
  else SendSerChar('C');

  while(firstchar != EOT && errors != ERRORMAX) {
    errorflag = FALSE;

    do {       /* get sync char */
      if(CheckForAbort()) {
   close(fd);
   return(FALSE);
      }

      firstchar = ReadSerChar(1);
      if(timeout != GOODREAD) {
   if((timeout == USERABORT)||(timeout==NOCARRIER)) {
     close(fd);
     return(FALSE);
   }

        if(errors++ == ERRORMAX) {
     SendMsg("Errored out...Aborting Transfer");
     CanTransfer();
     close(fd);
     return(FALSE);
   }

   if(fstime)
     if(numcs>3) docheck=TRUE; else numcs++;

   if(docheck) SendSerChar(NAK);
   else SendSerChar('C');
      }
    }
    while(firstchar != SOH && firstchar != STX && firstchar != EOT);

    fstime=FALSE;

    if (firstchar != EOT)
       {if (firstchar == SOH)
            secsiz = 128;
          else
            secsiz = 1024;

      sectcurr = ReadSerChar(0);

      if(timeout != GOODREAD) {
   CanTransfer();
   close(fd);
   return(FALSE);
      }

      sectcomp = ReadSerChar(0);

      if(timeout != GOODREAD) {
   CanTransfer();
   close(fd);
   return(FALSE);
      }

      if((sectcurr + sectcomp) == 255) {
   if(sectcurr == ((sectnum + 1) & 0xff)) {
     checksum=0;

     for(j = bufptr; j < (bufptr + secsiz); j++) {
       bufr[j] = ReadSerChar(0);

       if(timeout != GOODREAD) {
         CanTransfer();
         close(fd);
         return(FALSE);
       }

       if(docheck) checksum = (checksum + bufr[j]) & 0xff;
       else {
         checksum = checksum ^ (int)bufr[j] << 8;
         for(i = 0; i < 8; ++i)
           if(checksum & 0x8000) checksum = checksum << 1 ^ 0x1021;
           else checksum = checksum << 1;
       }
     }

     if(docheck) incheck=ReadSerChar(0);
     else {
       checksum &= 0xFFFF;

       memptr=(char *)&incheck;
       *memptr=(char)ReadSerChar(0);
       if(timeout == GOODREAD) *(memptr+1)=(char)ReadSerChar(0);
     }

     if((timeout == GOODREAD) && (checksum == incheck)) {
       errors = 0;
       sectnum++;
       bufptr += (long)secsiz;
       bytes_xferred += (long)secsiz;

       DLGProtoStatus(dsi,NULL,bytes_xferred,NULL);

       if(bufptr == BufSize) {
         if(write(fd,bufr,BufSize) == EOF) {
           SendMsg("Error Writing File");
      close(fd);
           return(FALSE);
         }
         bufptr = 0;
         for (j = 0; j < secsiz; j++) bufr[j] = bufr[(BufSize-secsiz)+j];
       }
       SendSerChar(ACK);
     }
     else {
       errorflag = TRUE;
       if(timeout!=GOODREAD) SendMsg("Bad Checksum");
     }
      }
   else {         /* got a duplicate sector */
     if(sectcurr == (sectnum & 0xff)) {
       /* wait until we time out for 5secs */
       do {
         ReadSerChar(0);
       }
       while(timeout == GOODREAD);

       SendMsg("Received Duplicate Sector");
       SendSerChar(ACK);
     }
     else {
       errorflag = TRUE;
       SendMsg("Bad sector");
     }
   }
      }
      else errorflag = TRUE;
    }

    if(errorflag == TRUE) {
      errors++;
      SendSerChar(NAK);
    }
  }

  if((firstchar == EOT) && (errors < ERRORMAX)) {
    SendSerChar(ACK);
    write(fd, bufr, bufptr);
    close(fd);
    return(TRUE);
  }

  CanTransfer();
  close(fd);
  return(FALSE);
}

BOOL XMODEM_Send(char *file)
{
  int sectnum, bytes_to_send, size, attempts, c, secsiz;
  int fd;
  unsigned checksum, j, i, bufptr;
  char  bufr[8192];
  long  bytes_xferred;
  ULONG filesize;

  bytes_xferred = 0L;
  ttime = TTIME_LONG;

  if((fd = open(file, 0)) < 0) {
    SendMsg("Cannot Open Send File");
    return FALSE;
  }

  FileSize(file,&filesize);

  DLGProtoStatus(dsi,filesize,NULL,NULL);
  SendMsg("Sending File");

  attempts = 0;
  sectnum = 1;
  docheck=TRUE;

  /* wait for sync char */

  j=1;

  for(;;) {
    if(CheckForAbort()) {
      close(fd);
      return(FALSE);
    }

    c = ReadSerChar(1);

    if(c == NAK) {
      SendMsg("User requested Checksum mode");
      onek=FALSE;
      break;
    }

    if(c == 'C') {
      SendMsg("User requested CRC mode");
      docheck=FALSE;
      break;
    }

    if(j++ >= ERRORMAX) break;

    if((timeout == USERABORT)||(timeout == NOCARRIER)) {
      close(fd);
      return(FALSE);
    }
  }

  if(j >= (ERRORMAX)) {
    SendMsg("Receiver not sending NAKs");
    CanTransfer();
    close(fd);
    return(FALSE);
  }

  if(onek) secsiz=1024;
  else secsiz=128;

  while((bytes_to_send = read(fd, bufr, BufSize)) && attempts != RETRYMAX) {
    if(bytes_to_send == EOF) {
      SendMsg("Error Reading File");
      CanTransfer();
      close(fd);
      return(FALSE);
    }

    bufptr = 0;

    while(bytes_to_send > 0 && attempts != RETRYMAX) {
      if(CheckForAbort()) {
   close(fd);
   return(FALSE);
      }

      attempts = 0;

      do {
   if(onek) SendSerChar(STX);
   else SendSerChar(SOH);
   SendSerChar(sectnum);
   SendSerChar(~sectnum);

   checksum = 0;
   size = secsiz <= bytes_to_send ? secsiz : bytes_to_send;
   bytes_to_send -= size;

   for(j = bufptr; j < (bufptr + secsiz); j++) {
     if(j >= (bufptr + size)) bufr[j]=CPMEOF;

     SendSerChar(bufr[j]);
     if(docheck) checksum += bufr[j];
     else {
       checksum = checksum ^ (int)bufr[j] << 8;
       for(i = 0; i < 8; ++i)
         if(checksum & 0x8000) checksum = checksum << 1 ^ 0x1021;
         else checksum = checksum << 1;
     }
   }

   if(docheck) SendSerChar(checksum);
   else {
     checksum &= 0xFFFF;
     SendSerChar(checksum >> 8);
     SendSerChar(checksum & 0xff);
   }

   attempts++;

   c = ReadSerChar(1);

   if((timeout == USERABORT)||(timeout == NOCARRIER)) {
     close(fd);
     return(FALSE);
   }
      }
      while((c != ACK) && (attempts != RETRYMAX));

      bufptr += size;
      bytes_xferred += (long)size;
      DLGProtoStatus(dsi,NULL,bytes_xferred,NULL);

      sectnum++;
    }
  }

  close(fd);

  if(attempts == RETRYMAX) {
    SendMsg("No Acknowledgment Of Sector...Aborting");
    CanTransfer();
    close(fd);
    return(FALSE);
  }
  else {
    attempts = 0;

    do {
      SendSerChar(EOT);
      attempts++;
    }
    while((ReadSerChar(1) != ACK) && (attempts != RETRYMAX)
     && (timeout != USERABORT) && (timeout != NOCARRIER));

    if(attempts == RETRYMAX) SendMsg("No Acknowledgment Of End Of File");
  }

  return(TRUE);
}


void SendMsg(char *s)

{if (!quiet)
      DLGProtoStatus(dsi,NULL,NULL,s);

 Delay(50);
}


void CanTransfer(void)

{SendSerChar(CAN);
 SendSerChar(CAN);
 SendSerChar(CAN);
 SendSerChar(CAN);
 SendSerChar(CAN);
}


BOOL CarrierDetect(void)

{UWORD cd;

 AbortIO((struct IORequest *)dsi->read);
 WaitIO ((struct IORequest *)dsi->read);

 dsi->read->IOSer.io_Command = SDCMD_QUERY;
 DoIO((struct IORequest *)dsi->read);

 cd                          = dsi->read->io_Status;
 dsi->read->IOSer.io_Command = CMD_READ;
 dsi->read->IOSer.io_Length  =   1;
 dsi->read->IOSer.io_Data    = (APTR) &in_c;
 BeginIO((struct IORequest *)dsi->read);

 return((BOOL)!(cd & 1<<5));
}


BOOL CheckForAbort(void)

{if (SetSignal(0, SIGBREAKF_CTRL_C) & SIGBREAKF_CTRL_C)
    {SendMsg("Received ^C...Aborting");
     return(TRUE);
    }

 return(FALSE);
}


BOOL OpenTimer(void)

{if (Timer_Port = CreatePort(NULL,NULL))
    {if ((Timer=(struct timerequest *)CreateExtIO(Timer_Port,sizeof (*Timer))))
        {if (!(OpenDevice(TIMERNAME, UNIT_VBLANK,(struct IORequest *)Timer, 0)))
            {Timer->tr_node.io_Command = TR_ADDREQUEST;
             Timer->tr_node.io_Flags   = NULL;
             Timer->tr_node.io_Error   = NULL;
             return(TRUE);
            }
          else
             Timer->tr_node.io_Device = NULL;
        }
    }

 return(FALSE);
}


void CloseTimer(void)

{if (Timer)
    {if (Timer->tr_node.io_Device)
         CloseDevice((struct IORequest *)Timer);
     DeleteExtIO((struct IORequest *)Timer);
    }

 if (Timer_Port)  DeletePort(Timer_Port);
}


void CleanUp(char *s)

{if (dsi)
    {AbortIO((struct IORequest *)dsi->read);
     WaitIO((struct IORequest *)dsi->read);

     SetSignal(0,dsi->read->IOSer.io_Message.mn_ReplyPort->mp_SigBit);
     DLGReleaseSer(dsi);
    }

 CloseTimer();
 if (DLGBase)  CloseLibrary(DLGBase);
 if (s)        printf("\n Error: %s\n\n",s);

 exit(s?5:0);
}


void Usage(char *string)

{printf("\n %s\n\n",string);
 printf(" Usage:  DLGXmodem [-(s|r) -c -k -q -p <protocolchar>] -f <filename>\n\n");
 printf("   Description of Switches:\n\n");
 printf("   -s: Send file\n");
 printf("   -r: Receive file\n");
 printf("   -c: Force checksum when receiving\n");
 printf("   -k: Send 1K blocks\n");
 printf("   -q: Quiet mode - do not monitor transfer\n");
 printf("   -p: Designate the one-character label for this protocol\n");
 printf("   -f: Designate the name of the file to send\n\n");

 CleanUp(NULL);
}


void _CXBRK(void)

{CleanUp(NULL);
}
