/*****************************************************************************
**                                                                          **
**  XMODEM.DLP                                                              **
**                                                                          **
**  This is a sample (DLP) DLL to implement the XMODEM protocol             **
**                                                                          **
**  Six calls are made to the (DLP) by PW4.EXE                              **
**     GET_DLL_CAPS (LPSTR)                                                 **
**     STARTDLL_XFER (PROTOSET FAR *, MAIN FAR *, HWND,                     **
**                    struct ADVANCEDSET FAR *, int, int)                   **
**     DLL_XFER (PROTOSET FAR *, MAIN FAR *, HWND, struct ADVANCEDSET FAR *,**
**                    int)                                                  **
**     INITDLGITEMS (HWND, struct ADVANCEDSET FAR *)                        **
**     SETWM_COMMAND (HWND, struct ADVANCEDSET FAR *, int, int)             **
**     GETDLGITEMS (HWND, struct ADVANCEDSET FAR *)                         **
**                                                                          **
**  PROTOSET is a struct of PW4.EXE variables which the protocol can        **
**           use. It also contains callback fuctions for all the            **
**           communication functions necessary to implement a               **
**           protocol. A large global buffer is already allocated           **
**           (10240 bytes) and pd->bigptr is a locked pointer to this       **
**           memory.  66 extra bytes are contained in the struct to         **
**           be cast and used by the (DLP) as required. Reference           **
**           PWDEFDLP.H.  The (DLP) should use this area for any global     **
**           variables it requires, thus supporting multiple instances      **
**           use of the protocol.                                           **
**                                                                          **
**  GENERALSET is a struct of PW4.EXE variables of a more general nature	 **
**           	which the protocol can use. Reference PWDLP.H.               **
**                                                                          **
**  ADVANCEDSET is a struct of advanced setup options defined in XMODEM.H.  **
**           It is used to keep dialog box settings.  The struct definition **
**           is referring to a 325 byte area defined in PW.                 **
**                                                                          **
**     Note: To allow ASPECT to determine sucess or failure of the          **
**           protocol * (pd->aspfxfer) should be set to equal to            **
**           ASP_PROT_FAIL on entry and set to ASP_PROT_SUCCESS on          **
**           termination if successful.                                     **
**                                                                          **
*****************************************************************************/

#define _WINDLL
#include <windows.h>
#include <string.h>
#include <io.h>
#include <dos.h>

#include "gpwindlx.h"
#include "pwdlx.h"
#include "pwdefdlx.h"
#include "ddirdlx.h"
#include "cbackdlx.h"
#include "xmodem.h"

/****************************************************************************
*                                                                           *
* Global variables used by xmodem.dlp                                       *
*                                                                           *
* WARNING: Do not rely on global data to retain values across multiple      *
* calls. In other words, reload the data values on each call into the DLP!  *
*                                                                           *
****************************************************************************/

HANDLE               hInst;            // This won't change from call to call
PROTOSET           * pd;
MAIN               * mn;
ADVANCEDSET        * adv;
HWND                 hWnd;
INSTANCEDATA       * fpInstD;
char                 szBuffer[128];

#define NOCHARWAIT   1000
#define _20_Secs     20000
#define _10_Secs     10000
#define _10K         10240
#define _NAK         (LPSTR)"\x15"
#define _ACK         (LPSTR)"\x06"
#define _EOT         (LPSTR)"\x04"

/****************************************************************************
*                                                                           *
*                     1. DLL entry and exit routine                         *
*                                                                           *
****************************************************************************/

int WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
   if(dwReason == DLL_PROCESS_ATTACH)
      hInst = hInstance;   // save instance handle for dialog box and resources
   else if(dwReason == DLL_PROCESS_DETACH)
      ;
   return 1;   // ok
}

/****************************************************************************
*                                                                           *
*                     2. Exported DLL Routines.                             *
*                                                                           *
****************************************************************************/

/*-----------------------------------------------------
                                                      |
   GET_DLL_CAPS () - Get DLL Capabilities             |
                                                      |
   This procedure is called by PW4.EXE when a new .DLP|
   file is found in the PW4.EXE directory.  The .DLP  |
   file will return the bitflags of its capablities:  |
                                                      |
   DLL_CAP_SHOWXFER     - requests PW4.EXE to show    |
                          its normal xfer dlg box     |
   DLL_CAP_MULTISEND    - (DLP) is capable of sending |
                          multiple files              |
   DLL_CAP_AUTODOWNLOAD - (DLP) has the capablity to  |
                          automatically download files|
   DLL_CAP_PWOPENFILE   - requests PW4.EXE to open the|
                          inital file                 |
   DLL_CAP_RCVNEEDNAME  - PW4.EXE is to prompt user   |
                          for a rcv file name         |
   DLL_CAP_ADVANCED     - (DLP) has advanced dialog   |
                          box for user setup          |
                                                      |
   If (DLP) returns DLL_CAP_AUTODOWNLOAD, then the    |
   auto detect string (null terminated, 11 char max.) |
   should be copied to the lptr passed into the       |
   function.                                          |
                                                      |
-----------------------------------------------------*/

