* PROGRAM: TERM II REMOTE FILE TRANSFER PROTOCOL 1 TRANSCEIVER (XTERM) * AUTHOR: RICHARD CONN * VERSION: 1.2 * DATE: 24 JUN 81 * PREVIOUS VERSIONS: 1.0 (24 APR 81), 1.1 (14 JUN 81) VERS equ 12 ; Version Number * THE FOLLOWING IS THE BASE ADDRESS FOR THE STANDARD (ORG 0) CP/M SYSTEM * IF YOU HAVE A NON-STANDARD SYSTEM, YOU MAY HAVE TO CHANGE THIS CPM$BASE EQU 0 * USER-SUPPLIED PARAMETERS DEFAULT$TO$DELAY EQU 0 ; DEFAULT TRANSMISSION TIMEOUT (0-9) * CP/M Values CPM EQU CPM$BASE+0 WBOOT EQU CPM$BASE+1 BDOS EQU CPM$BASE+5 FCB EQU CPM$BASE+5CH BUFF EQU CPM$BASE+80H * CP/M BDOS FUNCTIONS RDCON EQU 1 WRCON EQU 2 PRINT EQU 9 CREAD EQU 10 RSTDSK EQU 13 ; RESET DISK SYSTEM SELDISK EQU 14 ; SELECT DISK OPEN EQU 15 ;0FFH=NOT FOUND CLOSE EQU 16 ; " " SRCHF EQU 17 ; " " ERASE EQU 19 ;NO RET CODE READ EQU 20 ;0=OK, 1=EOF WRITE EQU 21 ;0=OK, 1=ERR, 2=?, 0FFH=NO DIR SPC MAKE EQU 22 ;0FFH=BAD REN EQU 23 ;0FFH=BAD RETDISK EQU 25 STDMA EQU 26 * DEFINE ASCII CONTROL CHARACTERS USED LXIB EQU 1 ; LXI B,XXXX INSTRUCTION SOH EQU 1 STX EQU 2 ETX EQU 3 EOT EQU 4 ENQ EQU 5 ACK EQU 6 BEL EQU 7 NAK EQU 15H SYN EQU 16H CAN EQU 18H LF EQU 10 CR EQU 13 BS EQU 8 TAB EQU 'I'-'@' ESC EQU 1BH CTRLB EQU 'B'-'@' CTRLC EQU 'C'-'@' CTRLE EQU 'E'-'@' CTRLX EQU 'X'-'@' CTRLY EQU 'Y'-'@' CTRLZ EQU 'Z'-'@' DL EQU 7FH * * Start of XTERM * ORG CPM$BASE+100H LXI H,0 ; SAVE STACK PTR DAD SP SHLD STACK LXI SP,STACK MVI A,DEFAULT$TO$DELAY ; SET DEFAULT TIMEOUT DELAY STA TO$DELAY CALL SP1$STAT ; IS THE I/O DEVICE SUPPORTED? ANI 1$0000B ; ALTERNATE 1 = CONSOLE JNZ XMIT1 LXI D,MSG$FATAL MVI C,PRINT ; PRINT FATAL ERROR MESSAGE CALL BDOS JMP CPM MSG$FATAL: DB 'TERM II Transceiver Fatal Error - Support Package Not ' DB 'Installed$' XMIT1: LDA FCB+1 ; PAUSE IF ? IS 1ST CHAR OF FILE NAME CPI '?' JNZ XMIT1$HEADER LXI D,SUBFIL ; ERASE CURRENT SUBMIT FILE MVI C,ERASE CALL BDOS CALL PRINT$MESSAGE DB 'TERM II FTP 1 Transceiver - Type Any Char to Cont -',0 CALL GET$RESPONSE XMIT1$HEADER: LXI SP,STACK CALL PRINT$MESSAGE DB CR,LF,LF,'** XTERM -- TERM II File Transfer Protocol 1 ' DB 'Transceiver, Version ',VERS/10+'0','.',(VERS MOD 10)+'0' DB ' **',CR,LF,LF,LF DB CR,LF,'Cmd Function Cmd Function' DB CR,LF,'--- -------- --- --------' DB CR,LF,' C Chat With Oper F Transceive File' DB CR,LF,' R Run CP/M Command V Set Default Values' DB CR,LF,' X Exit to CP/M',0 CALL PRINT$MESSAGE DB CR,LF,LF,' Transceiver Command? ',0 CALL GET$RESPONSE CPI ' '+1 ; IGNORE AND LESS JC XMIT1$HEADER LXI H,CTAB ; SCAN COMMAND TABLE FOR COMMAND MOV B,A ; COMMAND IN B CMND$SCANNER: MOV A,M ; GET TABLE LETTER ORA A ; END OF TABLE JZ INVLD$CMND ; INVALID COMMAND IF SO CMP B ; COMPARE AGAINST A JZ EXEC$CMND ; EXECUTE IF FOUND INX H ; SKIP LETTER INX H ; SKIP ADR LOW INX H ; SKIP ADR HIGH JMP CMND$SCANNER EXEC$CMND: INX H ; GET ADR LOW MOV E,M INX H ; GET ADR HIGH MOV D,M XCHG ; ADR IN HL MOV A,B ; COMMAND LETTER IN A PCHL ; "JMP" TO COMMAND INVLD$CMND: CALL PRINT$MESSAGE DB CR,LF,'** Invalid Command **',BEL,CR,LF,0 JMP XMIT1$HEADER * COMMAND TABLE CTAB: DB 'C' ; CHAT WITH OPERATOR DW CHAT DB 'F' ; FILE TRANSCEIVE DW SEND$RECV$FILE DB 'R' ; RUN CP/M COMMAND DW RUN$COMMAND DB 'V' ; SET DEFAULT VALUES DW SET$DEFAULTS DB 'X' ; EXIT TO CP/M DW CPM$EXIT DB 0 ; END OF TABLE ** CHAT WITH OPERATOR ** CHAT: CALL PRINT$MESSAGE DB CR,LF,'** CHAT -- Operator Communication Utility **',CR,LF DB CR,LF,' Signalling Operator -- Type ^E to Abort to XTERM' DB CR,LF,0 CALL SP1$STAT ANI 10$0000B ; ALTERNATE 2 = OPERATOR'S CONSOLE JNZ CHAT0 CALL PRINT$MESSAGE DB CR,LF,' Sorry, Operator''s Console Not Supported',CR,LF,0 JMP XMIT1$HEADER CHAT0: CALL PRINT$OMESSAGE DB CR,LF,'** CHAT -- Operator Communication Utility **',CR,LF DB CR,LF,' Strike Any Key to Answer Summons',CR,LF,0 * WAIT FOR ANSWER (ANY KEY) FROM OPERATOR CHAT$OANS: MVI A,BEL ; BEEP TO OPERATOR CALL A2$OUT CALL A2$ST ; RESPONSE? JNZ CHAT1 MVI B,1 ; 1 SEC BETWEEN BEEPS CALL RECV JC CHAT$OANS ; CONTINUE IF NO INPUT FROM USER ANI 7FH ; MASK INPUT CPI CTRLE JNZ CHAT$OANS JMP XMIT1$HEADER ; GOTO HEADER CHAT1: CALL A2$IN ; GET OPERATOR'S KEY CALL PRINT$MESSAGE DB CR,LF,'** CHAT Enabled -- Type ^E to Abort to XTERM **' DB CR,LF,0 CALL PRINT$OMESSAGE DB CR,LF,'** CHAT Enabled -- Type ^E to Abort to XTERM **' DB CR,LF,0 * MAIN CHAT LOOP CHAT$LOOP: CALL A2$ST ; INPUT FROM OPERATOR? JNZ CHAT$OIN CALL A1$ST ; INPUT FROM USER? JZ CHAT$LOOP * USER INPUT CALL A1$IN ; GET INPUT * INPUT CHAR PROCESSING CHAT$INPROC: ANI 7FH ; MASK MSB CPI CTRLE JZ CHAT$DONE CPI CR ; NEW LINE? JZ CHAT$CRLF CALL A1$OUT ; USER OUTPUT CALL A2$OUT ; OPERATOR OUTPUT JMP CHAT$LOOP * OPERATOR INPUT CHAT$OIN: CALL A2$IN ; GET INPUT JMP CHAT$INPROC ; PROCESS * CHAT COMPLETE CHAT$DONE: CALL PRINT$MESSAGE DB CR,LF,'** CHAT Complete **',CR,LF,0 CALL PRINT$OMESSAGE DB CR,LF,'** CHAT Complete **',CR,LF,0 JMP XMIT1$HEADER * CHAT NEWLINE CHAT$CRLF: MVI A,CR CALL A1$OUT CALL A2$OUT MVI A,LF CALL A1$OUT CALL A2$OUT JMP CHAT$LOOP ** SET DEFAULT VALUES OF XTERM ** SET$DEFAULTS: CALL PRINT$MESSAGE DB CR,LF,'** XTERM Default Value Set Routine **',CR,LF,0 CALL SET$TO$DELAY ; SET TIMEOUT DELAY CALL PRINT$MESSAGE DB CR,LF,'Default Value Set Complete',CR,LF,0 JMP XMIT1$HEADER ** EXECUTE A CP/M COMMAND AND RETURN TO TRANSCEIVER PROGRAM ** RUN$COMMAND: CALL PRINT$MESSAGE DB CR,LF,'** CP/M Command Execution **',CR,LF DB ' Enter Command Line (Just =Abort) -',CR,LF DB '........................................',CR,0 LXI D,FN$BUFFER ; 40 CHARS MAX MVI C,CREAD CALL BDOS CALL CRLF LDA FN$BUFFER+1 ; ABORT IF NO CHARS ORA A JZ XMIT1$HEADER * CREATE AND EXECUTE $$$.SUB FILE CALL ZERO$SUBFIL ; INIT SUBFIL FCB LXI D,SUBFIL ; ERASE OLD $$$.SUB FILE MVI C,ERASE CALL BDOS LXI D,SUBFIL ; MAKE NEW $$$.SUB FILE MVI C,MAKE CALL BDOS CPI 0FFH ; ERROR? JZ RUN$COMMAND$ERROR LXI H,RETURN$COMMAND ; WRITE RETURN COMMAND TO DISK CALL SUBMIT$WRITE LXI H,FN$BUFFER+1 ; WRITE COMMAND TO DISK CALL SUBMIT$WRITE LXI D,SUBFIL ; CLOSE $$$.SUB MVI C,CLOSE CALL BDOS JMP CPM ; EXECUTE BY WARM BOOTING * CANNOT CREATE SUBMIT FILE RUN$COMMAND$ERROR: CALL PRINT$MESSAGE DB CR,LF,'ERROR -- Cannot Create Submit File -- Disk Full',0 JMP XMIT1$HEADER * WRITE SUBMIT LINE TO BUFFER AND THEN TO DISK SUBMIT$WRITE: LXI D,BUFF ; PT TO BUFFER MOV C,M ; GET CHAR COUNT INR C ; ADD 1 TO COUNT (FOR CHAR COUNT) SW1: MOV A,M ; GET CHAR STAX D ; STORE IT INX H ; PT TO NEXT INX D DCR C ; COUNT DOWN JNZ SW1 XRA A ; STORE ZERO STAX D INX D ; PT TO NEXT MVI A,'$' ; STORE '$' STAX D INX D SW2: MVI A,CTRLZ ; STORE ^Z STAX D INR E ; PT TO NEXT MOV A,E CPI 0FFH JNZ SW2 LXI D,SUBFIL ; WRITE TO SUBMIT FILE MVI C,WRITE CALL BDOS RET ** TRANSCEIVE A FILE ** SEND$RECV$FILE: CALL PRINT$MESSAGE DB CR,LF,'** XTERM II File Transceiver **',CR,LF,LF,0 XRA A STA SECTNO ; ZERO SECTOR NUMBER LXI D,BUFF ; SET DMA ADDRESS MVI C,STDMA CALL BDOS CALL INIT$FCB CALL SELECT$DISK CALL PRINT$MESSAGE DB CR,LF DB ' Send File from Remote Host to TERM II [Remote>You] (S) or' DB CR,LF DB ' Receive File from TERM II on Remote Host [You>Remote] (R)' DB CR,LF,' (S/R/^C/=S)? ',0 CALL GET$RESPONSE CPI CTRLC ; ABORT? JZ XMIT1$HEADER CPI 'R' ; RECEIVE? JZ RECV$FILE SEND$FILE: LXI SP,STACK CALL PRINT$MESSAGE DB 'TERM II File Transfer Protocol 1 - SEND Function' DB CR,LF,' Initializing System -- Please Stand By',CR,LF,0 CALL OPEN$FILE CALL PRINT$MESSAGE DB CR,LF,'FTP 1 SEND - Type C to CONTINUE - ',0 CALL GET$RESPONSE CPI 'C' ; CONTINUE? JZ SEND$SYN$WAIT JMP XMIT1$HEADER ; CONTINUE WITH XTERM IF NOT 'C' * * XTERM EXIT ROUTINE * XT$EXIT: CALL IN$CON ANI 7FH ; MASK MSB CPI CR ; DONE IF JNZ XT$EXIT JMP XMIT1$HEADER * * CP/M EXIT ROUTINE * CPM$EXIT: CALL PRINT$MESSAGE DB CR,LF,'** Exiting to CP/M **',0 LHLD STACK SPHL RET * * SYNCHRONIZE WITH RECEIVER * SEND$SYN$WAIT: MVI A,SYN ; SEND SYN CALL SEND ; NO MONITOR MVI B,1 ; 1 SEC TIMEOUT CALL RECV ; NO MONITOR JC SEND$SYN$WAIT CPI ACK ; MUST BE ACK JNZ SEND$SYN$WAIT MVI B,1 ; 1 SEC TIMEOUT CALL RECV * * FILE TRANSFER PROTOCOL 1 -- SEND * * This section of the TERMINAL Program contains the routines * required to transmit files via Protocol 1. Protocol 1 data transmission * format is described below: * * TRANSMISSION FORMAT: * SOH 0 STX ETX ENQ * * In sending a file to an external computer, four basic situations may occur: * 1) TRANSMIT DATA AND RECEIVE AN ACKNOWLEDGE (ESC ACK) * 2) TRANSMIT DATA AND RECEIVE A NEGATIVE ACK (NAK) * 3) TRANSMIT DATA AND RECEIVE NEITHER ACK NOR NAK * 4) TRANSMIT DATA AND RECEIVE NOTHING * SEND1$FILE: LDA SECTNO ; INCREMENT SECTOR NUMBER INR A STA SECTNO * READ SECTOR AND ABORT IF DONE LXI D,FCB MVI C,20 CALL BDOS ORA A JNZ EOF ; PROCESS EOF CONDITION * SEND OR REPEAT SECTOR REPTB: LXI SP,STACK * SEND START OF HEADER MVI A,SOH CALL SEND * SEND DATA PACKET TYPE MVI A,0 ; DATA PACKET CALL SEND * SEND SECTOR NUMBER AND ITS COMPLEMENT LDA SECTNO ; GET SECTOR NUMBER CALL SEND CMA ; COMPLEMENT IT CALL SEND * SEND START OF TRANSMISSION MVI A,STX CALL SEND * SEND SECTOR DATA XRA A ; A=0 STA CKSUM ; INIT CKSUM LXI H,BUFF SENDC: MOV A,M CALL SEND ; SEND CHAR CALL ADD$TO$CKSUM ; ADD CHAR TO CKSUM INR L ; DONE WITH SECTOR? JNZ SENDC * SEND END OF TRANSMISSION MVI A,ETX CALL SEND * SEND CKSUM LDA CKSUM ; GET CHECKSUM CALL SEND * GET ACK ON SECTOR CALL RECV$ACK ; SEND ENQ AND GET ACK CPI ACK JZ SEND1$FILE RECV1$NAK$FLUSH: MVI B,1 ; FLUSH CHARS UNTIL NOTHING RECEIVED FOR 1 SEC CALL RECV JNC RECV1$NAK$FLUSH JMP REPTB * * SEND ENQ AND RECEIVE ACK OR NAK ONLY * RECV$ACK: PUSH B MVI A,ENQ ; SEND ENQ CALL SEND MVI B,5 ; 5 SEC TIMEOUT CALL RECV POP B JNC RECV2$ACK JMP XT$EXIT RECV2$ACK: CPI ESC ; IF ACK, MUST BE SEQUENCE OF ESC AND ACK JNZ RECV3$ACK PUSH B MVI B,1 ; 1 SEC TIMEOUT CALL RECV POP B JC RECV3$ACK CPI ACK RZ CPI CAN ; CANCEL? JNZ RECV3$ACK ; NO CAN, SO NAK JMP XT$EXIT ; GOTO EXIT MODE RECV3$ACK: MVI A,NAK ; INTERPRET NON- AS RET * EOF -- END OF FILE PROCESSING EOF: MVI C,10 ; SEND 10 EOT'S SEOT: PUSH B ; SAVE BC MVI A,EOT CALL SEND POP B ; GET COUNT DCR C JNZ SEOT JMP XT$EXIT RECV$FILE: CALL PRINT$MESSAGE DB 'TERM II File Transfer Protocol 1 - RECV Function' DB CR,LF,' Initializing System -- Please Stand By',CR,LF,0 CALL ERASE$OLD$FILE CALL MAKE$NEW$FILE CALL PRINT$MESSAGE DB CR,LF,'FTP 1 RECV - Type C to CONTINUE - ',0 CALL GET$RESPONSE CPI 'C' ; CONTINUE? JNZ XMIT1$HEADER RECV$SYN$WAIT: MVI B,1 ; 1 SEC TIMEOUT CALL RECV JC RECV$SYN$WAIT CPI SYN ; GOT IT? JNZ RECV$SYN$WAIT SYN$ACK: MVI A,ACK ; SEND ACK CALL SEND * * FILE TRANSFER PROTOCOL 1 -- RECEIVE * * WAIT FOR AND PICK UP HEADER RECV$HDR: LXI SP,STACK ; RESET STACK MVI B,6 ; 6 SEC TIMEOUT CALL RECV JC SEND$NAK ; TIMEOUT RECV$HDR1: CPI SYN ; SYNC? JZ SYN$ACK CPI EOT ; END OF TRANSMISSION? JZ RECV$DONE CPI SOH ; START OF HEADER? JZ RECV$SECTOR * SEND NAK AND GET BLOCK AGAIN SEND$NAK: MVI B,1 ; FLUSH UNTIL NO CHARS CALL RECV JNC SEND$NAK MVI A,NAK ; SEND NAK CALL SEND RECV$FLUSH: JMP RECV$HDR * SEND CANCEL AND KEEP ON SENDING UNTIL ABORT OR NO FURTHER INFO RECEIVED SEND$CAN: MVI B,2 ; 2 SEC TIMEOUT CALL RECV JNC SEND$CAN MVI A,ESC CALL SEND MVI A,CAN CALL SEND MVI B,3 ; 3 SEC TIMEOUT CALL RECV JNC SEND$CAN JMP XT$EXIT * RECEIVE A PACKET OF INFORMATION RECV$SECTOR: * RECEIVE PACKET TYPE MVI B,1 ; 1 SEC TIMEOUT CALL RECV JC SEND$NAK ORA A ; MUST BE OF TYPE 0 FOR NOW JNZ SEND$NAK * RECEIVE, VERIFY, AND COMPARE SECTOR NUMBER MVI B,1 ; 1 SEC TIMEOUT CALL RECV JC SEND$NAK MOV C,A ; SAVE SECTOR NUMBER IN C MVI B,1 ; 1 SEC TIMEOUT CALL RECV JC SEND$NAK ANA C ; OK? ZERO IF SO JNZ SEND$NAK LDA SECTNO ; COMPARE AGAINST SECTOR NUMBER CMP C ; COMPARE ... IF C = OLD SECTOR NUMBER, FLUSH JZ SEND$ACKF INR A ; ADD 1 CMP C ; COMPARE ... IF C>SECT NO, CANCEL JC SEND$CAN ; CANCEL IF INPUT SECT > EXPECTED SECT JNZ SEND$ACKF ; FLUSH SECTOR IF NOT EXPECTED SECT * RECEIVE STX MVI B,1 ; 1 SEC TIMEOUT CALL RECV JC SEND$NAK CPI STX JNZ SEND$NAK * RECEIVE SECTOR DATA XRA A ; A=0 STA CKSUM ; INIT CKSUM LXI H,80H ; RECEIVE BUFFER RECV$CHAR: MVI B,1 ; 1 SEC TIMEOUT CALL RECV ; GET CHAR JC SEND$NAK CALL ADD$TO$CKSUM ; ADD CHAR TO CKSUM MOV M,A ; STORE CHAR INR L ; PT TO NEXT JNZ RECV$CHAR MVI B,2 ; 2 SEC TIMEOUT FOR ETX CALL RECV JC SEND$NAK CPI ETX ; ETX EXPECTED JNZ SEND$NAK ; NAK OTHERWISE * ETX RECEIVED RECV$ETX: MVI B,1 ; GET CHECKSUM CALL RECV JC SEND$NAK MOV B,A ; SAVE CHECKSUM IN B PUSH B ; SAVE CKSUM MVI B,1 ; GET ENQ CALL RECV POP B ; GET CKSUM JC SEND$NAK CPI ENQ ; MUST BE ENQUIRY JNZ SEND$NAK LDA CKSUM ; GET COMPUTED CKSUM MOV C,A ; STORE IN C MOV A,B ; COMPARE CKSUMS CMP C JNZ SEND$NAK * WRITE SECTOR TO DISK LXI D,FCB MVI C,21 CALL BDOS ORA A JZ SEND$ACK MVI A,ESC ; DISK ERROR -- CANCEL CALL SEND MVI A,CAN CALL SEND JMP XT$EXIT * SEND ACKNOWLEDGE * FLUSH INPUT AND THEN SEND ACK SEND$ACKF: MVI B,1 ; FLUSH INPUT AND THEN SEND ACK CALL RECV JNC SEND$ACKF JMP SEND$ACK1 * INCR SECTOR NUMBER AND THEN SEND ACK SEND$ACK: LDA SECTNO ; INCREMENT SECTOR NUMBER INR A STA SECTNO * SEND ACK SEND$ACK1: MVI A,ESC ; SEND CALL SEND MVI A,ACK ; ACKNOWLEDGE CALL SEND MVI B,6 ; 6 SEC TIMEOUT CALL RECV JC SEND$NAK CPI ENQ ; ANOTHER ENQUIRY? JZ SEND$ACK1 JMP RECV$HDR1 * DONE W/RECEPTION OF DATA RECV$DONE: LXI D,FCB MVI C,16 ; CLOSE FILE CALL BDOS * FLUSH EOT'S FROM LINE UNTIL NOTHING IS SENT FOR 1 SEC RECV$DONE$FLUSH: MVI B,1 ; 1 SEC DELAY CALL RECV JNC RECV$DONE$FLUSH JMP XT$EXIT * * ABORT PROCEDURE * ABORT: JMP XT$EXIT * * DISK SUPPORT ROUTINES * ** SELECT DISK SPECIFIED BY LOW-ORDER BYTE OF FCB ** SELECT$DISK: LDA FCB ; GET BYTE ORA A ; 0=DEFAULT TO B: RZ DCR A ; ADJUST FOR LOG-IN PUSH PSW MOV E,A ; DISK IN E MVI C,SELDISK ; SELECT DISK CALL BDOS CALL PRINT$MESSAGE DB CR,LF,'Drive ',0 POP PSW ADI 'A' ; CONVERT TO ASCII CALL OUT$CON CALL PRINT$MESSAGE DB ' Selected (New Default)',0 RET ** LOAD FILE NAME INTO FCB ** LOAD$FCB: CALL PRINT$MESSAGE DB CR,LF,'File Name (D:FILENAME.TYP)? ',0 LXI D,FN$BUFFER ; FILE NAME BUFFER MVI C,CREAD ; READ FROM CONSOLE W/EDITING CALL BDOS CALL CRLF LXI H,FN$BUFFER+1 ; LOAD FCB NOW XRA A ; SET DEFAULT DRIVE STA FCB * CLEAR FCB LXI D,FCB+1 ; PT TO FN ENTRY MVI C,11 ; CLEAR 11 BYTES PUSH D MVI A,' ' ; STORE LFCB0: STAX D ; PUT CHAR INX D ; PT TO NEXT DCR C ; DECR COUNT JNZ LFCB0 CALL ZERO$FCB ; ZERO OUT FIELDS OF FCB * LOAD FCB WITH FILE NAME POP D ; GET PTR TO FN ENTRY IN FCB MOV A,M ; GET CHAR COUNT ORA A ; 0=CLEAR FCB RZ MOV C,M ; GET CHAR COUNT INX H ; CHECK FOR DRIVE SPEC INX H MOV A,M ; IS IT COLON? DCX H ; PT BACK TO CHAR COUNT DCX H CPI ':' ; COLON MEANS WE HAVE A DRIVE SPEC JNZ LFCB1 DCR C ; ADJUST CHAR COUNT DCR C RZ ; ABORT IF JUST DRIVE SPEC INX H ; GET DRIVE LETTER MOV A,M INX H ; SKIP OVER COLON SUI 'A'-1 ; INVALID SPEC? JC BADDSPEC JZ BADDSPEC CPI 6 ; A-D JNC BADDSPEC STA FCB ; SET DRIVE SPEC LFCB1: INX H ; PT TO NEXT CHAR MOV A,M ; GET CHAR CPI '.' ; EXT? JZ LFCB2 CPI ' '+1 ; SKIP CHAR IF <= JC LFCB1S CALL CAPS ; CAPITALIZE STAX D ; STORE CHAR INX D ; PT TO NEXT LFCB1S: DCR C ; DECR COUNT RZ JMP LFCB1 * LOAD FCB WITH FILE TYPE LFCB2: INX H ; PT TO TYP DCR C ; DECR COUNT FOR '.' RZ LXI D,FCB+9 ; PT TO EXT IN FCB LFCB3: MOV A,M ; GET CHAR CPI ' '+1 ; SKIP CHAR IF <= JC LFCB3S CALL CAPS ; CAPITALIZE STAX D ; PUT IT INX D ; PT TO NEXT LFCB3S: INX H DCR C ; COUNT DOWN JNZ LFCB3 RET BADDSPEC: CALL PRINT$MESSAGE DB 'ERROR - Invalid Drive Specification',CR,LF,0 JMP LOAD$FCB ** SPECIFY FILE NAME EXPLICITLY AND INITIALIZE FCB ** INIT$FCB: LDA FCB+1 ; CHECK FOR PRESENCE OF FILE NAME CPI ' '+1 JC INIT$FCB$ERR FCB0: PUSH H ! PUSH D ! PUSH B CALL ZERO$FCB ; ZERO OUT FIELDS OF FCB CALL PRINT$FN ; PRINT FILE NAME CALL PRINT$MESSAGE DB ' Verify File Name (Y/N/=N)? ',0 CALL GET$RESPONSE CPI 'Y' JNZ INIT$FCB$OPT POP B ! POP D ! POP H RET INIT$FCB$OPT: CALL PRINT$MESSAGE DB ' Specify Different File (Y/N/=Y)? ',0 CALL GET$RESPONSE CPI 'N' ; ABORT TO TEXIT JZ XMIT1$HEADER JMP INIT$FCB$ERR1 INIT$FCB$ERR: PUSH H ! PUSH D ! PUSH B CALL PRINT$MESSAGE DB CR,LF,'File Name Not Specified',0 INIT$FCB$ERR1: CALL LOAD$FCB ; GET FILE NAME POP B ! POP D ! POP H JMP FCB0 ** ZERO OUT ALL BUT DISK NUMBER, FILE NAME, AND FILE TYPE FIELDS OF FCB ** ZERO$FCB: PUSH H PUSH B LXI H,FCB+12 ; START AT 12TH POSITION MVI B,24 ; 24 BYTES (INCL RANDOM I/O BYTES) ZERO$FCB$LOOP: MVI M,0 ; STORE ZERO INX H DCR B JNZ ZERO$FCB$LOOP POP B POP H RET ZERO$SUBFIL: PUSH H PUSH B LXI H,SUBFIL+12 ; START AT 12TH POSITION MVI B,24 ; 24 BYTES JMP ZERO$FCB$LOOP ** PRINT FN AND EXT IN FCB ON CON: ** PRINT$FN: CALL PRINT$MESSAGE DB CR,LF,'File Name: ',0 CALL PR$FN$DISK ; DISK NAME LXI H,FCB+1 ; PT TO FN MVI C,8 ; 8 CHARS CALL PR$FN$LOOP MVI A,'.' ; SEPARATOR CALL OUT$CON MVI C,3 ; 3 CHARS IN EXT CALL PR$FN$LOOP CALL CRLF RET PR$FN$LOOP: MOV A,M ; GET CHAR INX H ; PT TO NEXT CALL OUT$CON DCR C JNZ PR$FN$LOOP RET PR$FN$DISK: LDA FCB ; GET SELECTED DISK ORA A ; DEFAULT? JNZ PR$FN$DISK1 MVI C,RETDISK ; GET DEFAULT DISK NUMBER CALL BDOS INR A ; ADD 1 SO A:=1, B:=2, ETC. PR$FN$DISK1: ADI 'A'-1 ; PRINT LETTER CALL OUT$CON MVI A,':' CALL OUT$CON RET ** ERASE DESITINATION FILE IF IT EXISTS ** ERASE$OLD$FILE: CALL ZERO$FCB * SEE IF FILE EXISTS LXI D,FCB MVI C,SRCHF ;SEE IF IT EXISTS CALL BDOS INR A ;FOUND? RZ ;NO, RETURN * FILE EXISTS -- ASK USER IF HE REALLY WANTS TO DELETE IT CALL PRINT$MESSAGE DB 'File Exists -- Type Y to Erase (Y/N/=N)? ',0 CALL GET$RESPONSE CPI 'Y' JNZ XMIT1$HEADER * ERASE OLD FILE LXI D,FCB MVI C,ERASE CALL BDOS RET ** OPEN THE DESTINATION FILE ** OPEN$FILE: CALL ZERO$FCB LDA FCB ; DETERMINE DEFAULT DRIVE ORA A ; 0=USE DEFAULT DRIVE JZ OF1 DCR A ; ADJUST TO 0-15 MOV E,A MVI C,SELDISK ; LOG IN DISK CALL BDOS OF1: LXI D,FCB MVI C,OPEN CALL BDOS INR A ;OPEN OK? RNZ ;GOOD OPEN CALL PRINT$MESSAGE DB 'Error - Can''t Open File',0 JMP XMIT1$HEADER ** CREATE THE DESTINATION FILE ON DISK ** MAKE$NEW$FILE: CALL ZERO$FCB LDA FCB ; DETERMINE DEFAULT DRIVE ORA A ; 0=DO IT JZ MF1 DCR A ; ADJUST TO 0-15 MOV E,A MVI C,SELDISK ; LOG IN DISK CALL BDOS MF1: LXI D,FCB MVI C,MAKE CALL BDOS INR A ;FF=BAD RNZ ;OPEN OK * DIRECTORY FULL - CAN'T MAKE FILE CALL PRINT$MESSAGE DB 'Error - Can''t Create File -- ' DB 'Directory must be full',0 JMP CPM$EXIT * * I/O SUPPORT ROUTINES * RECV$TIMEOUT$VALUE EQU 8000H RECV: PUSH D ; SAVE PUSH B MSEC: LXI D,RECV$TIMEOUT$VALUE ; 1 SEC DCR COUNT LDA TO$DELAY ; ADJUST BY TIMEOUT DELAY ADD D MOV D,A MWTI: CALL ST$CON ; GET MODEM RECEIVE STATUS JNZ MCHAR ; GOT CHAR DCR E ; COUNT DOWN JNZ MWTI ; FOR TIMEOUT DCR D JNZ MWTI DCR B ; DCR # OF SECONDS JNZ MSEC * MODEM TIMED OUT RECEIVING POP B POP D ; RESTORE BC, DE STC ; CARRY SHOWS TIMEOUT RET * ** GOT MODEM CHAR * MCHAR: CALL IN$CON ; GET CHAR FROM MODEM POP B POP D ; RESTORE BC,DE * TURN OFF CARRY TO SHOW NO TIMEOUT ORA A RET * SET TIMEOUT DELAY CONSTANT SET$TO$DELAY: CALL PRINT$MESSAGE DB CR,LF,'Specify Timeout Delay (0..9/=0)? ',0 CALL GET$RESPONSE CPI CR RZ SUI '0' ; CONVERT TO BINARY RC CPI 10 RNC RLC ; MOVE TO HIGH NYBBLE RLC RLC RLC ANI 0F0H STA TO$DELAY RET GET$RESPONSE: CALL IN$CON ; GET CHAR CALL OUT$CON ; ECHO CHAR CALL CRLF CAPS: ANI 7FH ; MASK OUT MSB CPI 'a' ; LESS THAN LOWER-CASE A? RC CPI 'z'+1 ; IN RANGE? RNC SUI 'a'-'A' ; ADJUST RET CRLF: PUSH PSW MVI A,CR CALL OUT$CON MVI A,LF CALL OUT$CON POP PSW RET ADD$TO$CKSUM: PUSH B PUSH PSW LDA CKSUM MOV B,A POP PSW PUSH PSW ADD B STA CKSUM POP PSW POP B RET * PRINT MESSAGE PTED TO BY RET ADR ENDING IN 0 PRINT$MESSAGE: MVI A,1 ; A1 (USER) JMP PM0 PRINT$OMESSAGE: MVI A,2 ; A2 (OPERATOR) PM0: STA OFLAG ; OUTPUT FLAG XTHL ; SAVE HL AND GET RET ADR PUSH B MVI C,0 ; TAB COUNT PM1: MOV A,M ; GET CHAR INX H ; PT TO NEXT ORA A ; 0=DONE JZ PM2 CPI TAB ; TABULATE? JZ PM$TAB INR C ; ADD 1 FOR TABULATION CALL A12$OUT ; OUTPUT TO A1 OR A2 CPI CR ; ? JNZ PM1 MVI C,0 ; RESET TABULATION JMP PM1 PM2: POP B XTHL ; PUT RET ADR AND RESTORE HL RET PM$TAB: MVI A,' ' ; PRINT CALL A12$OUT ; OUTPUT TO A1 OR A2 INR C MOV A,C ANI 7 ; EVERY 8 JNZ PM$TAB JMP PM1 * OUTPUT TO A1 OR A2 DEPENDING ON OFLAG A12$OUT: PUSH PSW ; SAVE CHAR LDA OFLAG ; A1 OR A2? CPI 2 ; A2? JZ A12$OUT2 POP PSW JMP A1$OUT ; A1 A12$OUT2: POP PSW JMP A2$OUT ; A2 * * BUFFERS * TO$DELAY: DS 1 SECTNO: DS 1 CKSUM: DS 1 OFLAG: DS 1 RETURN$COMMAND: DB 7,'XTERM *',0 ; CHAR COUNT AND COMMAND LINE SUBFIL: DB 0,'$$$ SUB',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 DB 0,0,0,0,0 DS 10 ; BUFFER BUF$SIZ EQU 40 FN$BUFFER: DB BUF$SIZ ; BUF$SIZ BYTES IN BUFFER DS BUF$SIZ+1 ; BUF$SIZ BYTES + CHAR COUNT DS 80 ; STACK SPACE STACK: DS 2 ; CP/M STACK ORG $/256*256+256 ; SET TO PAGE BOUNDARY BASE EQU $ XRA A ; NOTHING IMPLEMENTED NOP RET LXI H,BASE ; MARK END OF SUPPORT PACKAGE FOR SP$END MVI L,0 INR H RET SP1$STAT EQU BASE ; SUPPORT PACKAGE STATUS SP1$END EQU BASE+3 ; END OF SUPPORT PACKAGE A1$ST EQU BASE+84 ; ALTERNATE 1 INPUT STATUS A1$IN EQU BASE+90 ; ALTERNATE 1 INPUT A1$OUT EQU BASE+93 ; ALTERNATE 1 OUTPUT A2$ST EQU BASE+99 ; ALTERNATE 2 INPUT STATUS A2$IN EQU BASE+105 ; ALTERNATE 2 INPUT A2$OUT EQU BASE+108 ; ALTERNATE 2 OUTPUT ST$CON EQU A1$ST IN$CON EQU A1$IN OUT$CON EQU A1$OUT SEND EQU OUT$CON END