Category : Recently Uploaded Files
Archive   : JMOD120M.ZIP
Filename : JMODEM.ASM

 
Output of file : JMODEM.ASM contained in archive : JMOD120M.ZIP
PAGE ,132
TITLE JMODEM.ASM
;
COMMENT *
Created 02-SEP-1988 Richard B. Johnson

Purpose:
File transfer protocol. Use with BBS systems and communications
programs that allow "external protocols".
This program will transfer a single file between the host and an
IBM compatible microcomputer. Under MS-DOS, batch files may be
set up to transfer multiple files using this utility. It will use
data-compression when the data can be compressed and transfers data
uncompressed should compression techniques fail to reduce the block
size.

The block-size is variable, ranging from 512 bytes (plus a 6-byte
overhead) to 8192 bytes (plus a 6-byte overhead). The block-size
is increased up to the limit if there are no transmission errors.
Should errors occur, the block-size is reduced to a minimum of 512
bytes. Since the bytes transferred may be of any length, the file-
size is exactly correct after the file is received.

Data compression is used when its been found that the compression
will reduce the size of the transmitted blocks. Flow control is
also provided to interface with high-speed modems. The flow-control
techniques used are invisible to the user so no special provisions
are necessary. You don't have to add anything on the command line.

Usage:
JMODEM S1 (to send a file via COM1)
JMODEM R1 (to receive a file via COM1)
JMODEM S2 (to send a file via COM2)
JMODEM R2 (to receive a file via COM2)

Known bugs(?)
Since the DOS function 9 is used to print many strings, filenames
that contain a '$' will be incompletely displayed on the screen.
This will be fixed in the second minor revision.

Modification record: Release 04-SEP-1988

05-SEPT-1988 Richard B. Johnson V1.01
Added interrupt on receive routines so that data could be received
while the file is being written.

07-SEPT-1988 Richard B. Johnson V1.02
Added checks for an abort in the flow-control routine. This should
prevent long time-outs if one end aborts.

07-SEPT-1988 Richard B. Johnson V1.02
Fixed bug (typo) in source code that prevented operation on COM2.
Port address 2F8H was mispelled to 02FH which prevented operation

08-SEPT-1988 Richard B. Johnson V1.03
Added a status window with direct writes to the screen to reduce
the time necessary to show the TX/RX status

Changed the local variable [CLOCK] to a word and increased the sync
interval to allow slow users more time to get their files ready for
TX.

10-SEPT-1988 Richard B. Johnson V1.04
Fixed the print string routines so that a '$' can be displayed.
Also streamlined the FLOW control routine so a port need not be
read. Interrupts are now enabled for modem status as well as data
ready. Modem status is read and put in memory so FLOW control need
only check memory.

13-SEPT-1988 Richard B. Johnson V1.05
Added support for COM3 and COM4. Note that there are no IBM standards
denoting where the port addresses are for COM3 and COM4. They have
been put where most communications programs seem to have put them.