EXPORTED WORD CALLBACK GET_DLL_CAPS (LPSTR lptr)
{
   WORD i = DLL_CAP_ADVANCED | DLL_CAP_RCVNEEDNAME;

// NOTE: if this protocol supports autodownloading (e.g. CISB, ZMODEM, etc.)
// uncomment the next two lines (and define the trigger string)
// i |= DLL_CAP_AUTODOWNLOAD;          // Tell PW4 we support autodownloads
// lstrcpy (lptr, "0x1b0x1b");         // Tell PW4 what string triggers autodownld
   return( i );
}

/*-----------------------------------------------------
                                                      |
   STARTDLL_XFER ()                                   |
                                                      |
   Allows (DLP) to initialize variables and start the |
   actual xfer.                                       |
      send      = TRUE if sending a file,             |
                  FALSE if receiving a file           |
      from_auto = TRUE if called from auto detect     |
                  sequence                            |
      extra     = ptr to a 3-byte string.  Not used.  |
-----------------------------------------------------*/

EXPORTED int CALLBACK STARTDLL_XFER (PROTOSET FAR *inpd, MAIN FAR *inmn, HWND hwnd,
                              struct ADVANCEDSET FAR *AdvSet, int send,
                              int from_auto)
{
   pd = inpd;                          // set our global ptrs so the rtnes...
   mn = inmn;                          // .. can use the values in pd & mn
   adv = AdvSet;
   fpInstD = (INSTANCEDATA FAR *)inpd->temp;

// NOTE: Uncomment the following lines if you need to search for strings to
// automatically initiate downloading:
// Initialize variables needed if auto downloading (e.g. CISB, ZMODEM)
// if (from_auto)
// {
//    lstrcpy ( pd->szTempDir, "d:\\pw");    // identify dest. for download
//    lstrcpy ( pd->szTempName, "temp.gif"); // assign downloaded file name
// }

   if (!FileRxTX (send, pd->szTempName))  // open designated file
   {
      P_EndXfer ();
      return (FALSE);
   }

   // Initialize global varables
   pd->rec_len       = 128;
   pd->runcnt        = 0;
   pd->xcount        = 0;
   fpInstD->Aborting = FALSE;
   fpInstD->Retries  = 3;              // set #times to retry starting xfer

   // if requested show xfer box, ASPECT can request this to be FALSE
   if (pd->bdiag)
   {
      P_ShowXferBox (send);            // Call PW4.EXE function to show xfer box
      SetWindowText (pd->hDlg1, "DLL [XModem]");
      LastError (0);                   // initialize TRANSFER STATUS to spaces
   }
   if (send)
   {
      fpInstD->PCState   = IDX_WAITACK; // set state to show we are xmitting
      fpInstD->BBegin    = 1;           // BBegin = 1 is initial Send State
      fpInstD->EndRecord = 255;
      pd->bigptr         = pd->bigbuff + _10K;
   }
   else                                // we're receiving a file
   {
      fpInstD->PCState   = IDX_RXING;  // set state to show we are receiving
      fpInstD->BBegin    = 2;          // BBegin = 2 is initial Receive State
      fpInstD->EndRecord = 0;
      pd->xwaittime = GetTickCount()+_10_Secs;  // set timout = 10 secs
      pd->rec_no    = 1;                        // initialize record count
      P_WriteCommW (mn->hCid, _NAK, 1);         // start off by sending NAK
   }
   return (TRUE);
}

/*-----------------------------------------------------
                                                      |
   DLL_XFER ()                                        |
                                                      |
   Called every timer tick (~55msecs)                 |
   Used to drive a state machine.                     |
                                                      |
-----------------------------------------------------*/

EXPORTED int CALLBACK DLL_XFER (PROTOSET FAR *inpd, MAIN FAR *inmn, HWND hwnd,
                         struct ADVANCEDSET FAR *AdvSet, int send)
{
   pd = inpd;                          // set DS-based ptrs for faster access
   mn = inmn;
   adv = AdvSet;
   fpInstD = (INSTANCEDATA FAR *)inpd->temp;  // set ptr to Instance Data

