PAGE 59, 132 TITLE MSXModem -- Send and receive files using the XMODEM protocol ; Update 8 Jan 86 IF1 %OUT >> Starting pass 1 ELSE %OUT >> Starting pass 2 ENDIF PUBLIC XSend, XReceive INCLUDE MsDefs.H DataS SEGMENT PUBLIC 'DataS' EXTRN Count:WORD, Pack:BYTE, OFilSz:WORD, TFilSz:WORD, PortVal:WORD ; Static data ErMs31 DB '? File not found',cr,lf,'$' ErMs32 DB "? Can't create file",cr,lf,'$' No_data_in_file DB '? File is empty', Cr, Lf, '$' No_initiating_NAK DB 'Timeout waiting for receiver to start$' Too_many_retries DB 'Too many retries$' XM_send_status DB cr,' XMODEM send: Waiting for receiver to start$' XM_receive_status DB cr,' XMODEM receive: Starting$' In_progress DB 'In progress$' Completed DB 'Completed$' Failed DB 'Failed$' File_help DB ' File specification with optional path name$' ; Writable data EVEN Tally DW 0 ; Counter for checksum Filename_ptr DW 0 ; Pointer to filename from SPath routine Handle DW 0 ; File handle for FT file File_size DD 0 ; File size Buffer_ptr DW 0 ; Pointer into file buffer Buffer_count DW 0 ; Count of characters in buffer HrMn DW 0 ; Place to save hours and minutes ScHn DW 0 ; Place to save seconds and hundredths TFile DB 100 DUP (?) ; Place to store file name Buffer DB 1024 DUP (?) ; File I/O buffer Abort_flag DB 0 ; Flag that ^C was hit EOT_flag DB 0 ; Flag that we received a valid EOT signal Timeout DB 0 ; Number of seconds to allow DataS ENDS Code SEGMENT PUBLIC EXTRN Comnd:NEAR, SPath:NEAR, Init:NEAR, Close_transfer_screen:NEAR EXTRN Show_error:NEAR, Show_retries:NEAR, Show_status:NEAR, PerPr:NEAR EXTRN SerIni:NEAR, Nout:NEAR, RRInit:NEAR, Init:NEAR, Show_packets:NEAR EXTRN ClrFln:NEAR, Write_to_standard_output:NEAR, KbPr:NEAR, PrtChr:NEAR EXTRN Beep:NEAR, Say_aborted:NEAR, OutChr:NEAR, ClrBuf:NEAR, SerRst:NEAR EXTRN EOT_bells:NEAR ASSUME cs:Code, ds:DataS, es:DataS ; XReceive -- XMODEM receive routine, called at command level XReceive PROC mov ah, CMTXT ; Read in a line of text mov bx, OFFSET TFile ; Temp area for text, max 100 chars mov dx, OFFSET File_help ; File specification with optional path name call Comnd ; Do it jmp RSkp mov si, OFFSET TFile ; Point to start of user input mov bl, ah ; Copy length to bl sub bh, bh ; Clear high half mov BYTE PTR [si+bx], 0 ; Make it ASCIZ mov ax, (CREATE2*256) + 0 ; DOS 2.0 file create call sub cx, cx ; No special attributes mov dx, OFFSET TFile ; Point to name again int Dos jnc XRE_3 ; Create ok, keep going XRE_2: mov ah, PrStr ; Code to type a message mov dx, OFFSET ErMs32 ; ? Can't create file int Dos ; Type it jmp RSkp XRE_3: mov Buffer_count, 0 ; Clear count of chars in buffer mov Buffer_ptr, OFFSET Buffer ; Set up initial buffer pointer mov Handle, ax ; Save file descriptor mov OFilSz, 0 ; Flag no percentages on this call SerRst ; Unhook serial interrupt first just-in-case call SerIni ; Set up serial port as required call RRInit ; Set up counters call Init ; Set up screen for file transfer mov dx, OFFSET XM_receive_status ; "XMODEM receive: Starting" call Show_status ; Display status mov Pack.NumRtr, 0 ; Clear the retry count call Show_retries ; then display it call ClrFln ; Clear filename and position us there mov dx, OFFSET TFile ; Give him the filename pointer call Write_to_standard_output ; Use common routine to type filename mov bx, PortVal ; Port structure mov al, [bx].floflg ; Save current flow control setting push ax mov [bx].floflg, 0 ; Turn it off for XMODEM call Do_XMODEM_receive ; Use other routine to do the work call EOT_bells ; Make an optional noise mov bx, PortVal ; Port structure pop ax mov [bx].floflg, al ; Restore it to what it was call Close_transfer_screen ; Make screen go normal again mov ah, CLOSE2 ; Code to close a file mov bx, Handle ; The file handle int Dos ; Close the file cmp Abort_flag, 0 ; Did we abort this transfer? je XRE_9 ; No mov ah, 41h ; Code to unlink (delete) a file mov dx, OFFSET TFile ; Filename used to create file int Dos ; Delete the file PUBLIC XRE_9 XRE_9: jmp RSkp ; Done here XReceive ENDP ; XSend -- XMODEM send routine, called at command level XSend PROC mov ah, CMTXT ; Read in a line of text mov bx, OFFSET TFile ; Temp area for text, max 100 chars mov dx, OFFSET File_help ; File specification with optional path name call Comnd ; Do it jmp RSkp mov si, OFFSET TFile ; Point to start of user input mov bl, ah ; Copy length to bl sub bh, bh ; Clear high half mov BYTE PTR [si+bx], 0 ; Make it ASCIZ mov ax, si ; Point to name again call SPath ; Is it around? jc XSE_2 ; No, go complain mov Filename_ptr, ax ; Save pointer to filename for later mov dx, ax ; Point to name from SPath mov ax, (Open2*256) + 0 ; DOS 2.0 open call for read int Dos jnc XSE_3 ; Open ok, keep going XSE_2: mov ah, PrStr ; Code to type a message mov dx, OFFSET ErMs31 ; ? File not found int Dos ; Type it jmp RSkp XSE_3: mov Handle, ax ; Save file descriptor mov bx, ax ; Need descriptor here mov ax, (LSeek*256) + 2 ; Seek 0 bytes from end sub cx, cx sub dx, dx int Dos mov WORD PTR File_size, ax ; Store length mov WORD PTR File_size + 2, dx ; (doubleword) mov cx, ax ; Copy low order part to cx or cx, dx ; Merge the two words jnz XSE_4 ; There is at least one character mov ah, PrStr ; Code to type a message mov dx, OFFSET No_data_in_file ; ? File is empty int Dos ; Type the message jmp RSkp ; Give up XSE_4: mov bx, 100 ; Get a 100 into bx div bx ; Convert file size for percentage calculation mov OFilSz, ax ; Save it for PerPr mov ax, (LSeek*256) + 0 ; Seek back to the beginning mov bx, Handle ; Get back the file handle sub cx, cx sub dx, dx int Dos call Read_a_bufferfull ; Use other routine to load the buffer call SerRst ; Unhook serial interrupt first just-in-case call SerIni ; Set up serial port as required call RRInit ; Set up counters call Init ; Set up screen for file transfer mov dx, OFFSET XM_send_status ; "XMODEM send: Waiting for ..." call Show_status ; Display status mov Pack.NumRtr, 0 ; Clear the retry count call Show_retries ; then display it call ClrFln ; Clear filename and position us there mov dx, Filename_ptr ; Give him the filename pointer call Write_to_standard_output ; Use common routine to type filename mov bx, PortVal ; Port structure mov al, [bx].floflg ; Save current flow control setting push ax mov [bx].floflg, 0 ; Turn it off for XMODEM call Do_XMODEM_send ; Use other routine to do the work call EOT_bells ; Make an optional noise mov bx, PortVal ; Port structure pop ax mov [bx].floflg, al ; Restore it to what it was call Close_transfer_screen ; Make screen go normal again mov ah, CLOSE2 ; Code to close a file mov bx, Handle ; The file handle int Dos ; Close the file jmp RSkp ; Done here XSend ENDP PUBLIC Write_a_bufferfull ; Write_a_bufferfull -- Write to HANDLE from BUFFER Write_a_bufferfull PROC cmp Buffer_count, 0 ; Are chars to dump? jz WAB_1 ; Nothing to do, just exit mov ah, WRITEF2 ; DOS 2.0 file handle write mov bx, Handle ; File handle to use mov cx, Buffer_count ; Amount of stuff in buffer mov dx, OFFSET Buffer ; Ptr to buffer int Dos ; Write a buffer full mov Buffer_count, 0 ; Clear count of chars in buffer WAB_1: mov Buffer_ptr, OFFSET Buffer ; Set up initial buffer pointer ret ; Done here Write_a_bufferfull ENDP ; Read_a_bufferfull -- Read from HANDLE into BUFFER Read_a_bufferfull PROC mov si, OFFSET Buffer ; Ptr to start of buffer mov di, OFFSET Buffer + 1 ; Next byte mov cx, (SIZE Buffer)-1 ; How many to copy mov BYTE PTR [si], CtlZ ; Clear first element of array with control-Z rep movsb ; Clear rest of array mov ah, ReadF2 ; DOS 2.0 file handle read mov bx, Handle ; File handle to use mov cx, SIZE Buffer ; Size of buffer mov dx, OFFSET Buffer ; Ptr to buffer int Dos ; Read a buffer full, or whatever we can get mov Buffer_count, ax ; Store number of chars read sub WORD PTR File_size, ax ; Account for what we have just read sbb WORD PTR File_size + 2, 0 ; Do high order half also mov Buffer_ptr, OFFSET Buffer ; Set up initial buffer pointer ret ; Done here Read_a_bufferfull ENDP ; Do_XMODEM_receive -- Worker XMODEM file receive routine Do_XMODEM_receive PROC call ClrBuf ; Flush any chars already received mov EOT_flag, 0 ; Clear this flag mov Pack.NumPkt, 0 ; Clear count of transmitted packets mov Pack.PktNum, 1 ; XMODEM starts with packet number 1 mov TFilSz, 0 ; Clear out transfered file size ... mov TFilSz + 2, 0 ; ... low half too (this is backwards) mov ah, ASCII_NAK ; Load up the char to send call OutChr ; Send it once nop nop nop DXR_1: mov Pack.NumTry, 0 ; Clear this counter mov dx, OFFSET In_progress ; "XMODEM receive: In progress" call Show_status ; Display status DXR_2: call Receive_a_packet ; Receive a packet jnc DXR_4 ; Got a packet with no timeout or error cmp Abort_flag, 0 ; Aborted? jz DXR_No_abort ; No mov dx, OFFSET Failed ; Say transfer bombed jmp Show_status DXR_No_abort: inc Pack.NumRtr ; Bump global count call Show_retries ; Show the user inc Pack.NumTry ; Bump the count of retries on this packet cmp Pack.NumTry, 10 ; Hit 10 failures yet? jl DXR_2 ; No, keep going mov dx, OFFSET Failed call Show_status mov dx, OFFSET Too_many_retries ; Too many retries jmp Show_error ; Complain on-screen, ret from there DXR_4: cmp EOT_flag, 0 ; Receive an EOT? jne DXR_EOT ; Yes, ACK it and close file mov Pack.NumTry, 0 ; Clear per-packet error counter inc Pack.PktNum ; Advance to next packet number inc Pack.NumPkt ; Bump the number of packets call Show_packets ; Display the number of packets add TFilSz + 2, 128 ; Bump amount transmitted adc TFilSz, 0 ; and high half (done wrong for compatibility) call KbPr ; Show how we're doing ... add Buffer_ptr, 128 ; Advance this ptr add Buffer_count, 128 ; This counter too cmp Buffer_count, SIZE Buffer ; Is the buffer now full? jl DXR_2 ; Still room, get another packet %OUT >> About half way through source file call Write_a_bufferfull ; Dump out the buffer jmp DXR_2 ; Go do another packet DXR_EOT: call Write_a_bufferfull ; Dump out the buffer mov dx, OFFSET Completed ; "XMODEM receive: Completed" jmp Show_status ; Display status and go home Do_XMODEM_receive ENDP ; Do_XMODEM_send -- Worker XMODEM file send routine Do_XMODEM_send PROC call ClrBuf ; Flush any chars already received mov Pack.NumPkt, 0 ; Clear count of transmitted packets mov Pack.PktNum, 1 ; XMODEM starts with packet number 1 mov TFilSz, 0 ; Clear out transfered file size ... mov TFilSz + 2, 0 ; ... low half too (this is backwards) call Wait_for_NAK ; Give receiver time to start up and NAK us jnc DXS_1 ; We are OK ret ; We never got one DXS_1: mov Pack.NumTry, 0 ; Clear this counter mov dx, OFFSET In_progress ; "XMODEM send: In progress" call Show_status ; Display status DXS_2: call Send_a_packet ; Send a packet call Wait_for_ACK ; Get an ACK if we can, fail on timeout or NAK jnc DXS_4 ; Got our ACK, move on ... cmp Abort_flag, 0 ; Aborted? jz DXS_No_abort ; No mov dx, OFFSET Failed ; Say transfer bombed jmp Show_status DXS_No_abort: inc Pack.NumRtr ; Bump global count call Show_retries ; Show the user inc Pack.NumTry ; Bump the count of retries on this packet cmp Pack.NumTry, 10 ; Hit 10 failures yet? jl DXS_2 ; No, keep going mov dx, OFFSET Failed call Show_status mov dx, OFFSET Too_many_retries ; Too many retries jmp Show_error ; Complain on-screen, ret from there DXS_4: add Buffer_ptr, 128 ; Advance this ptr sub Buffer_count, 128 ; Drop this counter mov Pack.NumTry, 0 ; Clear per-packet error counter inc Pack.PktNum ; Advance to next packet number inc Pack.NumPkt ; Bump the number of packets call Show_packets ; Display the number of packets add TFilSz + 2, 128 ; Bump amount transmitted adc TFilSz, 0 ; and high half (done wrong for compatibility) call KbPr ; Show how we're doing ... call PerPr ; Percentage-wise also cmp Buffer_count, 0 ; Is the buffer now empty? jg DXS_2 ; Not empty, do more data jl DXS_Send_EOT ; "Over-empty", Buffer_count wasn't 1024 ; this go round and we used them all up, ; so this must be end-of-file mov ax, WORD PTR File_size ; Pick up half of file size or ax, WORD PTR File_size + 2 ; Merge in other half jz DXS_Send_EOT ; No chars to read, close it out call Read_a_bufferfull ; Load up the buffer jmp DXS_2 ; Go do another packet DXS_Send_EOT: mov Pack.NumTry, 0 ; Clear per-packet retry counter DXS_EOT_loop: mov ah, EOT ; Get an ACK call OutChr ; Send it out nop nop nop call Wait_for_ACK ; Wait for ACK, NAK or timeout jnc DXS_EOT_1 ; Got our ACK, move on ... cmp Abort_flag, 0 ; Aborted? jz DXS_EOT_no_abort mov dx, OFFSET Failed ; Say transfer bombed jmp Show_status DXS_EOT_no_abort: inc Pack.NumRtr ; Bump global count call Show_retries ; Show the user inc Pack.NumTry ; Bump the count of retries on this packet cmp Pack.NumTry, 10 ; Hit 10 failures yet? jl DXS_EOT_loop ; No, keep going mov dx, OFFSET Failed call Show_status mov dx, OFFSET Too_many_retries ; Message jmp Show_error ; Complain on-screen, ret from there DXS_EOT_1: mov dx, OFFSET Completed ; "XMODEM send: Completed" jmp Show_status ; Display status Do_XMODEM_send ENDP Wait_for_NAK PROC mov Abort_flag, 0 ; Clear abort flag mov ah, 2Ch ; Code to Get Time int Dos ; Get it inc cl ; Bump the minutes cmp cl, 59 ; Too many? jbe WFN_doit ; No sub cl, 60 ; Pull down the minutes inc ch ; Bump the hours WFN_doit: mov HrMn, cx ; Save hours and minutes mov ScHn, dx ; Save seconds and hundredths WFN_Loop: cmp Count, 0 ; Any characters to read? jnz WFN_Got_char ; Found one mov dl, 0FFh ; Want input mode mov ah, DConIO ; Code for direct console input w/o echo int Dos ; Get a char if any is there jz WFN_No_keyboard ; User hasn't typed anything cmp al, 3 ; User type ^C? jne WFN_Not_Ctrl_C ; No mov Abort_flag, 0FFh ; Flag the abort jmp Say_Aborted ; Standard exit message for ^C WFN_Not_Ctrl_C: or al, al ; Zero? jne WFN_Beep ; No int Dos ; Eat the second part of the funny char WFN_Beep: call Beep ; User hit wrong key, make a noise WFN_No_keyboard: call Carry_if_expired ; See if time has hit yet jnc WFN_loop ; Not yet mov dx, OFFSET Failed call Show_status mov dx, OFFSET No_initiating_NAK jmp Show_error ; Timeout, complain and return with carry on WFN_Got_char: call PrtChr ; Pick up the character cmp al, ASCII_NAK ; Did we get what we wanted? je WFN_Done ; Yes inc Pack.NumRtr ; Call every noise character a retry call Show_retries ; Display it jmp WFN_Loop WFN_Done: clc ; Found a NAK, clear the error flag ret ; Go home happy Wait_for_NAK ENDP Wait_for_ACK PROC mov Abort_flag, 0 ; Clear abort flag mov ah, 2Ch ; Code to Get Time int Dos ; Get it inc cl ; Bump the minutes cmp cl, 59 ; Too many? jbe WFA_doit ; No sub cl, 60 ; Pull down the minutes inc ch ; Bump the hours WFA_doit: mov HrMn, cx ; Save hours and minutes mov ScHn, dx ; Save seconds and hundredths WFA_Loop: cmp Count, 0 ; Any characters to read? jz WFA_No_char ; None call PrtChr ; Pick up the character cmp al, ACK ; Did we get an ACK? jne WFA_Not_ACK ; No clc ; Clear the error flag ret ; Go home happy WFA_Not_ACK: cmp al, ASCII_NAK ; Did we get a NAK? jne WFA_Not_NAK ; Yes stc ; Flag the error ret ; Go home WFA_Not_NAK: inc Pack.NumRtr ; Call every noise character a retry call Show_retries ; Display it jmp WFA_Loop WFA_No_char: mov ah, DConIO ; Code for direct console input w/o echo mov dl, 0FFh ; Want input mode int Dos ; Get a char if any is there jz WFA_No_keyboard ; User hasn't typed anything cmp al, 3 ; User type ^C? jne WFA_Not_Ctrl_C ; No mov Abort_flag, 0FFh ; Flag the abort jmp Say_Aborted ; Standard exit message for ^C WFA_Not_Ctrl_C: or al, al ; Zero? jne WFA_Beep ; No int Dos ; Eat the second part of the funny char WFA_Beep: call Beep ; User hit wrong key, make a noise WFA_No_keyboard: call Carry_if_expired ; See if time has hit yet jnc WFA_Loop ; Not yet ret ; Return with carry set Wait_for_ACK ENDP ; Routine to set the Carry Flag if the SLEEP timer has expired, clear it if not Carry_if_expired PROC mov ah, 2Ch ; Code to Get Time int Dos ; Get it cmp cx, HrMn ; Is the hour/minute too early? jb CIE_Not_expired ; Yes ja CIE_Expired ; NO! ; Maybe ... cmp dx, ScHn ; How about the seconds? jb CIE_Not_expired ; Too early CIE_Expired: stc ; Set the carry flag ret ; Return CIE_Not_expired: clc ; Clear the carry flag ret ; Return Carry_if_expired ENDP PUBLIC Receive_a_packet ; Receive_a_packet -- Routine to receive and disassemble an XMODEM data packet Receive_a_packet PROC mov Timeout, 10 ; Start with a ten second timeout call Get_char ; Get a char or timeout jc RAP_empty ; Timeout cmp al, SOH ; Proper start? je RAP_1 ; Yes cmp al, EOT ; EOT? jne RAP_err ; No, error mov EOT_flag, 1 ; Turn on this flag jmp SHORT RAP_OK ; Acknowledge it like a packet RAP_1: mov Timeout, 1 ; Switch to a quicker timeout call Get_char ; Get a char or timeout, 1 second jc RAP_empty ; Timeout cmp al, BYTE PTR Pack.PktNum ; Right packet number? jne RAP_err call Get_char ; Get a char or timeout jc RAP_empty ; Timeout not al ; Flip the bits cmp al, BYTE PTR Pack.PktNum ; Still look right? jne RAP_err mov cx, 128 ; Number of chars to get mov di, Buffer_ptr ; Pick up current ptr mov Tally, 0 ; Clear counter RAP_Loop: call Get_char ; Get a char or timeout jc RAP_empty ; Timeout stosb ; Lay down the byte sub ah, ah ; Clear high half add Tally, ax ; Add in the new character loop RAP_Loop ; Do 128 characters call Get_char ; Get a char or timeout jc RAP_empty ; Timeout cmp al, BYTE PTR Tally ; Does the checksum match? jne RAP_err ; No good RAP_OK: mov ah, ACK ; Code to ACK a good packet call OutChr ; Send it out nop nop nop clc ; No errors, right block number and everything ret ; Go home RAP_err: mov cx, 130 ; Max chars to tolerate in a row RAP_err_2: call Get_char ; Try for another char jc RAP_empty ; Line is idle loop RAP_err_2 ; Eat another char RAP_empty: mov ah, ASCII_NAK ; That nasty character call OutChr ; Send it nop nop nop stc ; Flag an error ret ; Go home Receive_a_packet ENDP ; Send_a_packet -- Routine to assemble and send an XMODEM data packet Send_a_packet PROC call ClrBuf ; Flush any chars already received mov ah, SOH ; Start each packet with SOH call OutChr ; Send it along nop nop nop mov ah, BYTE PTR Pack.PktNum ; Copy packet number to ah call OutChr ; Send it along nop nop nop mov ah, 255 ; Funny packet number encoding sub ah, BYTE PTR Pack.PktNum ; One's complement call OutChr ; It too nop nop nop mov cx, 128 ; Number of chars to send mov si, Buffer_ptr ; Pick up current ptr mov Tally, 0 ; Clear counter SAP_Loop: lodsb ; Pick up the byte sub ah, ah ; Clear high half add Tally, ax ; Add in the new character mov ah, al ; Copy char to ah call OutChr ; Send it out nop nop nop loop SAP_Loop ; Do 128 characters mov ah, BYTE PTR Tally ; Use low order half of total as checksum call OutChr ; Send the checksum (remainder) nop nop nop ret ; Go home Send_a_packet ENDP Get_char PROC push cx ; Save cx reg mov Abort_flag, 0 ; Clear abort flag mov ah, 2Ch ; Code to Get Time int Dos ; Get it add dh, Timeout ; Add in the timeout time cmp dh, 59 ; Too many? jle GCH_doit sub dh, 60 ; Pull down the seconds inc cl ; Bump the minutes cmp cl, 59 ; Too many? jle GCH_doit ; No sub cl, 60 ; Pull down the minutes inc ch ; Bump the hours GCH_doit: mov HrMn, cx ; Save hours and minutes mov ScHn, dx ; Save seconds and hundredths GCH_Loop: cmp Count, 0 ; Any characters to read? jnz GCH_Got_char ; Found one mov dl, 0FFh ; Want input mode mov ah, DConIO ; Code for direct console input w/o echo int Dos ; Get a char if any is there jz GCH_No_keyboard ; User hasn't typed anything cmp al, 3 ; User type ^C? jne GCH_Not_Ctrl_C ; No mov Abort_flag, 0FFh ; Flag the abort call Say_Aborted ; Standard exit message for ^C jmp SHORT GCH_done ; Done here GCH_Not_Ctrl_C: or al, al ; Zero? jne GCH_Beep ; No int Dos ; Eat the second part of the funny char GCH_Beep: call Beep ; User hit wrong key, make a noise GCH_No_keyboard: call Carry_if_expired ; See if time has hit yet jnc GCH_loop ; Not yet jmp SHORT GCH_done ; Timeout GCH_Got_char: call PrtChr ; Pick up the character clc ; Got a character, clear error flag GCH_done: pop cx ; Restore cx reg ret ; Go home happy Get_char ENDP ; Jumping to this location is like retskp. It assumes the instruction ; after the call is a jmp addr RSKP PROC NEAR pop bp add bp,3 push bp ; ret RSKP ENDP ; Jumping here is the same as a ret R PROC NEAR ret R ENDP Code ENDS END