home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / PASCAL / XFER10.ZIP / XFER.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1990-01-20  |  25.7 KB  |  612 lines

  1. { Xmodem Checksum/CRC/1K Transfer 1.0
  2.  
  3.  By Andrew Bartels
  4.  A Product of Digital Innovations
  5.  
  6.  Placed in the Public Domain, January, 1990, By Andrew Bartels  }
  7.  
  8.  
  9. Program Transfer_Protocol_Driver;
  10.  
  11. {$V-}     {Variables of any length can match in procedure headers}
  12.  
  13. {$S-,R-}  {Turn stack and range check off now that I'm done deugging}
  14.  
  15. Uses DOS,            { File I/O Routines }
  16.      CRT,            { Video Display Routines / Keyboard }
  17.      IBMCom;         { Use Wayne Conrad's Public Domain COM routines }
  18.  
  19. Const RX = 1;        { OpCode for Receiving Xmodem }
  20.       SX = 2;        { OpCode for Sending Xmodem   }
  21.  
  22.       Wt = True;     { Used to determine if OpenFile opens for }
  23.       Rd = False;    { Write (True) or Read (False)            }
  24.  
  25. Const ACK = #6;      { Acknowledgement     = ASCII Code 6     }
  26.                      { This is sent by receiver to indicate   }
  27.                      { transmitter that the block received    }
  28.                      { had no errors, and that the next block }
  29.                      { can be received.                       }
  30.  
  31.  
  32.       CAN = #24;     { Cancel              = ASCII Code 24    }
  33.                      { This signals that the protocol is to   }
  34.                      { be immediately terminated.  Receipt of }
  35.                      { two of these in a row when expecting   }
  36.                      { an SOH, STX, ACK, NAK, or EOT means    }
  37.                      { to abort.                              }
  38.  
  39.       EOT = #4;      { End Of Transmission = ASCII Code 4    }
  40.                      { Indicates that the tranmitter has     }
  41.                      { successfully transmitted all blocks,  }
  42.                      { and that the protocol is to terminate }
  43.  
  44.       SOH = #1;      { Start Of Header     = ASCII Code 1     }
  45.                      { This indicates start of a new block    }
  46.                      { of 128 bytes of data.                  }
  47.  
  48.       STX = #2;      { Start Of Text       = ASCII Code 2     }
  49.                      { This code is used exactly as the SOH   }
  50.                      { by the transmitter to signal block     }
  51.                      { beginning.  However, its use indicates }
  52.                      { the block will have 1K of data         }
  53.  
  54.       NAK = #21;     { Negative Acknowledgement = ASCII Code  21  }
  55.                      { This is sent to receiver to indicate that  }
  56.                      { the last block failed it's error check, or }
  57.                      { is sent to transmitter to indicate √sum    }
  58.                      { error checking is to be used.              }
  59.  
  60.  
  61. { This code has its own built-in error trapping code, designed to report  }
  62. { errors to the user as accurately as possible.  Each error condition has }
  63. { a number associated with it, as described below.  Upon receipt of an    }
  64. { error, the proper error description is printed via the InterpretCode    }
  65. { procedure, and the code itself becomes the exit code as the program     }
  66. { terminates.  Error code of zero means no error.                         }
  67.  
  68. { Exit Codes To Define Error Conditions Are As Follows:                   }
  69.  
  70.  
  71.       NoError                 = 0;  { No error happened - all is well  }
  72.       FileError               = 1;  { Can't open a file                }
  73.       OperatorAbortError      = 2;  { Operator aborted transfer        }
  74.       RemoteCancelError       = 3;  { Remote side aborted transfer     }
  75.       MaxTimeOutError         = 4;  { TimeOut Limit exceeded           }
  76.       BlockOutOfSequenceError = 5;  { Block received out of seq.       }
  77.       EOTTimeOut              = 6;  { Timeout on receiving second EOT. }
  78.       CarrierLostError        = 7;  { Can't xfer if we don't have one  }
  79. {      .
  80.        .
  81.        .
  82.        .                                       }
  83.       InvalidOperation        = 252; { An invalid operation was used      }
  84.       InvalidSwitch           = 253; { An invalid command switch was used }
  85.       InvalidBaudRate         = 254; { An invalid baud rate was specified }
  86.       InvalidCOMPort          = 255; { An Invalid COM Port was specified  }
  87.  
  88.  
  89. Type BlockType = Array[1..1024] of Byte;  { Use this type for all blocks }
  90.  
  91.  
  92. { Variables used in opening command line interpretation routines:         }
  93.  
  94.  
  95. Var Count         : Integer;      { pointer to next ParamStr item         }
  96.  
  97.     OneK,                         { True if 1K blocks are to be used      }
  98.  
  99.     CRCMode       : Boolean;      { True if CRC correction is to be used, }
  100.                                   { False if Checksum is to be used       }
  101.  
  102.     FileName      : String;       { Holds path/filename specified on      }
  103.                                   { command line.                         }
  104.  
  105.     OpCode        : Byte;         { Determines which transfer operation   }
  106.                                   { was specified.                        }
  107.  
  108.     ComPort,                      { Holds COM Port specified, or 1 as a   }
  109.                                   { default if none was specified.        }
  110.  
  111.     Place         : Word;         { Points to correct COM Port I/O Address}
  112.  
  113.     Hold          : Byte;         { Holds a temp. value when determining  }
  114.                                   { the port's initial baud rate          }
  115.  
  116.     Baud          : Word;         { Defaults to current COM Port speed, or}
  117.                                   { becomes desired baud when specified   }
  118.  
  119.     Code,                         { Holds returned error when COM Port is }
  120.                                   { initialized.                          }
  121.  
  122.     Status        : Word;         { Holds error status when converting the}
  123.                                   { COM Port or Baud rate from string to  }
  124.                                   { a number.                             }
  125.  
  126.     ExitStatus    : Byte;         { Is assigned an error code indicating  }
  127.                                   { results of operation.  This value is  }
  128.                                   { eventually returned as a process exit }
  129.                                   { code via Halt. See Const defs above.  }
  130.  
  131.  
  132.  
  133. Function Uppercase (Line : String) : String;  { Make a string all uppercase }
  134.                                               { ...used on filename         }
  135. Var Count : Integer;
  136. Begin
  137.   If Length(Line) > 0
  138.     then
  139.       For Count := 1 to Length(Line) do       { Loop through each character }
  140.         Line[Count] := UpCase(Line[Count]);   { Uppercase it if needed      }
  141.   Uppercase := Line;                          { Return results via function }
  142. End;
  143.  
  144.  
  145. { This procedure updates the CRC value of the block.  The CRC is initially }
  146. { set to zero, then as data is received or sent, a call for each byte in   }
  147. { the dat ais made here.  The old CRC value is passed too, and it is       }
  148. { updated to include the data byte being processed.  The result is that    }
  149. { once all the data has been sent/received, you have the CRC all ready for }
  150. { sending or checking against the one from the transmitter.  The only      }
  151. { thing you need to watch is this:  After the data has been run through    }
  152. { this routine byte by byte, the CRC you have is bitwise reversed from the }
  153. { value actually to be sent.  Transmission standards say that the CRC is to}
  154. { be transmitted high bit first.  But standards also say a byte is sent low}
  155. { bit first.  Thus, in order to get the desired value to send, use the     }
  156. { ReverseWord procedure below.  The value you have after ReverseWord       }
  157. { *** IS NOT *** the CRC, but rather the bit-reversed CRC, all ready to be }
  158. { transmitted.  The machine on the other end then has to reverse the bits  }
  159. { back again to get the CRC value.  It's kinda crazy to have to reverse the}
  160. { bits, but that's the way the IEEE says you do it.              -Andrew   }
  161.  
  162.  
  163. Function UpdCRC (Data    : Byte;
  164.                  CRCTemp : Word) : Word;
  165.  
  166. { We figure the CRC with an uncommonly small data table here: }
  167.  
  168. Const Table : Array[0..15] of Word = ($0000,$1081,$2102,$3183,
  169.                                       $4204,$5285,$6306,$7387,
  170.                                       $8408,$9489,$A50A,$B58B,
  171.                                       $C60C,$D68D,$E70E,$F78F);
  172.  
  173. Var Index : Byte;        { Index pointer to the data table above }
  174.     Power,               { bit pointer used in reversing byte    }
  175.     Power2 : Byte;       { ditto                                 }
  176.     Data2  : Byte;       { second version of byte, bit reversed  }
  177.     Count  : Integer;    { iterates from 1 to 8 for it reversal  }
  178.  
  179. Begin
  180.  
  181.  { Before we can figure out the CRC with this byte, we have to reverse it's }
  182.  { bits - due to the fact that data is normally transmitted low-bit first   }
  183.  
  184.   Data2 := 0;                          { Hold new reversed data here }
  185.   Power := 1;                          { Power of 2 counter going up }
  186.   Power2 := 128;                       { Power of 2 counter going down }
  187.   For Count := 1 to 8 do               { Do 8 bits of the byte         }
  188.     Begin
  189.       If (Data and Power) <> 0         { If lower bit is a 1, then ... }
  190.         then
  191.           Data2 := Data2 or Power2;    { ....set the higher bit        }
  192.       Power2 := Power2 shr 1;          { Shift higher power of 2 down  }
  193.       Power  := Power  shl 1;          { Shift lower power of 2 up     }
  194.     End;
  195.   Data := Data2;                       { Data is assigned it's reverse }
  196.  
  197.   { Now we can start doing the CRC divide with the following polynomial: }
  198.   {        16     12    5                                                }
  199.   {       X   +  X   + X  + 1   , where X = 2                            }
  200.  
  201.   { Thus, we're talking about an actual value of $11021.  The divide is  }
  202.   { done with Modulo Two arithmetic (XOR).  We don't actually divide here}
  203.   { because the table already takes care of it for us.  For a very       }
  204.   { clear and easy to understand description of the CRC-CCITT, and other }
  205.   { types of CRC, etc, please see "C Programmer's Guide To NetBIOS," by  }
  206.   { W. David Schwaderer, pages 167-217, published by Howard W. Sams &    }
  207.   { Company.  This code uses the same method of CRC calculation as is    }
  208.   { shown on pages 204 - 205 of the above book.                -AB       }
  209.  
  210.   Index := ((CRCTemp xor Data) and $000F);  { Figure an index to table }
  211.  
  212.   { Xor table data into CRC }
  213.   CRCTemp := ((CRCTemp shr 4) and $0FFF) xor (Table[Index]);
  214.  
  215.   Data := Data shr 4;   {Now, do it all again for lower nibble of Data}
  216.  
  217.   Index := ((CRCTemp xor Data) and $000F);  { Figure an index to table }
  218.  
  219.   { Xor table data into CRC }
  220.   CRCTemp := ((CRCTemp shr 4) and $0FFF) xor (Table[Index]);
  221.  
  222.   UpdCRC := CRCTemp;   { Set function result to new CRC value }
  223. End;
  224.  
  225.  
  226. { This procedure simply reverses the bits of the word sent by parameter. }
  227. { Used to reverse bits of the CRC before sending.  I'd hope there would  }
  228. { be a much more efficient way to do such a simple little operation, but }
  229. { this was all that came to mind at the time I wrote it.  We only use it }
  230. { once for each 128 or 1024 (in 1K transfers) bytes, so I guess it does  }
  231. { not need to be extremely efficient.                                    }
  232.  
  233. Procedure ReverseWord (Var CRC : Word);
  234. Var CRC2   : Word;     { Holds reversed Word     }
  235.     Power,             { bit pointer in reversal }
  236.     Power2 : Word;     { ditto                   }
  237.     Count  : Integer;  { interates for 16 bits   }
  238. Begin
  239.   Power  := 1;                     { Start one pointer at low bit }
  240.   Power2 := 32768;                 { Other pointer at high bit    }
  241.   CRC2   := 0;                     { new word starts out zero.    }
  242.  
  243.   For Count := 1 to 16 do          { Iterate for all 16 bits      }
  244.     Begin
  245.       If (CRC and Power) <> 0      { If lower bit is 1, then....  }
  246.         then
  247.           CRC2 := CRC2 or Power2;  { ...Set higher bit            }
  248.  
  249.       Power  := Power  shl 1;      { Shift bit pointers by one    }
  250.       Power2 := Power2 shr 1;
  251.     End;
  252.   CRC := CRC2;                     { Return reversed word to caller }
  253. End;
  254.  
  255.  
  256. Procedure Beep;   { Big Deal - Makes A Beep }
  257. Begin
  258.   Sound(500);
  259.   Delay(20);
  260.   NoSound;
  261. End;
  262.  
  263.  
  264. { This procedure is vital to timing of transfers.  We can use it to     }
  265. { determine if a timeout has occured (i.e., too long of time passes     }
  266. { between chars received from remote machine).  The SecsToWait param.   }
  267. { specifies the number of seconds to wait before declaring that a       }
  268. { timeout has occurred.  This can be from 1 to 60 secs wait.  A value   }
  269. { over 60 will cause errors in timing.  The TimeDone param is assigned  }
  270. { True when the time in SecsToWait has passed, while receiving no chars }
  271. { from the remote end.  If, however, a char comes in before the alloted }
  272. { SecsToWait passes, then TimeDone becomes False, and Ch returns the    }
  273. { character received.   This is VERY useful in Xmodem implementation.   }
  274. { This routine is accurate to within ± 1 second.                        }
  275.  
  276.  
  277. Procedure MonitorReceive (Var Ch         : Char;
  278.                                SecsToWait : Byte;
  279.                            Var TimeDone   : Boolean);
  280.  
  281. Var Hour,                 { Hold Hours from GetTime             }
  282.     Min,                  { Hold Mins  from GetTime             }
  283.     Sec,                  { Hold Secs  from GetTime             }
  284.     LookFor,              { Hold second value we're waiting for }
  285.     Hund       : Word;    { Hold Hundredths from GetTime        }
  286.  
  287.     Done       : Boolean; { Used in telling whether we've got a char }
  288.                           { in Repeat/Unit loop.                     }
  289.  
  290. Begin
  291.  
  292.   TimeDone := False;      { Automatically assume we've got a character }
  293.  
  294.   If not Com_RX_Empty     { If one is waiting, receive it & return     }
  295.     then
  296.       Begin
  297.         Ch := Com_RX;
  298.         Exit;
  299.       End;
  300.  
  301.   GetTime(Hour,Min,Sec,Hund);    { Otherwise, get the start time         }
  302.   LookFor := Sec + SecsToWait;   { Figure destination time to the second }
  303.  
  304.   While LookFor > 59 do          { Map it to a value from 0 to 59        }
  305.     LookFor := LookFor - 60;
  306.  
  307.   Done := False;                 { Assume no char received now.          }
  308.   Repeat
  309.     GetTime(Hour,Min,Sec,Hund);  { Get current time now                  }
  310.     TimeDone := Sec = LookFor;   { TimeDone is true if the time is up    }
  311.     If Not Com_RX_Empty          { If we did get a character in, then....}
  312.       then
  313.         Begin
  314.           Ch := Com_RX;          { ....Get it....                        }
  315.           TimeDone := False;     { ....Specify no TimeOut occurred....   }
  316.           Done     := True;      { ....And get us out of the loop.       }
  317.         End;
  318.   Until Done or TimeDone;        { Keep looping until we either run out  }
  319.                                  { of time, or get a character.          }
  320. End;
  321.  
  322.  
  323. { This routine is used to handle opening a file for read/write with error }
  324. { trapping capabilities.  OpenFile attempts to open the file in FileName  }
  325. { for Read or Write (depending on Direction), to a record length of       }
  326. { RecordSize bytes, using File1 for a file handle.  If this operation is  }
  327. { successful, the function returns True, else False.  A null Filename is  }
  328. { NOT counted as valid, although Pascal will allow such an operation.     }
  329.  
  330.  
  331. Function OpenFile (Var File1        : File;
  332.                        Direction    : Boolean;  {True = Write, False = read}
  333.                        RecordSize   : Integer;
  334.                    Var FileName     : String) : Boolean;
  335.  
  336. Begin
  337.   If FileName = ''                { If no filename, then we have an error }
  338.     then
  339.       Begin
  340.         OpenFile := False;
  341.       End
  342.     else
  343.       Begin
  344.         Assign(File1,Filename);    { Else, associate Filename with File1 }
  345.         {$I-}
  346.         If Direction                     { Attempt open for Read/Write   }
  347.           then
  348.             ReWrite(File1,RecordSize)
  349.           else
  350.             ReSet(File1,RecordSize);
  351.         {$I+}
  352.         OpenFile := not (IOResult > 0);  { If there was an error, return }
  353.                                          { False, else return True.      }
  354.       End;
  355. End;
  356.  
  357.  
  358.  
  359. { This procedure will transmit a data block.  The data is passed as a VAR }
  360. { parameter (to save on stack space), the length of the data (128 or 1024)}
  361. { comes next.  The procedure also is sent the block number to send the    }
  362. { data as, as well as whether CRC or Checksum error correction is used.   }
  363.  
  364.  
  365. Procedure SendBlock (Var Data : BlockType;
  366.                      DataLen  : Integer;
  367.                      BlockNum : Byte;
  368.                      CRCMode  : Boolean     );
  369.  
  370. Var Count    : Integer;  { Iteration counter                        }
  371.     CRC      : Word;     { Have WORD ready to handle CRC correction }
  372.     Checksum : Byte;     { Have a BYTE ready incase of checksum     }
  373. Begin
  374.   Case DataLen of              { If data is 128 bytes, then....     }
  375.     128  : Com_TX(SOH);        { ....tell receiver this, else....   }
  376.     1024 : Com_TX(STX);        { ....tell receiver it's 1K long     }
  377.   End;{Case}
  378.  
  379.   Com_TX(Chr(BlockNum));        { Transmit block number              }
  380.   Com_Tx(Chr(not BlockNum));    { Transmit one's complement of the   }
  381.                                 { block number                       }
  382.  
  383.   If CRCMode                    { If we're doing CRC, then.....      }
  384.     then
  385.       CRC := 0                  { ....initialize CRC variable, else  }
  386.     else
  387.       Checksum := 0;            { initialize the Checksum variable   }
  388.  
  389.   For Count := 1 to DataLen do  { Loop for all data bytes in block   }
  390.     Begin
  391.       Com_Tx(Chr(Data[Count])); { Transmit the data byte             }
  392.       If CRCMode
  393.         then
  394.           CRC := UpdCRC(Data[Count],CRC)  { If CRC, then update CRC  }
  395.         else
  396.           Checksum := Checksum + Data[Count];  { Else update the Checksum }
  397.     End;
  398.   If CRCMode                     { If CRC, then transmit reversed CRC }
  399.     then
  400.       Begin
  401.         ReverseWord(CRC);
  402.         Com_TX( Chr(Hi(CRC)) );
  403.         Com_TX( Chr(Lo(CRC)) );
  404.       End
  405.     else
  406.       Com_TX(Chr(Checksum));    { else transmit the chechsum }
  407.  
  408.   Repeat Until Com_TX_Empty;    { Wait until the buffer has been cleared. }
  409.                                 { THIS IS VERY IMPORTANT!  If we return   }
  410.                                 { before the data has actually been sent, }
  411.                                 { then the calling code might start trying}
  412.                                 { to wait for a response from the other   }
  413.                                 { end before the data actually arrives.   }
  414.                                 { If the baud rate is 300 baud or so, the }
  415.                                 { calling routine could conceivably get a }
  416.                                 { timeout error before the data was even  }
  417.                                 { completely sent from the buffer!  By    }
  418.                                 { waiting for the data to get sent, we    }
  419.                                 { don't have such worries.                }
  420. End;
  421.  
  422.  
  423.  
  424. {$I SXRX.INC}   { Include XmodemSend and XmodemReceive procedures here}
  425.                 { Other protocols soon to follow!                     }
  426.  
  427.  
  428.  
  429. { Here is where the exit status messages are reported back to the user }
  430. { at program termination.                                              }
  431.  
  432. Procedure InterpretCode (Code : Byte);
  433. Begin
  434.   Writeln;
  435.   Case Code of
  436.     NoError                 : Writeln('Receive Complete.');
  437.     FileError               : Writeln('Error Opening File!');
  438.     OperatorAbortError      : Writeln('Operator Abort!');
  439.     RemoteCancelError       : Writeln('Transfer Canceled By Remote!');
  440.     MaxTimeoutError         : Writeln('Maximum Timeout Count Exceeded!');
  441.     BlockOutOfSequenceError : Writeln('Block Number Out Of Sequence Error!');
  442.     EOTTimeOut              : Writeln('Timeout On Second EOT - Protocol Assumed Complete.');
  443.     CarrierLostError        : Writeln('No Carrier!');
  444. {      .
  445.        .
  446.        .
  447.        .                                       }
  448.     InvalidOperation        : Writeln('Missing or Invalid Operation Specified!');
  449.     InvalidSwitch           : Writeln('Invalid Command Line Switch!');
  450.     InvalidBaudRate         : Writeln('Invalid Baud Rate!');
  451.     InvalidCOMPort          : Writeln('Invalid COM Port Number!');
  452.   End;{Case}
  453.   Beep;
  454.   Beep;
  455.   Delay(1000);
  456.   Com_DeInstall;      { No need to keep COM Port running  }
  457.   Halt(Code);         { Exit program & return exit status }
  458. End;
  459.  
  460.  
  461. { Ahhhh....the main routine......... }
  462.  
  463.  
  464. Begin
  465.  
  466.   DirectVideo := True;    { Fast video speeds up the transfer time! }
  467.   ClrScr;                 { print opening credits }
  468.  
  469.   Writeln('Xmodem Checksum/CRC/1K Transfer 1.0');
  470.   Writeln('Copyright (C) 1990 By Andrew Bartels');
  471.   Writeln('A Product of Digital Innovations');
  472.   Writeln;
  473.   Writeln;
  474.  
  475.   Write('Command line = XFER ');   { Print out the command line entered }
  476.   For Count := 1 to ParamCount do
  477.     Write(Uppercase(ParamStr(Count)),' ');
  478.   Writeln;
  479.  
  480.   ComPort  := 1;        { Default COM Port = 1           }
  481.   Count    := 1;        { Count points to first ParamStr }
  482.   CRCMode  := False;    { Default to no CRC's yet        }
  483.   OneK     := False;    { Default to 128 byte blocks     }
  484.   OpCode   := 0;        { Set operation to nothing yet   }
  485.  
  486.  
  487.   If Uppercase(ParamStr(Count)) = 'PORT'  { if parameter is PORT, then }
  488.     then
  489.       Begin
  490.  
  491.         Val(ParamStr(Count+1),ComPort,Status);  {See if it's a valid # }
  492.  
  493.         If (Status <> 0) or (ComPort < 1) or (ComPort > 4)
  494.           then                               {If not a valid number, then }
  495.             InterpretCode(InvalidCOMPort);   {give error to user          }
  496.         Inc(Count,2);        {If it was valid, set pointer to next param}
  497.       End;
  498.  
  499.  
  500.   { The default baud rate is whatever the COM port is set at.  This little }
  501.   { area of the code figures out the default rate by reading I/O memory.   }
  502.  
  503.   Case ComPort of          {Figure out memory address of COM port}
  504.     1 : Place := $3F8;
  505.     2 : Place := $2F8;
  506.     3 : Place := $3E8;
  507.     4 : Place := $2E8;
  508.   End;{Case}
  509.  
  510.   Hold := Port[Place+3];
  511.   Port[Place+3] := Hold or $80;    {Open latch to current baud rate of port}
  512.   Baud := Trunc(115200.0 / (256 * Port[Place+1] + Port[Place])); {Get Baud}
  513.   Port[Place+3] := Hold and $7F;   {Close latch back up}
  514.  
  515.  
  516.   If Uppercase(ParamStr(Count)) = 'SPEED'    { If SPEED is specified, then }
  517.                                              { prepare to override default }
  518.     then
  519.       Begin
  520.         Val(ParamStr(Count+1),Baud,Status);  { Check if it's a valid speed}
  521.         If (Status <> 0) or
  522.            ((Baud <>  300) and (Baud <>  600) and (Baud <> 1200) and
  523.             (Baud <> 2400) and (Baud <> 4800) and (Baud <> 9600) and
  524.             (Baud <> 19200))
  525.           then        
  526.             InterpretCode(InvalidBaudRate);  {If not, give user an error}
  527.  
  528.         Inc(Count,2);          { else new baud rate is set, and pointer now}
  529.                                { points to the next successive ParamStr    }
  530.       End;
  531.  
  532.  
  533.   If Uppercase(ParamStr(Count)) = 'RX'
  534.     then
  535.       Begin
  536.         OpCode := RX;  { If user chose Rec. Xmodem, then set code}
  537.         Inc(Count);    { Move pointer to next ParamStr           }
  538.       End
  539.     else
  540.       Begin
  541.         If Uppercase(ParamStr(Count)) = 'SX'
  542.           then
  543.             Begin
  544.               OpCode := SX;  { If user chose Send Xmodem, then set code}
  545.               Inc(Count);    { Move pointer to next ParamStr           }
  546.             End
  547.           else
  548.             Begin
  549.             End;
  550.       End;
  551.  
  552.   { Since the FileName variable has not yet been used, we're going to   }
  553.   { temporarily use it here to hold parameter switches from the command }
  554.   { line.  This saves us memory space from allocating another variable  }
  555.  
  556.   FileName := Uppercase(ParamStr(Count));    {Get next cmd. line item}
  557.  
  558.   While (FileName[1] = '-') and (Length(FileName) = 2) do
  559.     Begin    { As long as it's a switch of some kind, loop }
  560.       Case FileName[2] of
  561.         'C' : Begin     { If CRC switch, then set CRC on }
  562.                 CRCMode := True;
  563.                 Inc(Count);
  564.               End;
  565.         'K' : Begin     { If 1K switch, then set 1K blocks on }
  566.                 OneK := True;
  567.                 Inc(Count);
  568.               End;
  569.         else  Begin     { If invalid, then report error & exit }
  570.                 InterpretCode(InvalidSwitch);
  571.               End;
  572.       End;{Case}
  573.       FileName := Uppercase(ParamStr(Count));  { get next parameter }
  574.     End;
  575.  
  576.     If OneK                { If 1K Xmodem is selected, attempt to force }
  577.       then                 { Use of CRC for best error correction.      }
  578.         CRCMode := True;
  579.  
  580.   { OK, from now on, FileName is used exclusively for the purpose of    }
  581.   { holding the path/filename the user specifies on the command line.   }
  582.  
  583.   FileName := '';     { Get rid of any traces of switches }
  584.  
  585.   If ParamCount >= Count  { If there is another parameter out there, then }
  586.     then
  587.       FileName := Uppercase(ParamStr(Count));    { get it as the filename }
  588.  
  589.  
  590.   Com_Install(ComPort,Code);    { OK, now try to fire up the COM routines }
  591.   If Code <> 0                  { If an error, then report it & exit     }
  592.     then
  593.       InterpretCode(InvalidCOMPort);
  594.   Com_Set_Speed(Baud);          { OK, now set the baud rate }
  595.   Com_Set_Parity(Com_None,1);   { Xmodem runs at 8-N-1      }
  596.  
  597.   Writeln('Using COM',ComPort,' at ',Baud,' baud, 8-N-1');
  598.  
  599.  
  600.   Case OpCode of
  601.  
  602.             { If Rec. Xmodem selected, then do it & report errors }
  603.     RX : InterpretCode(XmodemReceive(CRCMode,FileName));
  604.             { If Send Xmodem selected, then do it & report errors }
  605.     SX : InterpretCode(XmodemSend(CRCMode,OneK,FileName));
  606.             { If nothing selected, report error }
  607.     else InterpretCode(InvalidOperation);
  608.  
  609.   End;{Case}
  610.  
  611. End.
  612.