   switch (fpInstD->PCState)           // switch on xfer state
   {
      case IDX_RXING:                  // receive state
         RXING ();
         break;
      case IDX_NOCHAR:                 // waiting to receive a char
         NOCHAR ();
         break;
      case IDX_WAITACK:                // waiting to receive an ACK (sending)
         WAITACK ();
         break;
   }
   return (TRUE);
}

/****************************************************************************
*                                                                           *
*               3. Advanced Setup Options Callback Routines.                *
*                                                                           *
* The pointer passed in to the ADVANCEDSET struct is for the DLP's own use  *
* as it sees fit.  It can hold up to 325 bytes of data for the DLP.  Do     *
* not store setup information in local variables.  Since the DLPs can be    *
* accessed by multiple instances of PW4, setup options & data will be lost. *
****************************************************************************/

EXPORTED void CALLBACK INITDLGITEMS (HWND hDlg, struct ADVANCEDSET FAR *AdvSet)
{
   CheckRadioButton (hDlg, 14605, 14606, 14605+AdvSet->CRC);
   CheckRadioButton (hDlg, 14601, 14602, 14601+AdvSet->Block1024);
}

EXPORTED BOOL CALLBACK SETWM_COMMAND (HWND hDlg, struct ADVANCEDSET FAR *AdvSet,
                                      int Id, int Cmd)
{
   // No WM_COMMAND processing required here
   // Typically only needed if using push buttons
   return FALSE;
}

EXPORTED BOOL CALLBACK GETDLGITEMS (HWND hDlg, struct ADVANCEDSET FAR *AdvSet)
{
   AdvSet->CRC = IsDlgButtonChecked (hDlg, 14606);
   AdvSet->Block1024 = IsDlgButtonChecked (hDlg, 14602);
   return (TRUE);
}

/***************************************************************************/
/*                                                                         */
/*                        4. Local Routines.                               */
/*                                                                         */
/***************************************************************************/

/*-----------------------------------------------------
                                                      |
   High-level RECEIVE State Processing Routine        |
                                                      |
-----------------------------------------------------*/