16-SEPT-1988 Richard B. Johnson V1.06
Found bug in interrupt service routine where if both the line status
and modem status interrupts happened at the same time the UART would
ignore the lowest-priority interrupt (the modem status) forever. This
could occur when large files were being transferred and flow-control
was active. The program would hang forever waiting for the modem
status to change when, in fact, it had many hours ago. The work-
around was to read the modem status every time anything caused a
UART interrupt. This is one of the known "bugs" of the 8250 UART.
The bug is in the documentation. The UART is doing just what it was
told to do (which isn't what I wanted).

21-SEPT-1988 Richard B. Johnson V1.07
Found a problem with network compatibility. Existing file was opened
in compatibility mode (should be okay). Was not okay when operating
in a multi-tasking environment. Gave a SHARE violation. Found by
Alexander Morris. Changed code to open the file for read/deny nothing.
I think its a "SHARE" bug since compatibility-mode should have worked.
Otherwise, all previous programs written prior to DOS 3.0 that use
files, are "obsolete" and won't work if SHARE is installed.

22-SEPT-1988 Richard B. Johnson V1.08
Added 1/4 second time after the SYNCH routine before sending the first
data block to the receiver of the download. This should reduce the
first-block retry that usually happens on long-distance transfers.

25-SEPT-1988 Richard B. Johnson V1.09
Due to many requests (now we are getting picky), music has been added
to signal success or failure of a file transfer. The computer speaker
plays the "Victory" bugle call when the file-transfer was successful
and it plays "Retreat" when it has failed (aborted). Environment
logical JMODEM=SHUTUP removes the music.

28-SEPT-1988 Richard B. Johnson V1.10
Changed the command line parse routine to remove extra spaces and
tab characters that some were typing (and some BBS systems pass) that
was returning an "invalid file-name" error in some cases. Now its
possible to enter extra spaces and control characters on the command
line:
JMODEM S1 FILENAME.TYP
...Now works!

If a user would create a .BAT file that contained:
JMODEM S1 %1
^----- Extra space(s)
It would sometimes fail depending upon their version of DOS. The
space before the "S" would be passed to the command processor.

30-SEPT-1988 Richard B. Johnson V1.11
Added a lot of code to compensate for the incorrect way some BBS
programs load and execute this program. Instead of using the DOS
function 4BH function of INT 21H, to load/execute a program, these
poorly-written programs load the program as a data file and jump to
it. This is resulting in system crashes that I am getting blamed for.
The new routines save the entire machine state, all registers, and all
memory that may be modified by the execution of the program. The
contents of the segment used (presumably somebody's code) is written
to a file, "VIRTUAL.MEM", then restored when the program exits. You
must set the conditional "BAD_BBS" to true and re-compile to make this
take effect.

Added environment logical JMODEM_SCR=MONO
JMODEM_SCR=COLOR

To placate those who have both color and BW boards installed with
incorrectly-set baseboard switches. JMODEM had no way of knowing where
to write the status block in this case. Now you can force JMODEM to
write the status block wherever you want.

10-OCT-1988 Richard B. Johnson V1.12
Fixed bug that attempted to write a file past the end of a full disk.
MS-DOS does not consider this an error so does not set the CY flag.
Program now compares the write request with actual bytes written. If
these are different, it means the disk is full and the program aborts.

19-OCT-1988 Richard B. Johnson V1.13
Changed method in which JMODEM acquires memory. Since many BBS systems
seem to be messing with DOS's internal memory allocation scheme, it
isn't practical to assume that JMODEM was properly loaded. Therefore,
a DOS call is made to free up all memory allocated to JMODEM. This
will fail and cause an abort if JMODEM was not loaded by the parent
properly. Next a DOS call is made to allocate the memory JMODEM needs
for buffers. This will fail if JMODEM was not loaded above memory that
is currently in use. In both cases, error messages are printed that
disclaim any responsibility.

20-OCT-1988 Richard B. Johnson V1.14
Fixed bug discovered by Troy Fuqua that allowed the PSP to be over-
written by the MAP routine if the environment variables had an odd
number of characters. This was due to the method used to find the size
of the environment. If the double-null terminator occurred on an odd
boundary, the terminator would be missed. Last time I checked, the
environment was filled out with nulls to the next paragraph. In this
case, checking for a WORD of zeros would have found the end of the
environment regardless of alignment. Apparently I missed something
since DOS version 2.0 ???

22-OCT-1988 Richard B. Johnson V1.15
Added environment logical: SET JMODEM_SCR=OFF
To get rid of the status screen for those who use DesQView(tm)
Also moved all initialization code into the data buffer to save
space.

16-NOV-1988 Richard B. Johnson V1.16
Added quick-sync routine. Several revisions made during the beta-
test would result in the first block transmitted being rejected.
This resulted in a long retry interval. The final version, while
still maintaining compatibility back to rev V1.00 was to use both
an ACK and a NAK to resolve which process was the last to be enabled.
This reduced or eliminated the first retry.

08-DEC-1988 Richard B. Johnson V1.17
Removed all references to BIOS service 1AH which had been used to
obtain the system timer-ticks. READING this value during midnight
prevented DOS from rolling over the system date.

23-DEC-1988 Richard B. Johnson V1.18
Changed the telephone number of the PROGRAM EXCHANGE BBS system
shown in the help screen.

31-DEC-1988 Richard B. Johnson V1.19
Changed method of finding the screen-segment for color/mono.
Dale Brownell pointed out that it might be better to check for
the presence of a MONO card by default, rather than a color card.
Since the MONO card port-address does not change. It's only the
new enhanced color cards that have their port-addresses scattered
all over the place. This will probably do a better job of finding
the correct screen-card without having to use the environment to
tell JMODEM what you really have in the machine.

03-JAN-1989 Richard B. Johnson V1.20
To make JMODEM not abort as often on a noisy line:
Changed retry-count to 20.
Changed file-write to trap a similar record number.
Added code to require 3 or more ^Xs on the reverse-channel to abort.
Previously, phone-line noise containing a ^X would cause an abort.

To fix up status window bug:
Added 'fill' routines to over-write previous 'cps' number if the
previous number was very large and the present number is very small.

Previous: 1,206 cps
|
Present: 124 6 cps
|_______ previous character would show.



04-16-89 L.B. Neal V1.20a
Delayed opening file till communications established. In 1.20
the file was opened even if total abort. This left a 0 byte file
in the system and aborted recieving the real filename on next try.

04-17-89 L.B. Neal V1.20b
1.20a was functional but display not real clean. Better now.

04-19-89 L.B. Neal V1.20c/d
Third time was the charm. Now the display is correct!

04-23-89 Ron Pierce/L.B. Neal V1.20e
JMODEM 1.20 saves the file if a transmission is aborted, but does
NOT provide crash-recovery to complete the partial file. This
mod deletes the file if the transfer aborts during a transfer.
Therefore partial files will NOT be left in your system if the
transfer aborts.

04-30-89 Ron Pierce/L.B. Neal V1.20f
OOPS! Seems file being sent was deleted if transmission aborted.
Added a sending flag if sending and flow fails then do NOT
delete the file.

05-01-89 Ron Pierce/L.B.Neal/twc V1.20g
Since file is deleted on abort the file can be opened before
attempt to synchronize on receive. CAUTION.TXT revised.

04-14-90 Ron Pierce/L.B.Neal/twc V1.20h
Set initial block size to be 2048 bytes. Increments up will now
be 1024 bytes with down steps of 512 bytes. The header will
still be 512 bytes and then jump to 2048 bytes. Added test to
make sure max block is 8192 bytes.

04-19-90 L.B.Neal/Ron Pierce/twc V1.20i
1st block really is 1536 Bytes! Corrected the "cps" calculation
found it to be about 5% high now is about 1% slow. Max block size
reduced to 4096 since a larger block does NOT transfer faster and
a hit at 8182 takes a huge time to retransmit.

04-26-90 L.B.Neal/Ron Pierce/twc V1.20j
Added " to abort" message! Corrected cps calc to be very
close to exact. Count is now referenced to 18.2. (Thanks twc!).
Adjusted block # display was out of sync works 1.20 series, but
NOT with 3.xx version always 1024k off.

05-01-90 L.B.Neal/twc/Ron Pierce V1.20k
CPS now reported as average during course of transfer.

08-05-94 l.b.neal V1.20m
Skipped 1.20l.
Trimmed EASY_INT for speed.
Corrected CPS overflow with high rates i.e. 14k+ bps.

Note:
This must be linked as a '.COM' file.
MASM JMODEM;
LINK JMODEM;
EXE2BIN JMODEM.EXE JMODEM.COM
DEL JMODEM.EXE

see makj.bat for new usage with masm, tasm and optasm!
*
;
IF1
%OUT [PASS1]
ELSE
%OUT [PASS2]
ENDIF
;
VERS STRUC
DB 'V1.20m' ; This is the ONLY place to change
@VERS DB ' ' ; the version number....
VERS ENDS

ESCM STRUC ; 1.20j
DB ' Several to abort at block end.' ; User info
@ESCM DB ' '
ESCM ENDS

;
TRUE EQU 0FFFFH ; Define logicals
FALSE EQU NOT TRUE
;
; If your BBS system is well-behaved and follows DOS's rules, set
; the following to FALSE. If its a BAD BBS system that violates
; memory-management rules, set the following to TRUE.
;
BAD_BBS EQU FALSE
;
; Timer equates
;
PERIOD EQU 2400
TIMER EQU 42H ; Timer address
TMODE EQU TIMER + 1 ; Timer mode control port
SC EQU 2 ; Use timer 2
R1 EQU 3 ; Mode to load period
MODE EQU 3 ; Square wave generator
BCD EQU 0 ; Not BCD, use binary
CTLB EQU (SC SHL 6) OR (R1 SHL 4) OR (MODE SHL 1) OR BCD
SPEAKER EQU 61H ; Speaker port
;
; Misc Equates.
;
TIMOUT EQU 90 ; About 5 seconds
MAX EQU 20 ; Max retries
ENV_SEG EQU 2CH ; Where to find the environment seg
MS_DOS EQU 21H ; MS-DOS functions
VIDEO EQU 10H ; Video BIOS routines
CR EQU 0DH ; ASCII
LF EQU 0AH ; ASCII
CODELOC EQU 16384 ; Where to code/decode data
GOOD EQU 4C00H ; Exit to DOS with no errors
BAD EQU 4C01H ; Exit to DOS with errors
HOME EQU CODELOC + 16384 ; Interrupt buffer
;
; Interrupt control equates.
;
INT_NOS EQU 20H ; Non-specific end-of-interrupt
INT_RES EQU 20H ; Interrupt controller reset address
INT_CTL EQU 21H ; Interrupt controller mask address
INT_1 EQU 0CH ; For COM1
INT_2 EQU 0BH ; For COM2
IRQ4 EQU 11101111B ; For COM1
IRQ3 EQU 11110111B ; For COM2
;
; Control byte context
;
NORMAL EQU 00000001B ; Normal data
COMP EQU 00000010B ; Compressed data
EOF EQU 00000100B ; End of file
RETRY EQU 00001000B ; Ask/respond with retry
TIMEOUT EQU 00010000B ; Informational
ABORT EQU 00100000B ; Kill U/D load
SYNC EQU 01000000B ; Hello /ack
ERROR EQU 10000000B ; Undefined error
NONORM EQU NOT NORMAL ; Invert 'NORMAL' bits
;
; UART equates.
;
DR EQU 00000001B ; Data ready
INT_RC EQU 00000001B ; Interrupt on received character
RTS_DTR EQU 00000011B ; DTR/RTS bits
RDAT EQU 00000100B ; Received data available
INT_MS EQU 00001000B ; Interrupt on modem status
TRISTAT EQU 00001000B ; Turn on tristate buffer
CTS EQU 00010000B ; Clear to send
DSR EQU 00100000B ; Data set ready
THRE EQU 00100000B ; TX holding register empty
RLSD EQU 10000000B ; Receive line signal det
CTSDSR EQU CTS OR DSR
;
; Control bytes.
;
ACK EQU 'F' - 64
NAK EQU 'U' - 64
EOT EQU 'D' - 64
CAN EQU 'X' - 64
SYN EQU 'V' - 64
;
; Status window equates.
;
SCRATTR EQU 01110000B ; Screen attribute
ORIGIN EQU 1014 ; Where to put the status box
LINLEN EQU 23 ; Length of a line
SCR_BLK EQU ORIGIN + ( (160 - (LINLEN *2) ) * 3) ; Offset to block count
SCR_LEN EQU SCR_BLK + 160 ; Offset to block length
SCR_BYT EQU SCR_LEN + 160 ; Offset to byte count
SCR_CPS EQU SCR_BYT + 160 ; Offset to chars/sec
SCR_ACK EQU SCR_BLK + 18 ; Where to put ACK/NAK
;
; This heading is printed only if command-line parameters are not
; present.
; You might want to use your own BBS name and number. The assembler
; centers the text in later code. Warning!! Your system might get a lot
; of technical questions about the protocol once you change the heading
; to your BBS name!!! Caveat modulus carborundum.
;
LIN1 STRUC
DB 'J M O D E M File Transfer System '
@LIN1 DB ' '
LIN1 ENDS
;
LIN2 STRUC
DB ' Created 05-AUG-1994 L.B. Neal (Ron Pierce/twc)'
@LIN2 DB ' '
LIN2 ENDS
;
LIN3 STRUC
DB 'THE COMM CENTER (408)737-7245'
@LIN3 DB ' '
LIN3 ENDS
;
; Parameters for Various communications adapter ports.
;
COM1 STRUC
COM1PRT DW 03F8H ; Base port
COM1INT DB INT_1 ; IRQ4
COM1MSK DB IRQ4 ; Controller mask
COM1 ENDS
;
COM2 STRUC
COM2PRT DW 02F8H ; Base port
COM2INT DB INT_2 ; IRQ3
COM2MSK DB IRQ3 ; Controller mask
COM2 ENDS
;
COM3 STRUC
COM3PRT DW 03E8H ; Base port
COM3INT DB INT_1 ; IRQ4
COM3MSK DB IRQ4 ; Controller mask
COM3 ENDS
;
COM4 STRUC
COM4PRT DW 02E8H ; Base port
COM4INT DB INT_2 ; IRQ3
COM4MSK DB IRQ3 ; Controller mask
COM4 ENDS
;
; TX/RX block structure.
;
IBUFFER STRUC
LEN DW ? ; String length
CTRL DB ? ; Control byte
RECN DB ? ; Record number
IDATA DB ? ; Start of data
IBUFFER ENDS
;
; BIOS segment structure.
;
BSEG SEGMENT AT 40H
ORG 63H
ADDR_6845 DW ? ; Address of the video controller
BSEG ENDS
;
; Program code.
;

PSEG SEGMENT PARA PUBLIC 'CODE'
START EQU $
ASSUME CS:PSEG, DS:PSEG, ES:PSEG, SS:NOTHING
ORG 100H
MAIN PROC NEAR
IF BAD_BBS
;
; The following code was added because many users spawn this process as
; an external protocol without following ANY of the DOS conventions.
; This means that I get blamed for their system crashes when, in fact
; the writer of the BBS software is responsible.
;
MOV WORD PTR CS:[DS_SAV],DS ; Save DS whatever
PUSH CS
POP DS ; DS=CS For addressability.
MOV WORD PTR [AX_SAV],AX ; Save AX register!
MOV WORD PTR [BX_SAV],BX ; Save BX register!
MOV WORD PTR [CX_SAV],CX ; Save CX register!
MOV WORD PTR [DX_SAV],DX ; Save DX register!
MOV WORD PTR [SI_SAV],SI ; Save SI register!
MOV WORD PTR [DI_SAV],DI ; Save DI register!
MOV WORD PTR [BP_SAV],BP ; Save BP register!
MOV WORD PTR [ES_SAV],ES ; Save ES register!
CLI
MOV WORD PTR [SS_SAV],SS ; Save SS register!
MOV WORD PTR [SP_SAV],SP ; Save SP register!
STI
;
MOV AX,CS ; Get code segment
MOV ES,AX ; Into extra segment
CLI
MOV SS,AX ; Into stack segment
MOV SP,0FFFEH ; Locate stack pointer
STI
;
; Write all memory that will be modified to a disk file VIRTUAL.MEM.
; This doesn't guarantee that there will be no crash because the BBS
; software-writer may have just loaded this program right on top of
; his own program and I can't save is butt for that!
;
CALL SET_SEG ; Set up BUFFER data segment
JNC SO_FAR ; No error yet
MOV AX,BAD ; DOS exit with error
INT MS_DOS ; Exit
SO_FAR: MOV AX,3C00H ; Create Virtual memory file
XOR CX,CX ; Normal file
MOV DX,OFFSET VMEM ; File name
INT MS_DOS ; Create the file
JNC MEM0 ; Created okay
MOV SI,OFFSET PRP16 ; Point to 'can't create"
CALL PROMPT
MOV SI,OFFSET VMEM ; Point to file name
CALL PROMPT ; Print to screen
MOV AX,BAD ; DOS exit with error
INT MS_DOS ; Exit
;
MEM0: PUSH DS ; Save segment
MOV DS,WORD PTR [BUF_SEG] ; Set up segment for save
MOV BX,AX ; File handle
MOV CX,0FFFFH ; Bytes to save
XOR DX,DX ; Offset zero
MOV AX,4000H ; Write to file
INT MS_DOS ; Write a segment out to file
POP DS ; Restore segment
JNC MEM1 ; Good write
MOV SI,OFFSET PRP17 ; Point to 'can't write"
CALL PROMPT
MOV SI,OFFSET VMEM ; Point to file name
CALL PROMPT ; Print to screen
MOV AX,BAD ; DOS exit with error
INT MS_DOS ; Exit
;
MEM1: MOV AX,3E00H ; Close file function
INT MS_DOS ; Close the file
JNC MEM2 ; Good close
MOV SI,OFFSET PRP18 ; Point to 'can't close'
CALL PROMPT
MOV SI,OFFSET VMEM ; Point to file name
CALL PROMPT ; Print to screen
MOV AX,BAD ; DOS exit with error
INT MS_DOS ; Exit
;
MEM2: CMP WORD PTR CS:[6],OFFSET TOP ; Check memory in the segment
JNC MEMOK
;
; User just loaded this program over his CODE. This will probably crash
; the system, but I must exit after printing a nasty message.
;
MOV SI,OFFSET PRP14 ; Point to "Can't execute"
CALL PROMPT ; Print to screen
JMP EXIT_BAD
ENDIF
;
; This is the normal entry for a '.COM' program.
;
MEMOK: MOV AX,CS ; Segment setup should have
MOV DS,AX ; been done by DOS!
MOV ES,AX
CLI
MOV SS,AX
MOV SP,OFFSET STACKP ; Keep stack pointer safe.
STI
CLD ; Should have been done by DOS
MOV DI,OFFSET KILL ; First variable to zero out
MOV CX,KLEN ; Length of the variable list
XOR AL,AL ; Get a zero
REP STOSB ; Set all to zero
;
CALL CHK_ENV ; Check environment strings
CALL FIND_VIDEO ; Find the video segment
IF (NOT BAD_BBS)
CALL SET_SEG ; Set up data segment
JNC MEM_OK ; Memory okay
JMP EXIT_BAD ; Memory allocation error
ENDIF
MEM_OK: CALL PARSE ; Parse the command line
JNC WASOK ; Commands okay
CALL SENDX ; Tell other end to quit
MOV SI,OFFSET PRP3 ; Point to 'usage' prompt
CALL PROMPT ; Print to screen
JMP EXIT_BAD
;
WASOK: MOV AX,WORD PTR [ABS_TIM] ; Get current time
MOV WORD PTR [TIM_L],AX ; Save new LOW count
CMP BYTE PTR [RXTX],'S' ; Do we want to send?
JZ SEND ; Yes
JMP RECV ; Else receive
MAIN ENDP

;
; Send file contents to remote.
;
SEND PROC NEAR
MOV AL,1 ; Set AL to 1 1.20f
MOV SENDING,AL ; Set sending flag 1.20f
MOV WORD PTR [REC_NOL],0 ; Start with # 0
; MOV WORD PTR [SND_BLK],0 ; Strt 0 read bumps to 1 1.20k
MOV BYTE PTR [TRIES],0 ; Conditional for Read
CALL SET_INT ; Set up interrupts
MOV SI,OFFSET PRP7 ; Point to 'Sending'
CALL PROMPT ; Print to screen
CALL OPEN_R ; Open for read
JC EXITF ; Can't open the file
MOV SI,OFFSET FNAME ; Point to filename
CALL PROMPT ; Print to screen
MOV SI,OFFSET CRLF ; End of the line
CALL PROMPT ; Print to screen
CALL TXSYNCH ; Synchronize
JC EXITF ; Abort
CALL SAV_SCR ; Save the screen context
CALL WRT_SCR ; Write new screen status window
JMP SHORT BYPASS ; Bypass the status screen 1st time
SEND0: CALL SHOW ; Show what we are doing
BYPASS: CALL READ ; Read the file
MOV BYTE PTR [TRIES],MAX ; Max attempts to send blocks
MOV AX,WORD PTR [ABS_TIM] ; Get current time 1.20i
MOV WORD PTR [TIM_L],AX ; Save new LOW count 1.20i
SEND1: CALL SDATA ; Send the data
;
SEND2: CALL GET_ACK ; Get host ackknowlege
JC EXITF ; Fatal exit
JNZ CHKRES ; Was not good
MOV AL,' ' ; Clear any error message
CALL SHO_RES ; on the screen
CMP BYTE PTR [FEND],0 ; Check for file end
JZ SEND0 ; Not end of the file yet
;
CALL RES_SCR ; Restore the screen.
MOV SI,OFFSET PRP10 ; Point to the prompt
CALL PROMPT
CALL CLOSE ; Close the file
MOV SI,OFFSET TUNE1 ; Point to 'victory'
CALL PLAY ; Play the tune
CALL RES_INT ; Restore vectors
JMP EXIT_GOOD
;
CHKRES: CMP AL,CAN ; Wanted to cancel?
JZ EXITF ; Yes
CMP AL,NAK ; Is it a NAK?
JNZ SEND2 ; No, must be garbage, try again.
MOV AL,'*' ; Was a NAK.
CALL SHO_RES ; Show error response
DEC BYTE PTR [TRIES] ; Bump retries
JNZ SEND1 ; Continue
;
EXITF: MOV SP,OFFSET STACKP ; Level stack
CALL SENDX ; Tell other end to quit
CALL RES_SCR ; Restore screen state
MOV SI,OFFSET PRP9 ; Point to 'aborted'
CALL PROMPT ; Print to screen
CALL CLOSE ; Close the file
MOV SI,OFFSET TUNE2 ; Point to 'retreat'
CALL PLAY ; Play the tune
CALL RES_INT ; Restore interrupts
JMP EXIT_BAD
SEND ENDP
;
; Receive a file from remote
;
RECV PROC NEAR
MOV AL,0 ; Set AL to 0 1.20f
MOV SENDING,AL ; Set sending flag to off 1.20f
MOV WORD PTR [REC_NOL],1 ; Start with record number 1
MOV WORD PTR [SND_BLK],1 ; Start with count 1 1.20k
CALL SET_INT ; Set up interrupts
MOV SI,OFFSET PRP8 ; Point to 'receiving'
CALL PROMPT ; Print to screen
CALL OPEN_W ; Open for write - back in 1.20g
JC CANCEL ; Bad open - back in 1.20g
MOV SI,OFFSET FNAME ; Point to filename - back in 1.20c
CALL PROMPT ; Print to screen - back in 1.20c
MOV SI,OFFSET CRLF
CALL PROMPT
CALL RXSYNCH ; Establish communications
JC CANCEL ; No good, quit.
CALL SAV_SCR ; Save screen context
CALL WRT_SCR ; Write box on screen
MOV BYTE PTR [TRIES],MAX ; Max re-tries
JMP SHORT NONAK ; Bypass the initial NAK
RECV0: MOV AL,'*'
CALL SHO_RES ; Show response
MOV AL,NAK ; Get a NAK
RECV1: CALL RESP ; Send to remote
NONAK: CALL RDATA ; Receive data
JNC RECV3 ; Good block
RECV2: DEC BYTE PTR [TRIES] ; Bump retries
JNZ RECV0 ; Send a NAK and try again
;
CANCEL: MOV SP,OFFSET STACKP ; Level stack
CALL SENDX ; Tell remote to quit
CALL RES_SCR ; Restore screen context
MOV AX,4100H ; Delete file function ; 1.20e
MOV DX,OFFSET FNAME ; Delete aborted file ; 1.20e
INT MS_DOS ; Do it, ignore any errors ; 1.20e
MOV SI,OFFSET PRP9 ; Point to 'aborted'
CALL PROMPT ; Print to screen
CALL CLOSE ; Close the file
MOV SI,OFFSET TUNE2 ; Point to 'retreat'
CALL PLAY ; Play the tune

CALL RES_INT ; Restore interrupts
JMP EXIT_BAD

;
RECV3: CALL CRC ; Check the CRC
JNZ RECV2 ; Bad CRC
PUSH DS ; Save segment
MOV DS,WORD PTR [BUF_SEG] ; Get buffer segment
MOV AL,BYTE PTR DS:[CTRL] ; Get control byte
POP DS ; Restore segment
TEST AL,EOF ; Check for end-of-file
JNZ END_F ; Is end of file
TEST AL,ABORT ; Check for abort
JNZ CANCEL ; It was an abort
MOV AL,ACK ; Get an ACK
CALL RESP ; Send to remote
MOV AL,' '
CALL SHO_RES ; Show good response
CALL WRITE ; Write the file
JNZ CANCEL ; Write past end of disk!
MOV BYTE PTR [TRIES],MAX ; Reset the retry count
JMP SHORT NONAK ; Get more
;
END_F: MOV AL,' '
CALL SHO_RES ; Show response
CALL WRITE ; Write the last record
JC CANCEL ; Write past end of disk!
MOV SP,OFFSET STACKP ; Level stack
MOV CX,5 ; Number of ACKs
ENDACK: MOV AL,ACK ; Get an ack
CALL RESP ; Send to remote
LOOP ENDACK ; Send a few.
CALL RES_SCR ; Restore screen context
MOV SI,OFFSET PRP10 ; Point to 'good'
CALL PROMPT ; Print to screen
CALL CLOSE ; Close the file
MOV SI,OFFSET TUNE1 ; Point to 'victory'
CALL PLAY ; Play the tune
CALL RES_INT ; Restore vectors
JMP EXIT_GOOD
RECV ENDP
;
; Send data. Data is in the BUFFER.IDATA
;
SDATA PROC NEAR
MOV DX,WORD PTR [PORT] ; Get port address
MOV AL,BYTE PTR [REC_NOL] ; Get low byte of rec to xmit.
PUSH DS ; Save segment
MOV DS,WORD PTR [BUF_SEG] ; Pick up data buffer
MOV CX,WORD PTR DS:[LEN] ; Get the string length
MOV BYTE PTR DS:[RECN],AL ; Record number
CALL CRC ; Calculate the CRC
XOR SI,SI ; Offset Zero
SDATA1: ADD DX,5 ; Offset to status
SDATA2: IN AL,DX ; Get UART status
TEST AL,THRE ; Test holding register ready
JNZ FLOWG ; Go for it! 1.20j
JMP SHORT SDATA2 ; Wait 1.20j
; CALL FLOW ; Flow control now inline 1.20i
FLOWG: PUSH AX ; Save byte
PUSH CX ; Save any count
PUSH DS ; Save data segment
PUSH CS
POP DS ; DS=CS
CMP BYTE PTR [ENABLE],0 ; Original mask
JZ CONT1A ; Ignore flow control
FLOW0A: CMP BYTE PTR [KILL],0 ; Check for an abort
JNZ BYEBY ; Wanted to abort
MOV AL,BYTE PTR [MOD_STA] ; Get modem status
TEST AL,RLSD ; Check for carrier
JZ CBADA ; Carrier is bad 1.20i
JMP SHORT CAROKA ; Carrier still ok 1.20i
CBADA: CMP BYTE PTR [WASC],0 ; See if a carrier when we started
JZ CAROKA ; No, ignore status
MOV SI,OFFSET PRP11 ; Point to carrier failed
CALL PROMPT
BYEBY: MOV SP,OFFSET STACKP ; Level stack
CALL CLOSE ; Close the file
CMP SENDING,1 ; Are we sending 1.20f
JE GOWAY ; Yes! Don't delete the file 1.20f
MOV AX,4100H ; Delete file function ; 1.20e
MOV DX,OFFSET FNAME ; Delete aborted file ; 1.20e
INT MS_DOS ; Do it, ignore any errors ; 1.20e
GOWAY: CALL SENDX ; Tell other end to quit. 1.20f
CALL RES_SCR ; Restore screen
MOV SI,OFFSET PRP9 ; Point to aborted
CALL PROMPT
MOV SI,OFFSET TUNE2 ; Point to 'retreat'
CALL PLAY ; Play the tune
CALL RES_INT ; Restore interrupts
JMP EXIT_BAD
;
CAROKA: AND AL,CTSDSR ; Mask all but DSR/CTS
CMP AL,BYTE PTR [ENABLE] ; Original mask
JNZ FLOW0A ; Wait till okay was JZ 1.20i
; JMP SHORT FLOW0 ; Wait until Okay 1.20i
CONT0A: CALL CHK_STA ; Check for any incoming bytes
JZ CONT2A ; Buffer's clear
CALL CLR_BUF ; Clear the buffer
PUSH DS ; Save segment
MOV DS,WORD PTR CS:[BUF_SEG] ; Pick up segment
MOV AL,BYTE PTR DS:[HOME] ; Get byte in the buffer
POP DS ; Restore segment
CMP AL,CAN ; Want to cancel?
JNZ CONT1A ; No
DEC BYTE PTR [ABRT_X] ; Bump the abort-counter
JZ BYEBY ; Abort if zero
JMP SHORT CONT2A ; Continue
CONT1A: MOV BYTE PTR [ABRT_X],3 ; Restore abort-counter
;
CONT2A: POP DS ; Restore segment
POP CX ; Restore any count
POP AX ; Restore byte
; End of what was CALL FLOW
SUB DX,5 ; To base port
LODSB ; Get memory byte
OUT DX,AL ; Output to UART
DEC CX ; CX = CX - 1 1.20i
CMP CX,0 ; If CX zer0 we are done 1.20i
JZ ISDONE ; Chars finished 1.20i
JMP SDATA1 ; Continue for all bytes 1.20i
ISDONE: POP DS ; Restore data segment
RET ; Exit
SDATA ENDP
;
; Send a single byte in AL to the modem port.
;
RESP PROC NEAR
CALL FLOW ; Flow control
FORCE PROC NEAR ; Force output, no flow control
PUSH DX ; Save
PUSH AX ; Save byte
MOV DX,WORD PTR CS:[PORT] ; Get modem port
ADD DX,5 ; Offset to status
RESP0: IN AL,DX ; Get the status
AND AL,THRE ; Test holding register ready
JZ RESP0 ; Wait
SUB DX,5 ; To base port
POP AX ; Restore byte to send
OUT DX,AL ; Output to UART
POP DX ; Restore
RET
FORCE ENDP
RESP ENDP
;
;
; Easy interrupt happens each time a byte is assembled in the UART.
; The byte is stacked in the interrupt buffer and the time-out clock
; is reset. An appropriate reset is also sent to the interrupt
; controller.
;
EASY_INT PROC FAR
PUSH AX ; Save registers used.
PUSH BX
PUSH DX
PUSH DS
PUSH CS
POP DS ; DS=CS
STI ; 1.20m added for safety!! lbn
MOV DX,WORD PTR [PORT] ; Get port address
; ------------------------------- ; 1.20m this not really needed
; ADD DX,6 ; Offset to modem status register
; IN AL,DX ; Get the modem status
; MOV BYTE PTR [MOD_STA],AL ; Save in CS
; DEC DX ; Back to line status register
; IN AL,DX ; Get line status
; AND AL,DR ; See if data is ready
; JZ INT_EX ; No data available yet
; SUB DX,5 ; Back to base port
; --------------------------------------------
IN AL,DX ; Get the byte
MOV BX,WORD PTR [WPOINT] ; Pick up write pointer
INC WORD PTR [WPOINT] ; Ready next
INC WORD PTR [BYTE_CNT] ; Count of bytes in the buffer
MOV WORD PTR [CLOCK],TIMOUT ; Set timeout to max again
MOV DS,WORD PTR [BUF_SEG] ; Pick up buffer segment
MOV BYTE PTR [BX],AL ; Put byte in the buffer
INT_EX: MOV AL,INT_NOS ; Non-specific end-of-interrupt
OUT INT_RES,AL ; Reset the controller
;
POP DS ; Restore registers used
POP DX
POP BX
POP AX
IRET
EASY_INT ENDP
;
; Return byte count in interrupt buffer in CX.
;
CHK_STA PROC NEAR
CLI ; No interrupts
MOV CX,WORD PTR CS:[BYTE_CNT] ; Get byte count
OR CX,CX ; Test for zero
JZ NO_BY ; No bytes 1.20j
JMP SHORT CHK_EX ; Got some bytes 1.20j
NO_BY: ; 1.20j
CLR_BUF PROC NEAR
CLI ; No interrupts
MOV WORD PTR CS:[WPOINT],HOME ; Set pointer to top of buffer
MOV WORD PTR CS:[BYTE_CNT],0 ; Zero the byte-count
CHK_EX: STI ; Allow interrupts
RET
CLR_BUF ENDP
CHK_STA ENDP
;
; Get a single byte from the remote. Check to see if its an ACK.
;
GET_ACK PROC NEAR
MOV WORD PTR [CLOCK],255 ; Max time to wait
GET_INP PROC NEAR
CMP BYTE PTR [KILL],0FFH ; Do we want to abort?
JNZ NOWAY ; No
MOV AL,CAN ; Yes, flag with a ^X
OR AL,AL ; Set non-zero, no CY
RET ; And return to caller
;
NOWAY: PUSH CX ; Save count
GET0: CMP WORD PTR [CLOCK],0 ; Check for timeout
JZ GET1
CALL CHK_STA ; Check status
JZ GET0 ; No bytes available
;
CALL CLR_BUF ; Clear any garbage.
PUSH DS
MOV DS,WORD PTR [BUF_SEG] ; Pick up buffer segment
MOV AL,BYTE PTR DS:[HOME] ; Pick up byte at bottom
POP DS
CMP AL,ACK ; Check for an ACK
CLC ; Clear carry
JMP SHORT GET2 ; Exit
;
GET1: STC ; Set carry for error
GET2: POP CX ; Restore count
RET
GET_INP ENDP
GET_ACK ENDP
;
; Receive data string from remote. First word is the string length.
;
RDATA PROC NEAR
MOV WORD PTR [CLOCK],255 ; Max time to wait
;
; Wait for the first two bytes so we know how many bytes are supposed
; to be received.
;
PUSH AX ; Protect AX 1.20i
MOV AX,WORD PTR [ABS_TIM] ; Get current time 1.20i
MOV WORD PTR [TIM_L],AX ; Save new LOW count 1.20i
POP AX ; Protect AX 1.20i
RDATA0: CMP WORD PTR [CLOCK],0 ; Check for timeout
JZ RDATA2 ; Timed out
CALL CHK_STA ; Check status
CMP CX,2 ; Check for two bytes received
JC RDATA0 ; Not here yet
PUSH DS
MOV DS,WORD PTR [BUF_SEG] ; Pick up buffer segment
MOV AX,WORD PTR DS:[HOME] ; Pick up word at bottom
POP DS
;
; AX now contains the string length. Wait until the entire string is
; received by checking the status.
;
MOV WORD PTR [BYTES],AX ; Show block size 1.20j

RDATA1: CMP WORD PTR [CLOCK],0 ; Check for timeout
JZ RDATA2 ; Timed out
CALL CHK_STA ; Check status
CMP CX,AX ; Check byte count
JC RDATA1 ; Not here yet
;
; Got all the bytes we need, now transfer them.
;
PUSH DS
PUSH ES
MOV AX,WORD PTR [BUF_SEG] ; Pick up segment
MOV ES,AX
MOV DS,AX ; Fix up segments
MOV SI,HOME ; Where data is
MOV DI,LEN ; Where to put it
REP MOVSB ; transfer it
POP ES ; Restore registers
POP DS
CALL CLR_BUF ; Clear the buffer
CLC ; Show no errors
RET
;
; Timeout error return
;
RDATA2: STC
RET
RDATA ENDP
;
; Attempt to synchronize TX/RX
;
RXSYNCH PROC NEAR
MOV SI,OFFSET PRP13 ; Pick up prompt
CALL PROMPT ; Print to console
CALL CLR_BUF ; Clear any garbage

HOLD: MOV WORD PTR [CLOCK],36 ; Max time to wait
CALL GET_INP ; Wait for input
JC EMPTY ; Nothing there
CMP AL,ACK ; Was it an ACK?
JZ CLEAR ; Yes
CMP AL,NAK ; Was it a NAK?
JNZ RROBIN ; No
MOV AL,ACK ; Show we got it
CALL RESP ; Tell them we got it
JMP SHORT CLEAR ; And exit
;
EMPTY: MOV AL,NAK ; Get an ACK
CALL RESP ; Send to remote
JMP SHORT HOLD
;
RROBIN: MOV WORD PTR [CLOCK],512 ; Max time to wait
MOV CX,64 ; Bytes to TX/RX
; Bytes to get
RXSYN0: MOV AL,SYN ; Send a SYN byte

CALL RESP ; Send to remote
CALL GET_INP ; Get a byte
JC RXSYN4 ; Exit fatal timeout
CMP AL,CAN ; Was it a ^X?
JZ RXSYN4 ; Yes, Exit fatal
CMP AL,SYN ; Get SYNC character?
JNZ RXSYN0 ; No, wait some more
RXSYN2: LOOP RXSYN0 ; Continue for all bytes
CALL RESP ; Send to remote
FLUSH: CALL CHK_STA ; Check for a character ready
JZ CLEAR ; None ready
CALL GET_INP ; Flush input buffer
JMP SHORT FLUSH ; Wait until there's nothing
CLEAR: MOV SI,OFFSET PRP12 ; Pick up prompt
CALL PROMPT ; Print to console
CLC ; Show ready
RET
;
RXSYN4: CALL CLR_BUF ; Get any incoming bytes
STC ; Abort
RET
RXSYNCH ENDP
;
; TX synch calls sync routine first, then waits 1/3 second before
; returning to the transmitter routine.
;
TXSYNCH PROC NEAR
CALL RXSYNCH ; Handshake first
JC TXQ ; Handshake lost
MOV WORD PTR [CLOCK],6 ; Wait 6/18.5 's of a second
TXW: CMP WORD PTR [CLOCK],0 ; Check for zero
JNZ TXW
CALL CLR_BUF ; Clear any garbage.
TXQ: RET
TXSYNCH ENDP
;
; Show ACK/NAK with a '*' after the block
;
SHO_RES PROC NEAR
CMP BYTE PTR [NOSCR],0 ; Do we have a status screen?
JZ SCR3 ; Yes
RET ; No.
SCR3: PUSH DS ; Save screen segment
MOV DS,WORD PTR [SCR_SEG] ; Pick up screen segment
MOV AH,SCRATTR ; Normal attribute
MOV WORD PTR DS:[SCR_ACK],AX ; Put on the screen
POP DS
RET
SHO_RES ENDP
;
; Print string to console until a NULL. String location in SI
;
PROMPT PROC NEAR
LODSB ; Get byte
OR AL,AL ; Check for a null
JZ PREXT ; End of the string
MOV AH,14 ; 'Dumb' terminal mode
MOV BX,0007H ; Normal attribute
INT VIDEO ; Print character
JMP SHORT PROMPT ; Continue
PREXT: RET
PROMPT ENDP
;
; Flow control and carrier detect.
;
FLOW PROC NEAR
PUSH AX ; Save byte
PUSH CX ; Save any count
PUSH DS ; Save data segment
PUSH CS
POP DS ; DS=CS
CMP BYTE PTR [ENABLE],0 ; Original mask
JZ CONT1 ; Ignore flow control
FLOW0: CMP BYTE PTR [KILL],0 ; Check for an abort
JNZ BYEBYE ; Wanted to abort
MOV AL,BYTE PTR [MOD_STA] ; Get modem status
TEST AL,RLSD ; Check for carrier
JZ CBAD ; Carrier is bad 1.20i
JMP SHORT CAROK ; Carrier still ok 1.20i
CBAD: CMP BYTE PTR [WASC],0 ; See if a carrier when we started
JZ CAROK ; No, ignore status
MOV SI,OFFSET PRP11 ; Point to carrier failed
CALL PROMPT
BYEBYE: MOV SP,OFFSET STACKP ; Level stack
CALL CLOSE ; Close the file
CMP SENDING,1 ; Are we sending 1.20f
JE GOAWAY ; Yes! Don't delete the file 1.20f
MOV AX,4100H ; Delete file function ; 1.20e
MOV DX,OFFSET FNAME ; Delete aborted file ; 1.20e
INT MS_DOS ; Do it, ignore any errors ; 1.20e
GOAWAY: CALL SENDX ; Tell other end to quit. 1.20f
CALL RES_SCR ; Restore screen
MOV SI,OFFSET PRP9 ; Point to aborted
CALL PROMPT
MOV SI,OFFSET TUNE2 ; Point to 'retreat'
CALL PLAY ; Play the tune
CALL RES_INT ; Restore interrupts
JMP EXIT_BAD
;
CAROK: AND AL,CTSDSR ; Mask all but DSR/CTS
CMP AL,BYTE PTR [ENABLE] ; Original mask
JNZ FLOW0 ; Wait till okay was JZ 1.20i
; JMP SHORT FLOW0 ; Wait until Okay 1.20i
CONT0: CALL CHK_STA ; Check for any incoming bytes
JZ CONT2 ; Buffer's clear
CALL CLR_BUF ; Clear the buffer
PUSH DS ; Save segment
MOV DS,WORD PTR CS:[BUF_SEG] ; Pick up segment
MOV AL,BYTE PTR DS:[HOME] ; Get byte in the buffer
POP DS ; Restore segment
CMP AL,CAN ; Want to cancel?
JNZ CONT1 ; No
DEC BYTE PTR [ABRT_X] ; Bump the abort-counter
JZ BYEBYE ; Abort if zero
JMP SHORT CONT2 ; Continue
CONT1: MOV BYTE PTR [ABRT_X],3 ; Restore abort-counter
;
CONT2: POP DS ; Restore segment
POP CX ; Restore any count
POP AX ; Restore byte
RET
FLOW ENDP
;
; Make/check CRC word. String is in BUFFER
;
CRC PROC NEAR
PUSH CX
PUSH SI
PUSH DS
MOV DS,WORD PTR CS:[BUF_SEG] ; Pick up buffer segment
MOV CX,WORD PTR DS:[LEN] ; Get string length
SUB CX,2 ; Don't CRC the CRC
XOR AH,AH ; Always zero
XOR BX,BX ; Start with zero
XOR SI,SI ; Buffer offset zero
CRC0: PUSH CX ; Save count
LODSB ; Get byte
ADD BX,AX ; Total so far
AND CL,00000111B ; Rotate max 7 bits
ROL BX,CL ; Multiply
POP CX ; Restore count
LOOP CRC0 ; Continue
CMP WORD PTR [SI],BX ; For checking
MOV WORD PTR [SI],BX ; Insert CRC
POP DS
POP SI
POP CX
RET
CRC ENDP
;
; Local extension of the clock vector.
;
LCL_CLK PROC FAR
STI ; Allow interrupts
PUSH DS ; Save data segment
PUSH CS
POP DS ; DS=CS
PUSH AX ; Save registers used
MOV AL,INT_NOS ; Non-specific end-of-interrupt
OUT INT_RES,AL ; Out to the controller
POP AX ; Restore registers used
INC WORD PTR [ABS_TIM] ; Local timer, subst. for 1AH
CMP WORD PTR [CLOCK],0 ; See if already zero
JZ ZERO ; Yes, don't change
DEC WORD PTR [CLOCK] ; Bump the timer
ZERO: CMP BYTE PTR [METRO],0 ; Check the metronone
JZ CLK_EX ; Its already zero
DEC BYTE PTR [METRO] ; Bump the ticker
CLK_EX: POP DS ; Restore data segment
JMP DWORD PTR CS:[OLD_CLK] ; Continue
LCL_CLK ENDP
;
IF BAD_BBS
;
; Common exit routine to restore machine state.
;
EXIT_GOOD PROC NEAR
EXIT_BAD PROC NEAR
CALL RES_MEM ; Restore memory content
MOV AX,WORD PTR [AX_SAV] ; Restore AX register
MOV BX,WORD PTR [BX_SAV] ; Restore BX register
MOV CX,WORD PTR [CX_SAV] ; Restore CX register
MOV DX,WORD PTR [DX_SAV] ; Restore DX register
MOV SI,WORD PTR [SI_SAV] ; Restore SI register
MOV DI,WORD PTR [DI_SAV] ; Restore DI register
MOV BP,WORD PTR [BP_SAV] ; Restore BP register
MOV ES,WORD PTR [ES_SAV] ; Restore ES register
CLI
MOV SS,WORD PTR [SS_SAV] ; Restore SS register
MOV SP,WORD PTR [SP_SAV] ; Restore SP register
STI
MOV DS,WORD PTR [DS_SAV] ; Restore DS Last.
RET ; Will make jump to CS:00
EXIT_BAD ENDP
EXIT_GOOD ENDP
;
; Restore the content of any memory used.
;
RES_MEM PROC NEAR
MOV AX,3D20H ; Open for read/deny nothing
XOR CX,CX ; Normal file
MOV DX,OFFSET VMEM ; File name
INT MS_DOS
JNC MEM3 ; Good open
MOV SI,OFFSET PRP19 ; Point to 'can't open'
CALL PROMPT
MOV SI,OFFSET VMEM ; Point to file name
CALL PROMPT ; Print to screen
JMP STATE0
;
MEM3: MOV BX,AX ; File handle
MOV AX,3F00H ; Read file
MOV CX,0FFFFH ; Bytes to read
XOR DX,DX ; Offset zero
PUSH DS ; Save segment
MOV DS,WORD PTR [BUF_SEG] ; Get memory segment to restore
INT MS_DOS ; Read file into there
POP DS ; Restore segment
JNC MEM4
MOV SI,OFFSET PRP20 ; Point to 'can't write'
CALL PROMPT
MOV SI,OFFSET VMEM ; Point to file name
CALL PROMPT ; Print to screen
JMP SHORT STATE0
;
MEM4: MOV AX,3E00H ; Close file
INT MS_DOS ; Ask DOS to do it.
JNC MEM5 ; Good close
MOV SI,OFFSET PRP18 ; Point to 'can't close'
CALL PROMPT
MOV SI,OFFSET VMEM ; Point to file name
CALL PROMPT ; Print to screen
JMP SHORT STATE0
;
MEM5: MOV DX,OFFSET VMEM ; Point to the file name
MOV AX,4100H ; Delete the file
INT MS_DOS
JNC STATE0
;
MOV SI,OFFSET PRP21 ; Point to 'can't delete'
CALL PROMPT
MOV SI,OFFSET VMEM ; Point to file name
CALL PROMPT ; Print to screen
;
STATE0: RET
RES_MEM ENDP
;
ELSE
;
; Just normal DOS exit with "ERRORLEVEL"
;
EXIT_BAD PROC NEAR
MOV AX,BAD
INT MS_DOS
EXIT_BAD ENDP
;
EXIT_GOOD PROC NEAR
MOV AX,GOOD
INT MS_DOS
EXIT_GOOD ENDP
ENDIF
;
; Restore interrupts and vectors to the condition we had when we
; received control.
;
RES_INT PROC NEAR
CMP BYTE PTR [INT_FLG],0 ; See if we have hot Interrupts
JNZ HOT_IN ; Yes
RET ; No, don't "restore" them
HOT_IN: CALL CLR_BUF ; Clear receive buffer.
MOV BYTE PTR [INT_FLG],0 ; Show we are doing it now
MOV DX,WORD PTR [PORT] ; Get port address
ADD DX,1 ; Interrupt enable register
MOV AL,BYTE PTR [STATUS] ; Get old UART status
OUT DX,AL ; Restore it
;
MOV AL,BYTE PTR [OLD_MASK] ; Get old Interrupt/ctl mask
OUT INT_CTL,AL ; Restore it
;
PUSH DS ; Save segment
MOV AL,BYTE PTR [COM_INT] ; Vector to restore
MOV AH,25H ; Set interrupt function
LDS DX,CS:[OLD_UART] ; Pick up old vector
INT MS_DOS ; Restore it
;
MOV AL,1CH ; Vector to restore
MOV AH,25H ; Set interrupt function
LDS DX,CS:[OLD_CLK] ; Pick up old vector
INT MS_DOS ; Restore it
;
MOV AH,25H ; Set interrupt function
MOV AL,1BH ; Control break vector
LDS DX,CS:[OLD_BRK] ; Pick up old vector
INT MS_DOS ; Restore it
;
MOV AH,25H ; Set interrupt function
MOV AL,23H ; Control C vector
LDS DX,CS:[OLD_CTC] ; Pick up old vector
INT MS_DOS ; Restore it
POP DS
MOV AL,INT_NOS ; Non-specific end-of-interrupt
OUT INT_RES,AL ; Reset controller
RET
RES_INT ENDP
;
; Open a file for reading.
;
OPEN_R PROC NEAR
MOV AX,3D20H ; Open for read/deny nothing
XOR CX,CX ; Normal file
MOV DX,OFFSET FNAME ; File name
INT MS_DOS
MOV WORD PTR [FHANDLE],AX ; Save handle/error code
JNC OPN_OK
MOV SI,OFFSET PRP4 ; Point to prompt
CALL PROMPT ; Print to screen
MOV SI,OFFSET FNAME ; Point to filename
CALL PROMPT ; Print to screen
MOV SI,OFFSET PRP5 ; End of the string
CALL PROMPT
STC ; Maintain error status
OPN_OK: RET
OPEN_R ENDP
;
OPEN_W PROC NEAR
MOV AX,3D00H ; Open for read
XOR CX,CX ; Normal file
MOV DX,OFFSET FNAME ; File name
INT MS_DOS
MOV WORD PTR [FHANDLE],AX ; Save handle/error code
JC CREATE ; Not found, create one.
CALL CLOSE ; File was found, close it
MOV SI,OFFSET FNAME ; Present file name
MOV DI,OFFSET ONAME ; New name to '.old'
MOV CX,64 ; Max characters
NFILE0: LODSB ; Get byte
CMP AL,'.' ; Find the delimiter?
JNZ NODOT
STOSB ; Put in the dot
MOV AL,'O' ; Get a 'O' for old
STOSB ; Save
MOV AX,'DL' ; 'LD' backwards
STOSW ; Save in string
JMP SHORT NFILE1
NODOT: STOSB
LOOP NFILE0 ; Continue for the whole name
NFILE1: MOV AX,4100H ; Delete file function
MOV DX,OFFSET ONAME ; Delete any such old name
INT MS_DOS ; Do it, ignore any errors
MOV AX,5600H ; Rename file
MOV DX,OFFSET FNAME ; File to be renamed
MOV DI,OFFSET ONAME ; What to name it to
INT MS_DOS ; Rename the file
CREATE: MOV AX,3C00H ; Create file
MOV DX,OFFSET FNAME ; File to create
XOR CX,CX ; Normal attribute
INT MS_DOS ; Create it
MOV WORD PTR [FHANDLE],AX ; Save handle/error
JNC CREOK
MOV SI,OFFSET PRP4 ; Point to prompt
CALL PROMPT
MOV SI,OFFSET FNAME ; Point to filename
CALL PROMPT ; Print to screen
MOV SI,OFFSET PRP6 ; End of the string
CALL PROMPT
STC ; Maintain error status
CREOK: RET
OPEN_W ENDP
;
CLOSE PROC NEAR
MOV AX,3E00H ; Close file function
MOV BX,WORD PTR [FHANDLE] ; Get file handle
INT MS_DOS
RET
CLOSE ENDP
;
; Read the file open for reading into the buffer.
;
READ PROC NEAR
CMP BYTE PTR [TRIES],MAX ; No retries?
JNZ NOUP

CMP WORD PTR [BYTES],3072 ; Can we bump it 1024? 1.20i
JA SMUP ; No! See if can add 512 1.20h
ADD WORD PTR [BYTES],1024 ; Send more bytes 1.20h
JMP SHORT NODWN ; Now continue 1.20h

SMUP: CMP WORD PTR [BYTES],3584 ; Check upper limit 1.20i
JA NODWN ; Don't add more is max!
ADD WORD PTR [BYTES],512 ; Send max bytes
JMP SHORT NODWN ; Continue
NOUP: CMP WORD PTR [BYTES],512 ; Already the lowest?
JBE NODWN ; Lowest or lower? 1.20j
SHR WORD PTR [BYTES],1 ; Get/send fewer bytes
SHR WORD PTR [BYTES],1 ; Div 4
;
NODWN: MOV CX,WORD PTR [BYTES] ; Get Bytes to read
MOV BX,WORD PTR [FHANDLE] ; Get open file handle
MOV AX,3F00H ; Read file/device
PUSH DS ; Save segment
MOV DS,WORD PTR [BUF_SEG] ; Pick up segment for output data
MOV DX,IDATA ; Offset of the data
INT MS_DOS ; Read the data
MOV BYTE PTR DS:[CTRL],NORMAL ; Assume normal read
CMP AX,CX ; Check for end of file
JZ NORM ; Was not the end of file
OR BYTE PTR DS:[CTRL],EOF ; Is the end of file
MOV BYTE PTR ES:[FEND],EOF ; Local info also
NORM: MOV CX,AX ; Save real byte count
ADD AX,6 ; Overhead
MOV WORD PTR DS:[LEN],AX ; Bytes to transmit
;
PUSH ES ; Save segment
PUSH DS
POP ES ; ES=DS
PUSH CX ; Save byte count for now
MOV AX,CX ; Count to encode
MOV SI,IDATA ; Where the data is
MOV DI,CODELOC ; Where to put coded data
CALL ENCODE ; Encode the data
POP CX ; Restore original byte count
CMP AX,CX ; Check to see if new is less
JNC READ0 ; Not less
;
PUSH CX ; Save original byte count
MOV CX,AX ; New byte count
MOV SI,CODELOC ; Where compressed data is
MOV DI,IDATA ; Where to put compressed data
REP MOVSB ; Copy the data
ADD AX,6 ; Add in overhead
MOV WORD PTR DS:[LEN],AX ; Bytes to actually transmit
AND BYTE PTR DS:[CTRL],NONORM ; Reset, normal bit
OR BYTE PTR DS:[CTRL],COMP ; Show its encoded
POP CX ; Restore original byte count
;
READ0: POP ES ; Restore segments
POP DS
ADD WORD PTR [CNT_LO],CX ; Add to the count

ADC WORD PTR [CNT_HI],0 ; Take care of overflow.
INC WORD PTR [REC_NOL] ; Next record number
INC WORD PTR [SND_BLK] ; Next record number lbn 1.20k
RET
READ ENDP
;
; Write the buffer contents to the file
;
WRITE PROC NEAR
MOV AL,BYTE PTR [REC_NOL] ; Get next record number byte
PUSH DS ; Save segment
MOV DS,WORD PTR [BUF_SEG] ; Where the data is
MOV DX,IDATA ; Offset for data
MOV CX,WORD PTR DS:[LEN] ; Get string length
SUB CX,6 ; Remove overhead
CMP AL,BYTE PTR DS:[RECN] ; Record we expect?
JZ WRITE0 ; Yes, write record.
POP DS ; Restore segment
XOR AX,AX ; Set zero
RET ; Continue
;
WRITE0: TEST BYTE PTR DS:[CTRL],COMP ; Check for compressed data
JZ WRITE1 ; Its normal, not compressed
;
PUSH ES ; Save segment
PUSH DS
POP ES ; ES=DS
MOV SI,DX ; Location of the string
MOV DI,CODELOC ; Where to put the decoded data
MOV AX,CX ; String length
CALL DECODE ; Decode the data
MOV CX,AX ; New string length
MOV DX,CODELOC ; Where the data is
POP ES ; Restore segment
;
WRITE1: MOV BX,WORD PTR CS:[FHANDLE] ; Get the file access word
MOV AX,4000H ; Write to file/device
INT MS_DOS
POP DS ; Restore segment
CMP CX,AX ; See if a complete write
PUSHF ; Save write status
; MOV WORD PTR [BYTES],CX ; For 'SHOW' routine cmntout 1.20j
ADD WORD PTR [CNT_LO],CX ; Add to the count
ADC WORD PTR [CNT_HI],0 ; Take care of overflow.
CALL SHOW
INC WORD PTR [REC_NOL] ; Next record number
INC WORD PTR [SND_BLK] ; Next BLOCK # lbn 1.20k
POPF ; Restore write status
RET
WRITE ENDP
;
; Decode compressed string in DS:SI and transfer to ES:DI
; Input string length is in AX, output string length in AX
;
DECODE PROC NEAR
PUSH DI ; SAVE START ADDRESS
MOV BX,SI ; GET STARTING OFFSET
ADD BX,AX ; CALC END OF STRING OFFSET
DECD0: LODSB ; GET BYTE
CMP AL,0BBH ; SENTINEL ?
JNZ DECD1 ; NO
LODSW ; PICK UP LENGTH
MOV CX,AX ; UPDATE BYTE COUNT
LODSB ; GET REPEAT CHARACTER
REP STOSB ; FILL IN CHARACTER
JMP SHORT DECD2 ; AND CONTINUE
DECD1: STOSB ; TRANSFER BYTE
DECD2: CMP SI,BX ; END OF STRING?
JC DECD0 ; NOT YET
MOV AX,DI ; GET ENDING STRING OFFSET
POP DI ; RESTORE START ADDRESS
SUB AX,DI ; SUBTRACT START
RET ; AX = BYTE COUNT
DECODE ENDP
;
; Encode string in DS:SI. Output string in ES:DI. Upon input
; AX = Byte count. Upon output AX = new string length
;
ENCODE PROC NEAR
PUSH DX
PUSH DI ; SAVE OUTPUT STRING LOC
ADD AX,SI ; CALC LAST ADDRESS
MOV DX,AX ; SAVE LAST ADDESSS
ENCD0: LODSB ; GET BYTE
PUSH SI ; SAVE SOURCE INDEX
PUSH DI ; SAVE DEST INDEX
MOV CX,DX ; GET LAST POSSIBLE ADDRESS
SUB CX,SI ; SUBTRACT PRESENT ADDRESS
INC CX ; ADD ONE
;
MOV DI,SI ; FOR SEARCH DI=POINTER
REPZ SCASB ; SEE HOW MANY THERE ARE
MOV BX,DI ; GET LAST ADDRESS
;
POP DI ; RESTORE REGISTERS
POP SI
SUB BX,SI ; BX = BYTE COUNT
CMP AL,0BBH ; WAS IT THE SENTINEL?
JZ ENCD1 ; YES, REQUIRES ENCODING
CMP BX,4 ; ENOUGH TO COMPACT?
JC NOENC ; NO, DON'T ENCODE
ENCD1: PUSH AX ; SAVE BYTE
MOV AL,0BBH ; SENTINEL
STOSB ; PUT IN OUTPUT BUFFER
MOV AX,BX ; BYTE COUNT
STOSW ; INTO BUFFER
POP AX ; RESTORE SAVED BYTE
DEC BX ; LODSB INCREMENTED IT ONCE
ADD SI,BX ; UPDATE SOURCE
NOENC: STOSB ; SAVE BYTE
ENCD2: CMP SI,DX ; CHECK LIMITS
JC ENCD0 ; CONTINUE
ENCD3: MOV AX,DI ; GET LAST ADDRESS
POP DI ; RESTORE START ADDRESS
SUB AX,DI ; CALC BYTE COUNT
POP DX
RET
ENCODE ENDP
;
; Control Break not allowed.
;
BRK PROC FAR
MOV BYTE PTR CS:[KILL],0FFH ; Set the flag.
IRET
BRK ENDP
;
;
SHOW PROC NEAR
CMP BYTE PTR [NOSCR],0 ; Do we have a status screen?
JZ SCR2 ; Yes
RET ; No
SCR2: MOV DX,WORD PTR [ABS_TIM] ; Get current time
MOV AX,WORD PTR [TIM_L] ; Get Last time
; MOV WORD PTR [TIM_L],DX ; Save new LOW count 1.20i
SUB DX,AX ; Sub new LOW from old
JC NOCY
ADD DX,0FFFFH ; Must have gone past the hour
NOCY: MOV CX,DX ; Save low count
JNZ NOZER ; Not a zero
MOV CX,1 ; Make zero one
NOZER: MOV AX,WORD PTR [BYTES] ; Get bytes TX/RX this time
XOR DX,DX ; Ready for DIV
DIV CX ; AX = bytes / tick
MOV CX,182 ; OOPS! was 190 too big twc 1.20j
MUL CX ; real value is 18.2 ~1% error 1.20i
XOR DX,DX ; clear DX twc 1.20j
; changed to 100 to kill overflow of CPS lbn 1.20m
MOV CX,100 ; Adjust for 18.2 twc 1.20m was10
DIV CX ; and do it twc 1.20j
ADD WORD PTR [CPS],AX ; Add to total lbn 1.20k
MOV AX,WORD PTR [CPS] ; Retrive total lbn 1.20k
XOR DX,DX ; Clear for divide lbn 1.20k
MOV CX,WORD PTR [SND_BLK] ; Get Block total lbn 1.20k
DIV CX ; Divide by count lbn 1.20k
; restore value to correct amount w/o overflow lbn 1.20m

MOV CX,10 ; kill overflow lbn 1.20m
MUL CX ; kill overflow lbn 1.20m
;
PUSH ES
MOV ES,WORD PTR [SCR_SEG] ; Pick up screen segment
;
MOV DI,SCR_CPS
XOR DX,DX ; Zero high word
CALL ASCII
MOV AX,(SCRATTR SHL 8) OR 20H ; Normal attribute and a space
FILL0: STOSW
CMP DI,SCR_CPS + 14
JC FILL0
;
MOV AX,WORD PTR [REC_NOL] ; Get record number
XOR DX,DX ; Set high word
MOV DI,SCR_BLK ; Where to put the data
CALL ASCII ; Convert
;
PUSH DS ; Save segment
MOV DS,WORD PTR [BUF_SEG] ; Get buffer segment
MOV AX,WORD PTR DS:[LEN] ; Get bytes
POP DS ; Restore segment
XOR DX,DX ; Zero high word
MOV DI,SCR_LEN ; Where to put the ASCII
CALL ASCII ; Convert to ascii
MOV AX,(SCRATTR SHL 8) OR 20H ; Normal attribute and a space
FILL2: STOSW
CMP DI,SCR_LEN + 14
JC FILL2
;
MOV DX,WORD PTR [CNT_HI] ; Pick up byte count
MOV AX,WORD PTR [CNT_LO]
MOV DI,SCR_BYT ; Where to put the data
CALL ASCII ; Convert to ASCII decimal
MOV AX,(SCRATTR SHL 8) OR 20H ; Normal attribute and a space
FILL3: STOSW
CMP DI,SCR_BYT + 14
JC FILL3
;
POP ES
RET
SHOW ENDP
;
; Print double precision number in DX:AX. The string is addressed by DI
; All registers except BP are destroyed by this call.
;
ASCII PROC NEAR
MOV BP,0000 ; Leading zero flag
MOV CX,3B9AH ; Get billions
MOV BX,0CA00H
CALL SUBTR ; Subtract them out
CALL COMMA ; Put in a comma
MOV CX,05F5H ; Get hundred-millions
MOV BX,0E100H
CALL SUBTR ; Subtract them out
MOV CX,0098H ; Get ten-millions
MOV BX,9680H
CALL SUBTR ; Subtract them out
MOV CX,000FH ; Get millions
MOV BX,4240H
CALL SUBTR ; Subtract them out
CALL COMMA ; Put in a comma
MOV CX,0001H ; Get hundred-thousands
MOV BX,86A0H
CALL SUBTR ; Subtract them out
MOV CX,0000H ; Get ten-thousands
MOV BX,2710H
CALL SUBTR ; Subtract them out
MOV CX,0000H ; Get thousands
MOV BX,03E8H
CALL SUBTR ; Subtract them out
CALL COMMA ; Put in a comma
MOV CX,0000H ; Get hundreds
MOV BX,0064H
CALL SUBTR ; Subtract them out
MOV CX,0000H ; Get tens
MOV BX,000AH
CALL SUBTR ; Subtract them out
ADD AL,'0' ; Add bias to residual
MOV AH,SCRATTR ; Normal attribute
STOSW ; Put in the string
RET
;
SUBTR: MOV SI,'0'-1 ; We are out of registers!
SUBTR1: INC SI ; Counter
SUB AX,BX ; Dword subtraction
SBB DX,CX
JNB SUBTR1 ; Continue until a carry
ADD AX,BX ; One too many, add back
ADC DX,CX ; and the remainder
CMP BP,0 ; See if we printed anything yet
JNZ SUBTR2 ; No, can't be a leading zero
CMP SI,'0' ; See if its a zero
JZ SUBTR3 ; Yes, don't print them
;
SUBTR2: INC BP ; We are now 'printing' a character
PUSH AX ; Save accumulator
MOV AX,SI ; Get index
MOV AH,SCRATTR ; Attribute byte
STOSW ; Put in screen memory
POP AX ; Restore accumulator
SUBTR3: RET
;
COMMA: CMP BP,0 ; Any bytes printed?
JZ PASS ; No, then no commas
PUSH AX ; Yes, save
MOV AL,',' ; Get a comma
MOV AH,SCRATTR ; Attribute
STOSW ; Into string
POP AX ; Restore
PASS: RET
ASCII ENDP
;
; Send 10 control-Xes to remote to cause an abort.
;
SENDX PROC NEAR
MOV CX,10 ; Bytes to send
SENDX0: MOV AL,CAN ; Control-x
CALL FORCE ; Send (no flow control)the byte
LOOP SENDX0 ; Continue for all bytes
RET
SENDX ENDP
;
; Restore saved bytes/attributes to the screen regen buffer
;
RES_SCR PROC NEAR
CMP BYTE PTR [SCR_SAV],0 ; Did we save the screen?
JNZ RESIT ; Yes
RET ; No, don't restore
RESIT: MOV BYTE PTR [SCR_SAV],0 ; Show its already been restored.
PUSH ES ; Save segment
MOV ES,WORD PTR [SCR_SEG] ; Pick up screen segment
MOV DI,ORIGIN ; Where to start
MOV SI,OFFSET SCR_BUF ; Where the saved data are
MOV CX,7 ; Lines to restore
RES0: PUSH CX ; Save count
MOV CX,LINLEN ; Line length
REP MOVSW ; Move to screen buffer
ADD DI,160 - ( LINLEN * 2 ) ; Ready next line
POP CX ; Restore line count
LOOP RES0 ; Continue for all lines
POP ES ; Restore segment
;
MOV AX,0200H ; Set cursor position
MOV BX,0 ; Page zero
MOV DX,WORD PTR [CUR_POS] ; Get old position
INT VIDEO ; Set the position
RET
RES_SCR ENDP
;
; Upon entry, SI points to the tune to be played.
;
PLAY PROC NEAR
CMP BYTE PTR [QUIET],0 ; Should we be quiet?
JZ PLAY1 ; No, sound
RET ; Yes
PLAY1:
LODSB ; Get function from table
OR AL,AL ; Was it a null?
JZ PLAY6 ; All done
;
PLAY4: CMP AL,'N' ; Sound note?
JNZ PLAY5 ; No
;
LODSB ; Get note
CALL PITCH ; Create pitch
MOV CX,DX ; Resupt in CX
CALL TONESET ; Set tone frequency
CALL TONEON ; Turn tine on
;
LODSB ; Get tine length
CALL DELAY ; Wait appropriate time
CALL TONEOFF ; Turn off the tone
LODSB ; Get silent time
;
CALL DELAY ; Quiet
JMP PLAY1 ; Continue
;
PLAY5: CMP AL,'R' ; A rest?
JNZ PLAY6 ; No
LODSB ; Yes, get delay
CALL DELAY ; Wait delay time
JMP PLAY1 ; Continue
PLAY6: RET
PLAY ENDP
;
; Set note pitch.
;
PITCH PROC NEAR
PUSH BX
PUSH CX
MOV AH,0 ; Extend pitch to 16 bite
MOV CL,12 ; 12 semitones
DIV CL ; DIV
MOV DL,AL ; Quotient=octave
MOV AL,AH ; Remainder = pitch table
CBW ; 16 bit for lookup
SAL AX,1 ; Times two (words in table)
MOV BX,AX ; Index register
MOV CX, WORD PTR [NOTES+BX] ; Look up notes
CALL FREQ ; Convert
XCHG CX,DX ; Octave = CL, Per=DX
NEG CL ; 8 - Octave = shift count
ADD CL,8
SAL DX,CL
POP CX ; Restore registers used
POP BX
RET
PITCH ENDP
;
; Set the tone frequencies.
;
FREQ PROC NEAR
PUSH AX
PUSH DX
MOV DX,12H
MOV AX,34DEH
DIV CX ; Divide by frequency
MOV CX,AX ; CX = output
POP DX
POP AX
RET
FREQ ENDP
;
; Turn tone off.
;
TONEOFF PROC NEAR
IN AL,SPEAKER ; Speaker port
AND AL,11111100B ; Set mask
OUT SPEAKER,AL ; Back out
RET
TONEOFF ENDP
;
; Turn tone on.
;
TONEON PROC NEAR
IN AL,SPEAKER ; Speaker port
OR AL,00000011B ; Set mask
OUT SPEAKER,AL ; Back out
RET
TONEON ENDP
;
; Set tone frequency.
;
TONESET PROC NEAR
PUSH AX
MOV AL,CL ; Get low byte
OUT TIMER,AL ; Out the port
MOV AL,CH ; Get high byte
OUT TIMER,AL ; Out the port
POP AX
RET
TONESET ENDP
;
; Delays
;
DELAY PROC NEAR
MOV BYTE PTR [METRO],AL ; Delay time in clock
DEL1: CMP BYTE PTR [METRO],0 ; Wait until its done
JNZ DEL1
RET
DELAY ENDP
;
ABS_TIM DW 0 ; Absolute time (clock ticks)
NOSCR DB 0 ; Flag for no status screen.
TIM_L DW 0 ; Transfer time Low word
METRO DB 0 ; Music metronone
QUIET DB 0 ; Kill music
KILL DB 0 ; Abort program.
SCR_SAV DB 0 ; Flag to show we saved the screen.
INT_FLG DB 0 ; Flag to show hot interrupts
CNT_HI DW 0 ; Transfer byte count
CNT_LO DW 0 ; Low word of above.
ENABLE DB 0 ; TX enable bytes
WASC DB 0 ; Flag for carrier
STATUS DB 0 ; Port status
FEND DB 0 ; End of file?
MOD_STA DB 0 ; Modem status at interrupt time
REC_NOL DW 0 ; Record number of file to send/rec
SND_BLK DW 0 ; Blocks xfered lbn 1.20k
SCR_SEG DW 0 ; Screen segment
KLEN EQU $ - KILL ; Length to zero out
BYTES DW 512 ; Bytes to read/send was 2048 1.20j
TRIES DB ? ; Attempts to send a block
RXTX DB ? ; Receive/transm flag
CLOCK DW ? ; Local timer
FHANDLE DW ? ; File handle
BUF_SEG DW ? ; Segment of buffer
DAT_SEG DW ? ; Next 64k segment
OLD_CLK LABEL DWORD ; Old clock vector
CLK_OFF DW ? ; Clock offset
CLK_SEG DW ? ; Clock segment
OLD_BRK LABEL DWORD ; Old break key vector
BRK_OFF DW ?
BRK_SEG DW ?
OLD_CTC LABEL DWORD ; Old ^C vector
CTC_OFF DW ?
CTC_SEG DW ?
OLD_UART LABEL DWORD ; Old UART interrupt address
UAR_OFF DW ? ; Its offset
UAR_SEG DW ? ; and segment
OLD_MASK DB ? ; Old interrupt mask
BYTE_CNT DW ? ; Interrupt byte count
WPOINT DW ? ; Write pointer for interrupt
CUR_POS DW ? ; Saved cursor position
PORT DW ? ; Communications adapter port
COM_INT DB ? ; Communications adapter interrupt
COM_MSK DB ? ; Communications adapter contr mask
ABRT_X DB 3 ; Check for an abort three times.
SENDING DB 0 ; Sending flag 1.20f
CPS DW 0 ; CPS storage area
;
; Tables for music played for success/failure
;
; TONE LEN PAUSE
;
TUNE1 DB 'N', 55, 4, 1
DB 'N', 60, 3, 1
DB 'N', 64, 2, 1
DB 'N', 67, 1, 1
DB 'N', 67, 1, 1
DB 'N', 67, 1, 1
DB 'N', 64, 1, 1
DB 'N', 64, 2, 1
DB 'N', 64, 2, 1
DB 'N', 60, 2, 1
DB 'N', 60, 1, 1
DB 'N', 55, 3, 1
DB 'R', 2
DB 'N', 55, 1, 1
DB 'N', 55, 1, 1
DB 'N', 55, 1, 1
DB 'N', 60, 4, 1
DB 0

TUNE2 DB 'N', 55, 1, 1
DB 'N', 55, 1, 1
DB 'N', 60, 4, 1

DB 'N', 55, 1, 1
DB 'N', 55, 1, 1
DB 'N', 60, 4, 1

DB 'N', 55, 1, 1
DB 'N', 55, 1, 1
DB 'N', 60, 4, 1
DB 'R', 2
DB 'N', 55, 1, 1
DB 'N', 55, 1, 1
DB 'N', 67, 3, 1

DB 'N', 64, 1, 1
DB 'N', 60, 2, 1
DB 'N', 55, 3, 1

DB 'N', 55, 1, 1
DB 'N', 55, 2, 1
DB 'N', 60, 4, 1
DB 0
;
NOTES DW 4186
DW 4435
DW 4699
DW 4978
DW 5274
DW 5588
DW 5920
DW 6272
DW 6645
DW 7040
DW 7459
DW 7902
;
LINDAT DB 201
DB 21 DUP (205)
DB 187
DB 186,' J M O D E M Status ',186
DB 186,' Block : ',186
DB 186,' Length : ',186
DB 186,' Bytes : ',186
DB 186,' Rate : acps',186 ; lbn 1.20k
DB 200
DB 21 DUP (205)
DB 188
;
; Screen "window" contents will be saved here.
;
SCR_BUF LABEL BYTE
PRP3 DB CR,LF
DB ((80-(@LIN1+@VERS))/2) DUP (' ')
LIN1 <>
VERS <>
IF BAD_BBS
DB 'B'
ENDIF
DB CR,LF
DB ((80-@LIN2)/2) DUP (' ')
LIN2 <>
DB CR,LF
; Out 8-5-94 1.20m
; DB ((80-@LIN3)/2) DUP (' ')
; LIN3 <>
; DB CR,LF
DB CR,LF,'Usage:'
DB CR,LF,'JMODEM S1 (to send a file via COM1)'
DB CR,LF,'JMODEM R1 (to receive a file via COM1)'
DB CR,LF,'JMODEM S2 (to send a file via COM2)'
DB CR,LF,'JMODEM R2 (to receive a file via COM2)'
DB CR,LF,'JMODEM S3 (to send a file via COM3)'
DB CR,LF,'JMODEM R3 (to receive a file via COM3)'
DB CR,LF,'JMODEM S4 (to send a file via COM4)'
DB CR,LF,'JMODEM R4 (to receive a file via COM4)'
CRLF DB CR,LF,0
PRP4 DB CR,LF,'Specified file "',0
PRP5 DB '" not found.',0
PRP6 DB '" Can''t be created.',0
PRP7 DB CR,LF,'Sending file ',0
PRP8 DB CR,LF,'Receiving file ',0
PRP9 DB CR,LF,'File transfer aborted!',0
PRP10 DB CR,LF,'File transfer complete.',0
PRP11 DB CR,LF,'Modem carrier failed.',0
PRP12 DB ' [ Synchronized ] ',CR,LF,0
PRP13 DB ' [ Synchronizing ] '
VERS<>
ESCM<> ; Abort with message 1.20j
IF BAD_BBS
DB 'B'
ENDIF
DB CR,0
PRP14 DB CR,LF,'Can''t execute, no free RAM!'
DB CR,LF,'This program was already loaded over resident code.'
DB CR,LF,'The system will probably crash!',CR,LF,0
IF BAD_BBS
PRP16 DB CR,LF,'Can''t create file ',0
PRP17 DB CR,LF,'Can''t write file ' ,0
PRP18 DB CR,LF,'Can''t close file ' ,0
PRP19 DB CR,LF,'Can''t open file ' ,0
PRP20 DB CR,LF,'Can''t write file ' ,0
PRP21 DB CR,LF,'Can''t delete file ',0
;
; Space to save the registers upon entry and restore on exit.
;
AX_SAV DW ?
BX_SAV DW ?
CX_SAV DW ?
DX_SAV DW ?
SI_SAV DW ?
DI_SAV DW ?
DS_SAV DW ?
ES_SAV DW ?
SS_SAV DW ?
SP_SAV DW ?
BP_SAV DW ?
;
VMEM DB 'VIRTUAL',255,'.MEM',0 ; Memory image file, Hard to delete
ENDIF
;
FNAME DB 64 DUP (0) ; TX/RX file name.
ONAME DB 64 DUP (0) ; Filename to rename to '.OLD'
;
ORG ((($ - START) + 16) AND 0FFF0H)
DB 32 DUP ('STACK ')
STACKP LABEL WORD
;
ORG ((($ - START) + 16) AND 0FFF0H) ; Put on a paragraph boundary!
; ------------------------------------------------------------------------
; The following is used only once during initialization and will be
; overwritten with data once file transfer starts.
; ------------------------------------------------------------------------
BUFFER LABEL BYTE ; Offset 0 of the data segment
PRP22 DB CR,LF,'DOS reports that JMODEM was improperly loaded'
DB CR,LF,'and does not own the memory it is using!',CR,LF,0
PRP23 DB CR,LF,'Not enough free memory for JMODEM to use!',CR,LF,0
;
COMINIT COM1<> ; COM1 parameters
CLEN EQU $ - COMINIT ; Length of data to move
COM2<> ; COM2 parameters
COM3<> ; COM3 parameters
COM4<> ; COM4 parameters
;
; Set up segment for the data and interrupt buffer.
;
SET_SEG PROC NEAR
MOV BX,OFFSET BUFFER ; Point to buffer
SHR BX,1 ; /2
SHR BX,1 ; /4
SHR BX,1 ; /8
SHR BX,1 ; /16 BX= current use in paragraphs
MOV AX,4A00H ; Modify memory blocks
INT MS_DOS ; Call DOS and do it.
JNC SETOK ; Able to reduce memory allocated
MOV SI,OFFSET PRP22 ; Point to error message
CALL PROMPT ; Write error message
STC ; Show error
JMP SHORT SETEXT
;
SETOK: MOV AX,4800H ; Allocate memory
MOV BX,(0FFFFH SHR 4) ; Request 64k -1
INT MS_DOS ; Get some memory
JNC SETSEG ; We have the memory we need
MOV SI,OFFSET PRP23 ; Point to error message
CALL PROMPT ; Write to screen
STC ; Show error, fall through
;
SETSEG: MOV WORD PTR [BUF_SEG],AX ; Save segment for later
SETEXT: RET
SET_SEG ENDP
;
; Check environment for 'JMODEM=X'. This is used only once so its
; put in the data buffer to be overwritten after initialization.
;
CHK_ENV PROC NEAR
PUSH ES ; Save segments used
MOV ES,WORD PTR DS:[ENV_SEG] ; Get environment segment
MOV CX,(0FFFFH SHR 1) ; Largest environment possible!
XOR AL,AL ; Search for double-zero
XOR DI,DI ; Offset zero
CHK00: REPNZ SCASB ; Look for the 00 byte
CMP BYTE PTR ES:[DI],0 ; Second byte?
JZ DBLZER ; Yes
LOOP CHK00 ; No, continue
POP ES ; Level stack
RET ; No environment ???
;
DBLZER: MOV CX,DI ; CX = environment string length
MOV BX,CX ; Save string length
PUSH DS ; Save local segment
PUSH ES
POP DS ; DS = ES
XOR SI,SI ; Offset zero
MOV DI,SI ; Same here
CALL MAP ; Map to upper case
POP DS ; Restore local segment
;
XOR DI,DI ; Offset zero
MOV CX,BX ; Pick up string length
MOV DX,OFFSET JMODEMQ ; Sub-string to find
MOV BX,JLENQ ; Length of the sub-string
CALL SEARCH ; Check for the sub-string
JNZ CK1 ; Not found
MOV BYTE PTR [QUIET],0FFH ; Set quiet flag
;
CK1: XOR DI,DI ; Offset zero
MOV DX,OFFSET JMODEMC ; Sub-string to find
MOV BX,JLENC ; Length of the sub-string
CALL SEARCH ; Check for the sub-string
JNZ CK2 ; Not found
MOV WORD PTR [SCR_SEG],0B800H ; Set segment for color
;
CK2: XOR DI,DI ; Offset zero
MOV DX,OFFSET JMODEMM ; Sub-string to find
MOV BX,JLENM ; Length of the sub-string
CALL SEARCH ; Check for the sub-string
JNZ CK3 ; Not found
MOV WORD PTR [SCR_SEG],0B000H ; Set segment for color
;
CK3: XOR DI,DI ; Offset zero
MOV DX,OFFSET JMODEMO ; Sub-string to find
MOV BX,JLENO ; Length of the sub-string
CALL SEARCH ; Check for the sub-string
JNZ CK4 ; Not found
MOV BYTE PTR [NOSCR],0FFH ; Set no status acreen
;
CK4: POP ES ; Restore segment
RET
CHK_ENV ENDP
;
; Search for a substring within a string.
; Upon entry: DX = location of substring
; BX = length of substring
; DI = location of the string
; CX = length of the string
; Upon exit: ZF if string is found.
; DI = location of byte after string
; CX = Saved Length of string
;
SEARCH PROC NEAR
PUSH CX ; Save string length
SEAR0: PUSH CX ; Save count
MOV SI,DX ; String to find
MOV CX,BX ; Length of the string
REPZ CMPSB ; Look for the string
POP CX
JZ SEAR1 ; We found it!
LOOP SEAR0 ; Continue
INC CX ; Set non-zero
SEAR1: POP CX ; Restore string length
RET
SEARCH ENDP
;
; Parse the command line to extract filename and function.
;
PARSE PROC NEAR
MOV SI,OFFSET COMINIT ; Assume COM1
MOV DI,OFFSET PORT ; Where to put the data
MOV CX,CLEN ; Length of string to move
REP MOVSB ; Fill the table
MOV SI,80H
LODSB ; Get bytes typed
CBW
MOV CX,AX ; Use as a count
JCXZ PARSEX ; Nothing typed
CALL MAP ; Map to upper case
CALL CLR_SPC ; Clear spaces
JCXZ PARSEX ; Ran out of characters
LODSB ; Get The byte
DEC CX ; Bump byte count
JZ PARSEX ; No more characters
CMP AL,'R' ; Was it a [R]eceive?
JZ YES ; Yes
CMP AL,'S' ; Was it a [S]end?
JZ YES ; Yes
PARSEX: STC ; Show error and exit
RET
;
YES: MOV BYTE PTR [RXTX],AL ; Save flag
MOV AX,CX ; Save count
CALL CLR_SPC ; Clear spaces
JCXZ PARSEX ; Ran out of characters
CMP AX,CX ; See if any spaces
JNZ PARSE2 ; Yes, use default port
;
LODSB ; Get port
DEC CX ; Bump byte count
JCXZ PARSEX ; Ran out of bytes
SUB AL,'0' ; Remove ASCII bias
JBE PARSE2 ; Lower limit
CMP AL,4 ; Check upper limit
JG PARSE2 ; Out of limits
DEC AL ; COM one = offset zero
XOR AH,AH ; Zero high byte
SHL AX,1 ; Times two
SHL AX,1 ; Times four
PUSH SI ; Save command line location
MOV SI,OFFSET COMINIT ; Where the table
ADD SI,AX ; Offset for COM1,,,4
MOV DI,OFFSET PORT ; Runtime parameters
PUSH CX ; Save count
MOV CX,CLEN ; Four bytes
REP MOVSB ; Insert new parameters
POP CX ; Restore count
POP SI ; Restore command line location
PARSE2: CALL CLR_SPC ; Clear spaces
JCXZ PARSEX ; Ran out of characters
MOV DI,OFFSET FNAME ; We got to the filename
REP MOVSB ; Copy it
RET
PARSE ENDP
;
; Clear spaces/tabs from the command line.
; Upon entry, SI points to the command line, CX is the byte count.
; Upon exit, SI points to the next, non-space character, CX= remaining
; Byte count.
;
CLR_SPC PROC NEAR
CMP BYTE PTR [SI],' ' ; Check the byte
JA CLREX ; Not a space or control chr
INC SI ; Ready next
LOOP CLR_SPC ; Continue
CLREX: RET
CLR_SPC ENDP
;
; Map string addressed by SI to upper case. CX = byte count.
;
MAP PROC NEAR
PUSH CX
PUSH SI
PUSH DI
MOV DI,SI ; Copy for same offset
MAP0: LODSB ; Get byte
CMP AL,'z' ; Check upper limit
JA MAP1 ; Not a lower case character
CMP AL,'a' ; Check lower limit
JB MAP1 ; Not a lower case character
AND AL,95 ; Reset lower case bits
MAP1: STOSB ; Replace in the string
LOOP MAP0
POP DI
POP SI
POP CX
RET
MAP ENDP
;
; Set up interrupt vectors. Save the machine state to be restored upon
; exit.
;
SET_INT PROC NEAR
MOV BYTE PTR [INT_FLG],0FFH ; Show we've set up interrupts
MOV AL,CTLB ; Control byte for timer
OUT TMODE,AL ; Set timer mode port
CALL CLR_BUF ; Put pointers at correct loc
MOV DX,WORD PTR [PORT] ; Get port address
ADD DX,1 ; Interrupt enable register
IN AL,DX ; Get the control byte
MOV BYTE PTR [STATUS],AL ; Save interrupt status
MOV AL,(INT_RC OR INT_MS) ; Interrupt on rec. chr. / mod. sta.
OUT DX,AL ; Enable interrupts
;
ADD DX,3 ; Offset to modem control register
MOV AL,RTS_DTR OR TRISTAT ; Get control byte
OUT DX,AL ; Output the byte
ADD DX,2 ; Offset to modem status
IN AL,DX ; Get modem status
MOV BYTE PTR [MOD_STA],AL ; Save the modem status
MOV AH,AL ; Duplicate
AND AH,RLSD ; Mask all but carrier det
MOV BYTE PTR [WASC],AH ; Save carrier status
AND AL,CTSDSR ; Mask all but CTS/DSR
MOV BYTE PTR [ENABLE],AL ; Save data enable byte
;
PUSH ES ; Save segment
MOV AL,BYTE PTR [COM_INT] ; COM interrupt
MOV AH,35H ; Get interrupt vector
INT MS_DOS ; Go get it
MOV WORD PTR [UAR_SEG],ES ; Store UART segment
MOV WORD PTR [UAR_OFF],BX ; Store UART offset
;
MOV AL,1CH ; Clock interrupt
MOV AH,35H ; Get interrupt vector
INT MS_DOS ; Go get it
MOV WORD PTR [CLK_SEG],ES ; Store clock segment
MOV WORD PTR [CLK_OFF],BX ; Store clock offset
;
MOV AL,1BH ; Control break interrupt
MOV AH,35H ; Get interrupt vector
INT MS_DOS ; Go get it
MOV WORD PTR [BRK_SEG],ES ; Store clock segment
MOV WORD PTR [BRK_OFF],BX ; Store clock offset
;
MOV AL,23H ; Control C interrupt
MOV AH,35H ; Get interrupt vector
INT MS_DOS ; Go get it
MOV WORD PTR [CTC_SEG],ES ; Store clock segment
MOV WORD PTR [CTC_OFF],BX ; Store clock offset
POP ES ; Restore segment
;
MOV AL,BYTE PTR [COM_INT] ; Communications vector
MOV AH,25H ; Set interrupt vector
MOV DX,OFFSET EASY_INT ; Our local vector
INT MS_DOS ; Go set it
;
IN AL,INT_CTL ; Get interrupt control mask
MOV BYTE PTR [OLD_MASK],AL ; Save the mask.
AND AL,BYTE PTR [COM_MSK] ; Set the mask
OUT INT_CTL,AL ; Set the mask
;
MOV AL,1CH ; Clock vector
MOV AH,25H ; Set interrupt vector
MOV DX,OFFSET LCL_CLK ; Our local vector
INT MS_DOS ; Go set it
;
MOV AL,1BH ; Control break vector
MOV AH,25H ; Set interrupt vector
MOV DX,OFFSET BRK ; Our local vector
INT MS_DOS ; Go set it
;
MOV AL,23H ; Control C vector
MOV AH,25H ; Set interrupt vector
MOV DX,OFFSET BRK ; Our local vector
INT MS_DOS ; Go set it
;
; Must reset all possible interrupts on the UART because its buggy.
; We'll do this by reading the registers for all possible sources of
; interrupts.
;
MOV CX,6 ; Possible registers
MOV DX,WORD PTR [PORT] ; Pick up the port
RESET: IN AL,DX ; Read the port
INC DX ; Next port
LOOP RESET ; Read all ports
;
MOV AL,INT_NOS ; Non-specific end-of-interrupt
OUT INT_RES,AL ; Reset the controller
RET
SET_INT ENDP
;
; Save any information in the screen regen buffer.
;
SAV_SCR PROC NEAR
CMP BYTE PTR [NOSCR],0 ; Do we want a status screen?
JZ SCR1 ; Yes
RET ; No
SCR1: MOV BYTE PTR [SCR_SAV],0FFH ; Set entry flag
MOV AX,0500H ; Select page zero
INT VIDEO ; Do it
;
MOV AX,0300H ; Get cursor position
MOV BX,0 ; Page zero
INT VIDEO ; Get the cursor position
MOV WORD PTR [CUR_POS],DX ; Save the position
;
MOV AX,0200H ; Set cursor position
MOV BX,0 ; Page zero
MOV DX,1A00H ; Hide off screen
INT VIDEO ; Set the position
;
PUSH DS ; Save data segment
MOV DS,WORD PTR [SCR_SEG] ; Pick up screen segment
MOV SI,ORIGIN ; Where we will put logo
MOV DI,OFFSET SCR_BUF ; Where to save the data
MOV CX,7 ; Lines to save
SAV0: PUSH CX ; Save count
MOV CX,LINLEN ; Length of line
REP MOVSW ; Copy to buffer
ADD SI,160 - (LINLEN *2) ; Next line
POP CX ; Restore count
LOOP SAV0 ; Continue for all lines
POP DS ; Restore segment
RET
SAV_SCR ENDP
;
; Write status block to the screen.
;
WRT_SCR PROC NEAR
CMP BYTE PTR [NOSCR],0 ; Do we want a status screen?
JZ SCR4 ; Yes
RET ; No
;
SCR4: PUSH ES ; Save segment
MOV ES,WORD PTR [SCR_SEG] ; Pick up screen segment
MOV SI,OFFSET LINDAT ; Data for line
MOV DI,ORIGIN ; Screen regen buffer
MOV CX,7 ; Number of lines
MOV AH,SCRATTR ; Attribute
WRT0: PUSH CX ; Save line count
MOV CX,LINLEN ; Get length of the line
WRT1: LODSB ; Get byte from logo
STOSW ; Save a word in screen memory
LOOP WRT1 ; Continue for the line
ADD DI,160 - (LINLEN * 2) ; Offset to next line
POP CX ; Restore line count
LOOP WRT0 ; Continue for all lines
POP ES ; Restore segment
RET
WRT_SCR ENDP
;
; Get segment address for screen regen buffer and base/status port for
; the video controller. Save addresses in code segment. This is used during
; initialization so its put in the data buffer to be overwritten later.
;
FIND_VIDEO PROC NEAR
CMP WORD PTR [SCR_SEG],0 ; See if we know the screen seg
JZ UNKNWN ; No, nothing in environment
RET ; Yes, don't change
;
UNKNWN: PUSH DS ; Save present
MOV AX,BSEG ; Get BIOS segment
MOV DS,AX ; Into ours
ASSUME DS:BSEG ; Tell MASM about offset
CMP WORD PTR DS:[ADDR_6845],03B4H ; Permanent B&W port.
ASSUME DS:PSEG ; Tell MASM about offset
POP DS ; Restore the data segment
MOV AX,0B000H ; Segment for mono
JZ BW ; Yes, its correct
MOV AX,0B800H ; Must be color
BW: MOV WORD PTR [SCR_SEG],AX ; Save for later
;
PUSH DS
MOV DS,WORD PTR [SCR_SEG] ; Pick up screen segment
MOV AX,WORD PTR DS:[0] ; Get first word
NOT AX ; Invert it
MOV WORD PTR DS:[0],AX ; Put back in screen memory
PUSH AX ; Exercise bus
POP AX
CMP WORD PTR DS:[0],AX ; See if it went
PUSHF ; Save result
NOT WORD PTR DS:[0] ; Change back to normal
POPF
POP DS ; Restore segment
JNZ CHKMEM ; Memory was not writable
RET
;
CHKMEM: CMP WORD PTR [SCR_SEG],0B000H ; See if mono
JZ MONO
;
; Color didn't work, change to Mono
;
MOV WORD PTR [SCR_SEG],0B000H
RET
;
; Mono didn't work, change to color
;
MONO: MOV WORD PTR [SCR_SEG],0B800H
RET
FIND_VIDEO ENDP
;
JMODEMQ DB 'JMODEM=SHUT'
JLENQ EQU $ - JMODEMQ
;
JMODEMC DB 'JMODEM_SCR=COLOR'
JLENC EQU $ - JMODEMC
;
JMODEMM DB 'JMODEM_SCR=MONO'
JLENM EQU $ - JMODEMM
;
JMODEMO DB 'JMODEM_SCR=OFF'
JLENO EQU $ - JMODEMO
;
TOP EQU $
PSEG ENDS
END MAIN


  3 Responses to “Category : Recently Uploaded Files
Archive   : JMOD120M.ZIP
Filename : JMODEM.ASM

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/