static void PASCAL RXING (void)
{
   LPSTR lptr;
   int i;

   //--  First check to see if the file xfer has been canceled
   if (pd->bCancel)                    // set if user pressed cancel on the xfer box
   {
      if (pd->hDlg1)                   // if xfer box is diplayed, show "Aborting by user"
         LastError (4);                // IDS_LASTERROR+3    "Aborting"
      End_It (17);                     // IDS_LASTERROR+16   "Transfer Aborted by User"
      return;                          // user pressed cancel, so terminate */
   }

   //--  Next, handle any timeout issues
   if (GetTickCount() > pd->xwaittime) // If we've waited long enough....
   {
      if (pd->hDlg1)                   // do we have our dialog box up?
         LastError (1);                // IDS_LASTERROR  "Timed out"

      //--  If we haven't started the file XFER yet, send a NAK to the other
      //    side to tell him to start sending.
      if (fpInstD->BBegin == 2)        // Are we in Initial Receive State?
      {
         if (--fpInstD->Retries == 0)  // Only retry 3 times to start xfer
            End_It (14);               // IDS_LASTERROR+13 "Transfer Aborted Timed Out"
         else
         {
            P_WriteCommW (mn->hCid, _NAK, 1);           // send a NAK
            pd->xwaittime = GetTickCount() + _10_Secs;  // set next timeout
         }
         return;
      }

      //--  Special Case: we timed out after sending EOF, waiting for other
      //                  side to send NAK
      if (fpInstD->BBegin == 99)
      {
         if (_lwrite ((HFILE)pd->hCommFile, pd->bigbuff,
            (WORD)(pd->bigptr-pd->bigbuff)) != (WORD)(pd->bigptr-pd->bigbuff))
            StatusLine (IDS_LASTERROR+19, NULL);
            // IDS_LASTERROR+19    "Transfer Aborted File Write Error"
         else
            StatusLine (IDS_LASTERROR+14, pd->szRcvFile);
            // IDS_LASTERROR+14    "Transfer Complete -"

         //--  Here's where we send an ACK after we sent a NAK but got no
         //    response and protocol != batch stuff
         P_WriteCommW (mn->hCid, _ACK, 1);
         *pd->aspfxfer = ASP_PROT_SUCCESS;
         P_EndXfer ();                 // exit xfer
         return;
      }

      //--  OK, we're timing out waiting for a response.  After a couple of
      //    timeouts, see if sending a NAK to the other side starts things
      //    back up.
      pd->xwaittime = GetTickCount() + _20_Secs;  // set next timeout
      if (fpInstD->BBegin > 2)         // If > initial recv state
         fpInstD->BBegin += 1;         // bump state
      else
         fpInstD->BBegin = 3;
      if (fpInstD->BBegin == 5)
         P_WriteCommW (mn->hCid, _NAK, 1);
      return;
   }  //--  End of handling timeout condition

   //--  OK, we haven't been canceled, and we haven't timed out.
   //    Read the next 128-byte record and process it
   switch (GetXRecord ())
   {
      case IDP_ACK:
         //--  If the block number is the one we expected to receive AND
         //    it's not just a bogus block number that happens to be what
         //    we're looking for...
         if (((BYTE)pd->rec_no == (BYTE)*(pd->cbuff+1)) &&
             ((BYTE)(*(pd->cbuff+2)+pd->rec_no) == 255))
         {
            lptr = pd->cbuff;
            lptr += 3;
            P_WriteCommW (mn->hCid, _ACK, 1);  // send an ACK

            /************************************************
            * If we're not in a gif state and we want to be *
            ************************************************/
            if (!mn->gifstate && pd->should_gif)
            {
               /*****************************
               * If this is the first block *
               *****************************/
               if (pd->runcnt == 0)
               {
                  AnsiLower ((LPSTR)pd->szRcvFile);
                  if (_fstrstr (pd->szRcvFile, ".gif"))
                     P_startgif ();
               }
            }

            if (mn->gifstate)
            {
               //-- Now process each data byte in the incoming xmodem block
               for (i=0;  i < pd->rec_len;  i++)
               {
                  if (! P_FeedDisplay ((BYTE)*lptr))
                  {
                     StatusLine (IDS_LASTERROR+36, pd->szRcvFile);
                     // IDS_LASTERROR+36  "Error in GIF file"
                     if (!pd->hDlg1 && pd->bdiag)
                     {
                        P_ShowXferBox (0);  // show xfer box for recieve
                        SetWindowText (pd->hDlg1, "DLL [XModem]");
                        SetDlgItemText (pd->hDlg1, ID_FILENAME,
                                        AnsiLower (pd->szRcvFile));
                     }
                  }
                  *pd->bigptr++ = *lptr++;
               }
            }
            else
            {
               /************************************************************
               * If we're not doing gifs and we don't have a status box up *
               ************************************************************/
               _fmemcpy (pd->bigptr, lptr, pd->rec_len);
               pd->bigptr += pd->rec_len;
               if (!pd->hDlg1 && pd->bdiag)
               {
                  P_ShowXferBox (0);   // show xfer box for receive
                  SetWindowText (pd->hDlg1, "DLL [XModem]");
                  SetDlgItemText (pd->hDlg1, ID_FILENAME, AnsiLower (pd->szRcvFile));
               }
            }

            if (pd->bigptr == (pd->bigbuff + _10K))
            {
               if (_lwrite ((HFILE)pd->hCommFile, pd->bigbuff, _10K) != _10K)
               {
                  End_It (20);         // IDS_LASTERROR+19  "Transfer Aborted File Write Error"
                  return;
               }
               pd->bigptr = pd->bigbuff;
            }
            pd->rec_no++;              // bump next expected record number
            pd->rec_no &= 0xff;        // (it's only a byte long)
            pd->runcnt += pd->rec_len;
            if (pd->hDlg1)
            {
               wsprintf (szBuffer, "%lu", pd->runcnt);
               SetDlgItemText (pd->hDlg1, ID_BLKNO, szBuffer);
               P_TimeToGo ();          // causes CPS to be re-calc'ed
            }
            fpInstD->PCState = IDX_RXING;
         }  //-- End of processing a valid block

         else   //-- If we didn't get the block we were expecting, then....
         {
            //-- Did the other side re-send the previous block?
            //   ..If so, send ACK, don't add to file buffer
            if (((BYTE)(pd->rec_no-1) == (BYTE)*(pd->cbuff+1)) &&
                  ((BYTE)(*(pd->cbuff+2)+pd->rec_no-1) == 255))
            {
               P_WriteCommW (mn->hCid, _ACK, 1);       // send an ACK back
               if (++pd->nackcount > 20)  // bump #nacks, chk for too many
                  pd->bCancel = 2;        // If too many, set ABORT flag
               if (pd->hDlg1)          // If the file xfer dialog box dsplyd
               {
                  SetDlgItemInt (pd->hDlg1, ID_ERR, pd->nackcount, 0);
                  LastError (7);       // IDS_LASTERROR+6  "Bad Block No."
               }
               fpInstD->PCState = IDX_RXING;
            }
            else  //-- It's a bad block, and it isn't a re-send of last block
            {     //   (XMODEM only knows how to deal with resending last block)
               if (pd->rec_no == 0)
                  End_It (24);         // IDS_LASTERROR+23  "Sending Protocol is not TRUE YMODEM.  Try using XMODEM-crc"
               else
                  End_It (19);         // IDS_LASTERROR+18  "Transfer Aborted Bad Block Number Received"
            }
            pd->xwaittime = GetTickCount() + _20_Secs;  // set next timeout
            pd->xcount = 0;
            return;
         }

      case IDX_NOCHAR:
         if (pd->bCancel)
         {
            if (pd->hDlg1)
               LastError (4);          // IDS_LASTERROR+3   "Aborting"
            End_It (17);               // IDS_LASTERROR+16  "Transfer Aborted by User"
            return;                    // Xfer was canceled so terminate
         }
         pd->xcount = fpInstD->BBegin = 0;
         pd->xwaittime = GetTickCount() + NOCHARWAIT;
         return;

      case IDP_EOT:
         // 1st send a NAK to make sure it was a valid EOT
         if (fpInstD->BBegin != 99)
         {
            P_WriteCommW (mn->hCid, _NAK, 1);
            fpInstD->BBegin = 99;
            fpInstD->PCState = IDX_RXING;
            pd->xwaittime = GetTickCount()+2000;  // wait up to 2 secs for EOT
         }
         else
         {
            if (_lwrite ((HFILE)pd->hCommFile, pd->bigbuff,
               (WORD)(pd->bigptr-pd->bigbuff)) != (WORD) (pd->bigptr-pd->bigbuff))
               StatusLine (IDS_LASTERROR+19, NULL);
               // IDS_LASTERROR+19  "Transfer Aborted File Write Error"
            else
               StatusLine (IDS_LASTERROR+14, pd->szRcvFile);
               // IDS_LASTERROR+14  "Transfer Complete -"
            P_WriteCommW (mn->hCid, _ACK, 1);
            *pd->aspfxfer = ASP_PROT_SUCCESS;
            P_EndXfer ();
         }
         return;

      case IDX_RXING:
      default:
         return;
   }
}

/*-----------------------------------------------------
                                                      |
   WAITACK state: entered after xmitting a block,     |
                  waiting for other side to ACK/NAK   |
                                                      |
-----------------------------------------------------*/

static void PASCAL WAITACK (void)
{
   if (pd->bCancel && !fpInstD->Aborting) // set so user knows we are Aborting
   {
      fpInstD->Aborting = TRUE;
      *pd->aspfxfer = ASP_PROT_FAIL;   // pointer to aspect file xfer flag
      if (pd->hDlg1)
         LastError (4);                // IDS_LASTERROR+3   "Aborting"
      if (fpInstD->BBegin == 1)
         End_It (17);                  // IDS_LASTERROR+16  "Transfer Aborted by User"
      return;                          // xfer canceled, so return
   }

   if (GetTickCount() > pd->xwaittime) // have we timed out waiting for ACK?
   {
      if (pd->rec_no != fpInstD->EndRecord)  // have we sent the entire file?
         End_It (1);                         // IDS_LASTERROR  "Timed out"
      else
         P_EndXfer ();                 // terminate file xfer
      return;
   }

   switch( WaitingForAck() )
   {
      case IDP_TERMINATE:
         End_It (13);                  // IDS_LASTERROR+12  "Transfer Aborted by Remote"
         return;

      case IDP_ACK:
         if (pd->bCancel)
         {
            End_It (17);               // IDS_LASTERROR+16  "Transfer Aborted by User"
            return;
         }
         ymodem_tx ();                 // format and xmit the next block of the file
         pd->xwaittime = GetTickCount()+_20_Secs;
   }
}

/*-----------------------------------------------------
                                                      |
   State NOCHAR: waiting to receive a char            |
                                                      |
-----------------------------------------------------*/

static void PASCAL NOCHAR (void)
{
   if (pd->bCancel)
   {
      *pd->aspfxfer = ASP_PROT_FAIL;   // pointer to aspect file xfer flag
      if (pd->hDlg1)
         LastError (4);                // IDS_LASTERROR+3   "Aborting"
   }

   P_GetCommErrorW (mn->hCid);         // check for new Comm Errors

   if (mn->comstat.cbInQue != 0)       // if no unprocessed data in queue
   {
      P_FlushCommW (mn->hCid, 1);      // flush Rcv queue
      pd->xwaittime = GetTickCount() + NOCHARWAIT;  // set inter-char timeout
      return;
   }

   if (GetTickCount() > NOCHARWAIT)
   {
      P_WriteCommW (mn->hCid, _NAK, 1);         // send a NAK
      pd->xwaittime = GetTickCount()+_20_Secs;  // wait up to 20 seconds
      fpInstD->PCState = IDX_RXING;             // set next state = WAIT FOR RX CHAR
   }
}

/*-----------------------------------------------------
                                                      |
   Place a message on PW4.EXE statusline              |
                                                      |
-----------------------------------------------------*/

static void PASCAL StatusLine (UINT index, LPSTR lptr)
{
   char line[128];

   LoadString (hInst, index, line, 80);
   if (lptr)
      lstrcat (line, lptr);
   P_StatusLineDirect ((LPSTR)line);   // call statusline function in PW4.EXE
}

/*-----------------------------------------------------
                                                      |
   Display an Err Msg on the Dialog Box's LAST ERROR  |
   line                                               |
                                                      |
-----------------------------------------------------*/

static void PASCAL LastError (int i)
{
   char line[21];

   if (pd->hDlg1)
   {
      if (i)
         LoadString (hInst, IDS_LASTERROR+i-1, (LPSTR)line, 20);
      else
         line[0] = 0;
      SetDlgItemText (pd->hDlg1, ID_UARTERR, (LPSTR)line);
   }
}

/*-----------------------------------------------------
                                                      |
   Display an Err Msg on the PW4.EXE status line and  |
   terminate the file transfer                        |
                                                      |
-----------------------------------------------------*/

static void PASCAL End_It (int i)
{
   P_WriteCommW ( mn->hCid, (LPSTR)"\x18\x18\x18\x18\x18\x18\x18\x18\x08\x08\x08\x08\x08\x08\x08\x08", (int)16);
   StatusLine (IDS_LASTERROR+i-1, NULL);
   *pd->aspfxfer = ASP_PROT_FAIL;      // pointer to aspect file xfer flag
   P_EndXfer ();
}

/*-----------------------------------------------------
                                                      |
   Build and XMIT a single 128 byte XMODEM record     |
                                                      |
-----------------------------------------------------*/

static void PASCAL StartXRecord (int dlen, LPSTR data)
{
   LPSTR s;
   int i, crc;

   s = pd->cbuff;
   pd->rec_no++;                       // bump record number
   pd->rec_no &= 0xff;                 // (it's only a byte long, wraps @ 256)
   *s++ = 1;
   *s++ = (BYTE) (pd->rec_no);
   *s++ = (BYTE) (255-pd->rec_no);
   P_WriteCommW (mn->hCid, (LPSTR) (s-3), 3);

   crc = 0;
   for (i=0; i<pd->rec_len; i++)
   {
      *s=*data;
      crc += *data++;
      P_WriteCommW (mn->hCid, s++, 1);
   }
   *s = LOBYTE (crc);
   P_WriteCommW (mn->hCid, s, 1);
}

/*-----------------------------------------------------
                                                      |
   After we've xmitted a block, come here and wait    |
   to see what kind of response we receive            |
                                                      |
-----------------------------------------------------*/

static int PASCAL WaitingForAck (void)
{
   char recvchar;

   //--  If we had Comm Errors, display a message & return (keep same STATE)
   if (P_GetCommErrorW (mn->hCid))
   {
      while (P_GetCommErrorW (mn->hCid))
         ;                             // flush multiple errors
      P_FlushCommW (mn->hCid, 1);      // flush receive queue

      if (++pd->nackcount > 20)        // bump error count, chk for too many
         pd->bCancel = 2;              // set cancel flag if excessive errors

      if (pd->hDlg1)
      {
         SetDlgItemInt (pd->hDlg1, ID_ERR, pd->nackcount, 0);
         LastError (8);                // IDS_LASTERROR+7     "Uart Error"
      }
      return (fpInstD->PCState);
   }

   //--  If there's no data in the Comm Buffer, just return
   if (mn->comstat.cbInQue == 0)
      return (fpInstD->PCState);

   //--  Read & process the next byte in Comm Buffer, which should be the
   //    other side's response to the last block we sent him
   P_ReadCommW (mn->hCid, &recvchar, 1);
   switch (recvchar)
   {
      case ACK:
                // set next processing state so we can resume xmitting
         return (fpInstD->PCState = IDP_ACK);

      case CANCEL:
         P_FlushCommW (mn->hCid, 0);
         return (fpInstD->PCState = IDP_TERMINATE);

      case NAK:
         if (pd->bCancel)              // If we've been canceled..
            return (fpInstD->PCState = IDP_ACK);

         if (fpInstD->BBegin == 1)     // If we're in the Initial Send State..
         {
            fpInstD->BBegin = 0;       // ...take us out of Initial Send State
            pd->xcount = 132;
            // pretend we've been ACK'd so we'll send the next block (block 1)
            return (fpInstD->PCState = IDP_ACK);
         }

         if (fpInstD->BBegin == 0)     // If we got a NAK and we aren't in
         {                             // ..the Initial Send State
            P_FlushCommW (mn->hCid, 0);
                                       // ReXMIT the previous block
            P_WriteCommW (mn->hCid, pd->cbuff, pd->xcount);
            if (++pd->nackcount > 20)  // bump error count, chk for too many
               pd->bCancel = 2;        // if excessive errors, set cancel flag

            if (pd->hDlg1)
            {
               SetDlgItemInt (pd->hDlg1, ID_ERR, pd->nackcount, 0);
               LastError (6);          // IDS_LASTERROR+5   "Nack Received"
            }
         }
         break;

      default:
         P_FlushCommW (mn->hCid, 1);
   }
   return (fpInstD->PCState);
}

/*-----------------------------------------------------
                                                      |
   low-level worker to read in next 128-byte XMODEM   |
   record.                                            |
                                                      |
-----------------------------------------------------*/

static int PASCAL GetXRecord (void)
{
   short ErrCode;
   int i, j, k;

   if (fpInstD->RCnt > (3*pd->xcount)/4 && pd->xcount)
      k=4;
   else
      k=1;
   for (j=0; j<k; j++)
   {
      if (P_GetCommErrorW (mn->hCid))
      {
         while (P_GetCommErrorW (mn->hCid))
            ;
         ErrCode = 8;                  // IDS_LASTERROR+7  "Uart Error"
error_state:
         if (++pd->nackcount > 20)     // bump error count, chk for too many
            pd->bCancel = 2;           // if too many errs, set cancel flag
         if (pd->hDlg1)
         {
            SetDlgItemInt (pd->hDlg1, ID_ERR, pd->nackcount, 0);
            LastError (ErrCode);
         }
         return (fpInstD->PCState = IDX_NOCHAR);
      }

      if ((mn->comstat.cbInQue >= 1) && (pd->xcount == 0))
      {
         if (fpInstD->BBegin && fpInstD->BBegin != 99)
         {
            fpInstD->BBegin = 0;
            pd->xwaittime = GetTickCount()+2000;
         }
         P_ReadCommW (mn->hCid, pd->cbuff, 1);
         if (pd->cbuff[0] == 1)
         {
            pd->rec_len = 128;
            pd->xcount = 128+3;
         }
         else
         {
            if (pd->cbuff[0] == EOT)
               fpInstD->PCState = IDP_EOT;
            else
            {
               if (pd->cbuff[0] == CANCEL)
               {
                  StatusLine (IDS_LASTERROR+12, NULL);
                  // IDS_LASTERROR+12  "Transfer Aborted by Remote "
                  P_EndXfer ();
                  return (0);
               }
               ErrCode = 10;           // IDS_LASTERROR+9 "Packet Type Error"
               goto error_state;
            }
         }

         fpInstD->pdCRC = 0;
         mn->comstat.cbInQue -= 1;
         fpInstD->RCnt = 0;
      }

      if (mn->comstat.cbInQue && pd->xcount)
      {
         for (i=0;  i < (int)mn->comstat.cbInQue;  i++)
         {
            P_ReadCommW (mn->hCid, pd->cbuff+fpInstD->RCnt+1, 1);
            if (++fpInstD->RCnt < 3)
               continue;
            else
               if (fpInstD->RCnt < pd->xcount)
                  fpInstD->pdCRC += pd->cbuff[fpInstD->RCnt];
            if (fpInstD->RCnt != pd->xcount)
               continue;
            if ((BYTE)*(pd->cbuff+pd->xcount) == LOBYTE (fpInstD->pdCRC))
               fpInstD->PCState = IDP_ACK;
            else
            {
               ErrCode = 2;            // IDS_LASTERROR+1  "Bad CRC"
               goto error_state;
            }
         }
      }
   }
   return (fpInstD->PCState);
}

/*-----------------------------------------------------
                                                      |
   Low-level worker to deblock & xmit a file          |
                                                      |
   If necessary, read in a 10K chunk of the file      |
   being sent.  Take the next 128 bytes of that chunk |
   and transmit it.                                   |
                                                      |
-----------------------------------------------------*/

static void PASCAL ymodem_tx (void)
{
   int nbytes;
   LPSTR lptr;

go_again:
   /*  If we're at end of file, xmit an EOT, wait for it to be ACK'd  */
   if (pd->rec_no == fpInstD->EndRecord)
   {
      if (fpInstD->BPos)
      {
         /*  Send End-of-file (EOT) and be ready to re-xmit if NAK recv'd */
         P_WriteCommW (mn->hCid, _EOT, 1);
         pd->xcount = 1;
         *pd->cbuff = '\x4';
         fpInstD->PCState = IDX_WAITACK;
         fpInstD->BPos = 0;
         return;
      }

      StatusLine (IDS_LASTERROR+14, pd->szRcvFile);
      // IDS_LASTERROR+14  "Transfer Complete -"
      P_EndXfer ();
      return;
   }  //--  End of END-OF-FILE processing

   /*********************************************************************
   * If our 10K file buffer is empty, try to read in the next 10K chunk *
   * of the file into our buffer                                        *
   *********************************************************************/
   if (pd->bigptr == pd->bigbuff + _10K)
   {
      nbytes = _lread ((HFILE)pd->hCommFile, pd->bigbuff, _10K);
      if (!nbytes)
      {
         // this is to let the IF statements pass through
         fpInstD->EndRecord = pd->rec_no = fpInstD->BPos = 1;
         goto go_again;
      }

      pd->bigptr = pd->bigbuff;
      if (nbytes != _10K)
      {
         fpInstD->EndRecord = pd->rec_no+ (int) (nbytes/pd->rec_len);
         if (nbytes % pd->rec_len)
            fpInstD->EndRecord += 1;
         lptr = pd->bigbuff+nbytes;
         _fmemset (lptr, 0, (size_t)10240L-nbytes);
         fpInstD->BPos = 1;
      }
      else
         fpInstD->EndRecord = pd->rec_no - 5;
   }  //--  End of reading in big 10K chunk


   StartXRecord (pd->rec_len, pd->bigptr);  // build & xmit nxt 128-byte block
   pd->bigptr += pd->rec_len;          // bump ptr up 128 bytes

   /**  Update Dialog Box with new status info  **/
   if (pd->hDlg1)
   {
      pd->runcnt += pd->rec_len;
      wsprintf (szBuffer, "%lu", pd->runcnt);
      SetDlgItemText (pd->hDlg1, ID_BLKNO, szBuffer);
      InvalidateRect (GetDlgItem (pd->hDlg1, ID_PERCENT), NULL, FALSE);
      P_TimeToGo ();
   }
   fpInstD->PCState = IDX_WAITACK;     // set next state = WAITING FOR ACK
}

/*-----------------------------------------------------
                                                      |
   File Open routine for both XMIT and RCV            |
                                                      |
-----------------------------------------------------*/

static BOOL PASCAL FileRxTX (int send, LPSTR ptr)
{
   static unsigned nDate, nTime;
   OFSTRUCT ofs;

   lstrcpy (szBuffer, pd->szTempDir);
   lstrcat (szBuffer, "\\");
   lstrcat (szBuffer, ptr);
   lstrcpy (pd->szRcvFile, ptr);
   if (send == 0)
   {
      if ((pd->hCommFile = (FILE_HANDLE)OpenFile(szBuffer, &ofs, OF_CREATE | OF_WRITE)) == INVALID_HANDLE_VALUE)
      {
         StatusLine (IDS_LASTERROR+26, szBuffer);
         // IDS_LASTERROR+26  "Transfer Aborted File Creation Error"
         *pd->aspfxfer = ASP_PROT_FAIL;
         return (FALSE);
      }
      return (TRUE);
   }

   if ((pd->hCommFile = (FILE_HANDLE)OpenFile (szBuffer, &ofs, OF_READ)) == INVALID_HANDLE_VALUE)
   {
      StatusLine (IDS_LASTERROR+24, szBuffer);
      // IDS_LASTERROR+24  "Transfer Aborted File Read Error"
      *pd->aspfxfer = ASP_PROT_FAIL;
      return (FALSE);
   }

   pd->flen = _filelength ((int)pd->hCommFile);
//FIX  _dos_getftime (pd->hCommFile, &nDate, &nTime);
//     pd->fdate = nDate;
//     pd->fftime = nTime;
   return (TRUE);
}

