	PAGE 59, 132

	TITLE MSKERM -- Main module for MS-Kermit and descendents

; Update 20 Jan 86

IF1
 %OUT >> Starting pass 1
ELSE
 %OUT >> Starting pass 2
ENDIF

    PUBLIC Prompt, DosNum, CurDsk, SwChar, Kermit, KrmRet, OldStk
    PUBLIC SPath, PC_Type, PC_Subtype, Dos_Minor, TakRd, NoInt, OkInt
    PUBLIC Get_memory_block, Quick_Push, Exit2, In_menu_mode
    PUBLIC TakAdr, TakLev, Force_mono

Code	SEGMENT PARA PUBLIC
Code	ENDS

DataS	SEGMENT PARA PUBLIC 'datas'
DataS	ENDS

Stack	SEGMENT PARA STACK 'STACK'
Stack	ENDS

    INCLUDE MsDefs.H

; Based on ...

;******************** Version 2.26 **********************************
; KERMIT, Celtic for "free"
;
; The name "Kermit" is a registered trade mark of Henson Associates, Inc.,
; used by permission
;
;       Kermit-MS Program Version 2.26, July 26, 1984
;
;       Based on the Columbia University KERMIT Protocol
;
;       Copyright (C) 1982,1983,1984 Trustees of Columbia University
;
;       Daphne Tzoar, Jeff Damens
;       Columbia University Center for Computing Activities
;       612 West 115th Street
;       New York, NY  10025
;
; Special thanks to Frank da Cruz, Bill Catchings, Steve Jensen, Herm Fischer,
; Vace Kundakci, and Bernie Eiben for their help and contributions

makseg	EQU 26H
deffcb	EQU 5cH
setblk	EQU 4AH
exec	EQU 4BH
env	EQU 2CH			; environment address in psp
cline	EQU 80H			; offset in psp of command line
namsiz	EQU 64			; Bytes for file name and size
maxnam	EQU 10
chmod	EQU 43H			; chmod call (used to test for file existence)

DataS	SEGMENT PUBLIC 'datas'

    EXTRN DTA:byte, comand:byte, flags:byte, pack:byte, trans:byte
    EXTRN fcb:byte, cpfcb:byte, prmptr:word, inichk:byte

Help_text db Cr,Lf
	Program_name
	DB ' is an enhanced version of Columbia '
	db "University's MS-Kermit program.",Cr,Lf,'This version supports '
	db 'IBM PCs and compatibles as well as the DEC Rainbow.',Cr,Lf,Cr,Lf

	Program_name
	db ' has two main functions:  Terminal Emulation and File '
	db 'Transfer.  In',Cr,Lf,'Terminal Emulation Mode, '
	Program_name
	DB ' permits '
	db 'your microcomputer to act as a DEC',Cr,Lf,'VT100 terminal. '
	db ' In File Transfer Mode, '
	Program_name
	DB ' permits you to exchange files'
	db Cr,Lf,'with a remote computer running a program which '
	db 'supports the Kermit file',Cr,Lf,'transfer protocol.  '
	db 'A third mode, Command Mode, is used to set parameters and '
	db Cr,Lf,'control the program.  Press ? while '
	db 'typing a command for help, or press Esc',Cr,Lf
	db 'for command completion.',Cr,Lf,Cr,Lf

	db 'To use Terminal Emulation, you must first specify (or default) '
	db 'all required',Cr,Lf,'parameters (PORT, BAUD rate, and PARITY),'
	db " then press the IBM PC's F5 key ",'("Do"',Cr,Lf,'on Rainbow).  '
	db 'To get back to Command Mode, press F5 or Do again.  While in'
	db Cr,Lf,'Terminal Mode you can press F6 (PC only) '
	db 'to get more help.',Cr,Lf,Cr,Lf

	db 'To use File Transfer:',Cr,Lf
	db '  1)  Establish a connection to a remote host computer;',Cr,Lf
	db '  2)  Run Kermit (or a Kermit-based program) on the host in '
	db 'SERVER mode;',Cr,Lf
	db '  3)  Enter local Command Mode and type "SEND filename" to '
	db 'send a file,',Cr,Lf
	db '        or "GET filename" to receive a file from the host.'
	db Cr,Lf,Cr,Lf,'$'

versio	DB cr, lf		; Leading CrLf
	verdef
	DB ' running $'

On	DB 'on $'
IBMsg	DB 'IBM PC$'
RbMsg	DB 'DEC Rainbow$'
GrMsg	DB 'GRiD GRiDCase$'
Att6300Msg DB 'AT&T 6300$'
QuadRamsg DB 'QuadRam DataVue$'
MSDOS	DB 'under MS-DOS$'
UkMsg	DB 'MS-DOS OEM #$'

XT_str	DB '/XT$'
Portable_str DB ' Portable$'
jr_str	DB 'jr$'
AT_str	DB ' AT$'
Compaq_str DB ' clone (Compaq)$'
Clone_str DB ' clone (?)$'

;	DB ' under DOS v'
;dosvers DB '9.99'

EndVersio DB '  --  type ? or HELP for help',cr,lf,cr,lf,'$'

tmp	db ?,'$'
crlf    db cr,lf,'$'
ermes1  db '? Unrecognized command',cr,lf,'$'
ermes3  db '? Not confirmed',cr,lf,'$'
erms30	db '? Passed maximum nesting level for TAKE command',cr,lf,'$'
erms31	db '? File not found',cr,lf,'$'
erms32	db '? File(s) not found',cr,lf,'$'
erms33	db '? CHKDSK program not found on current disk',cr,lf,'$'
erms34	db '? This program runs only under DOS 2.0 and above',cr,lf,'$'
erms35	db '? Must specify program name',cr,lf,'$'
erms36	db '? Could not free memory',cr,lf,'$'
erms37	db '? Unable to execute program',cr,lf,'$'
erms38	db '? Not enough TAKE stack space left to parse filename',cr,lf,'$'
infms1	db 'Really erase *.*? $'
infms8	db 'File(s) erased',cr,lf,'$'
Echo_help DB ' Text to be echoed on screen$'
tmsg5	db '[Closing log file]',cr,lf,'$' ; [jd]
filhlp1 db ' Command file specification $'
filhlp2 db ' File specification (possibly wild) $'
filhlp3	db ' File spec (possibly wild) or confirm with carriage return$'
filmsg	db ' File specification with optional path name $'
filwmsg	db ' File specification (possibly wild) with optional path name $'
chkfil	db 0,'CHKDSK  COM'
chkflen	EQU $-chkfil

tophlp	DB ' One of the following:'
	DB cr,lf,' '
	DB 'BYE',tab,tab
	DB 'CLOSE',tab,tab
	DB 'CLS',tab,tab
	DB 'CONNECT'
	DB cr,lf,' '
	DB 'DEFINE',tab,tab
	DB 'DELETE',tab,tab
	DB 'DIRECTORY',tab
	DB 'DO'
	DB cr,lf,' '
	DB 'DO-SCRIPT',tab
	DB 'DROP-DTR',tab
	DB 'ECHO',tab,tab
	DB 'EXIT'
	DB cr,lf,' '
	DB 'FINISH',tab,tab
	DB 'GET',tab,tab
	DB 'HELP',tab,tab
	DB 'LOCAL'
	DB cr,lf,' '
	DB 'LOG',tab,tab
	DB 'LOGOUT',tab,tab
	DB 'MENU',tab,tab
	DB 'NPSEND'
	DB cr,lf,' '
	DB 'PUSH',tab,tab
	DB 'QUIT',tab,tab
	DB 'RECEIVE',tab,tab
	DB 'REMOTE'
	DB cr,lf,' '
	DB 'RUN',tab,tab
	DB 'SCRIPT-FILE',tab
	DB 'SEND',tab,tab
	DB 'SERVER'
	DB cr,lf,' '
	DB 'SET',tab,tab
	DB 'SHOW',tab,tab
	DB 'SPACE',tab,tab
	DB 'STATUS'
	DB cr,lf,' '
	DB 'TAKE',tab,tab
	DB 'XRECEIVE',tab
	DB 'XSEND'
	DB '$'

lochlp	DB cr,lf,' DELETE file'
	DB cr,lf,' DIRECTORY [filespec]'
	DB cr,lf,' PUSH to command interpreter'
	DB cr,lf,' RUN program'
	DB cr,lf,' SPACE remaining on current disk'
	DB '$'

	; COMND tables

yestab	DB 2
	mkeyw	'NO',0
	mkeyw	'YES',1

comtab  DB 36
	mkeyw	'BYE',bye
	mkeyw	'C',telnet
	mkeyw	'CLOSE',clscpt
	mkeyw	'CLS',Clear_screen
	mkeyw	'CONNECT',telnet
	mkeyw	'DEFINE',dodef
	mkeyw	'DELETE',delete
	mkeyw	'DIRECTORY',direct
	mkeyw	'DO',docom
	mkeyw	'DO-SCRIPT', Do_Script
	mkeyw	'DROP-DTR',DoDrop
	mkeyw	'ECHO', Echo_command
	mkeyw	'EXIT',exit
	mkeyw	'FINISH',finish
	mkeyw	'GET',get
	mkeyw	'HELP',help
	mkeyw	'LOCAL',lclcmd
	mkeyw	'LOG',setcpt
	mkeyw	'LOGOUT',logout
	mkeyw	'MENU', Menu
	mkeyw	'NPSEND', Non_protocol_send
	mkeyw	'PUSH',dopush
	mkeyw	'QUIT',exit
	mkeyw	'RECEIVE',read
	mkeyw	'REMOTE',remote
	mkeyw	'RUN',run
	mkeyw	'SCRIPT-FILE', Script_File
	mkeyw	'SEND',send
	mkeyw	'SERVER',server
	mkeyw	'SET',setcom
	mkeyw	'SHOW',showcmd
	mkeyw	'SPACE',chkdsk
	mkeyw	'STATUS',status
	mkeyw	'TAKE',take
	mkeyw	'XRECEIVE', XReceive
	mkeyw	'XSEND', XSend

loctab	DB 5
	mkeyw	'DELETE',delete
	mkeyw	'DIRECTORY',direct
	mkeyw	'PUSH',dopush
	mkeyw	'RUN',run
	mkeyw	'SPACE',chkdsk

shotab	DB 2
	mkeyw	'KEY',shokey
	mkeyw	'MACRO',shomac

; Program storage

Hold_di	DW ?			; Place to hold di reg over call to COMND

oldstk  DW ?			; Storage for system stack
oldsts  DW ? 			; System stack segment
ssave	DW ?			; Original SS when doing CHKDSK
siz	DW ?			; Memory size
in3ad	DD 0			; Original break interrupt addresses. [25]
curdsk	DB 0			; Current disk
origd	DB 0			; Original disk
fildat	DB 0			; Manipulate file data/time creation
	DB 0
taklev	DB 0			; Take levels. [25t]
takadr	DW takstr-(size takinfo) ; Pointer into structure. [25t]
temp	DW 0
temp1   DW ?		 	; Temporary storage
temp2   DW ?
temp3   DW ?
temp4   DW ?
psp	DW ?
divst	DW 0

Max_count_of_blocks=10		; Size of memory block table
Count_of_allocated_blocks DW 0	; Number of entries in memory block table
Block_table DW Max_count_of_blocks DUP (0) ; Memory block table

PC_Type	DB 0			; Zero for IBM
				;  2 for Rainbow
				;  4 for GRiD GRiDCase
				;  6 for AT&T 6300
				;  8 for QuadRam DataVue
				;  20 for unknown hardware (crash is likely)
PC_Subtype DB 0			; Zero for vanilla PC,
				;  1 for PC/XT
				;  2 for PCjr
				;  3 for PC AT
				;  4 for Portable PC
				;  90 for Compaq
				;  99 for PC clones

takstr	DB (size takinfo) * maxtak dup(?)


; Build a 12 char FCB style INI file name

ininam	DB 0
IniXXX	DB '        INI'	; Start with 8 spaces followed by INI
IniYYY	LABEL BYTE		; A label following the base name
	ORG IniXXX		; Go back to where the 8 space are
	Program_name		; Overlay the start with our file name
	ORG IniYYY		; Go back to proper location


; Build a DOS 2.0 style INI file name

IniNm2	LABEL	BYTE
	Program_name
	DB '.INI',0	; Name of INIT file
IniNm2_len EQU $ - IniNm2

nambuf	DB maxnam * namsiz dup (?)
cmdnam	DB namsiz dup (?)
exefcb	DB fcbsiz dup (?)
exefcb2	DB fcbsiz dup (?)
exearg	DW ?			; segment addr of environment (filled in below)
	DD 0			; ptr to cmd line (filled in below)
	DD exefcb		; default fcb
	DD exefcb2		; second default fcb
dircmd	DB ' /c dir '
dirclen	EQU $-dircmd
dirnam	DB 50h dup (?)
chkdcmd	DB 'chkdsk.com'
chkdlen	EQU $-chkdcmd
dosnum	DB ?			; dos version number
Dos_Minor DB ?			; Dos minor version number
Force_mono DB ?			; Use monochrome attributes on color displays
pthnam	DB 'PATH='
pthlen	EQU $-pthnam
pthbuf	DB 160 dup (?)		; buffer for path definition
defpth	DB '\', 70 dup (?)	; buffer for default path
cmspnam	DB 'COMSPEC='
cmsplen	EQU $-cmspnam
cmspbuf	DB '\command.com', 0	; Default name
	DB 30 dup (?)		; Some additional space
TFile	DB 100 dup (?)		; Temp space for file names
swchar	DB '\'			; Default switch character

In_menu_mode DB 0		; If non-0, we are in menu (not command) mode
NoInt_Count DB 0		; Number of times we have been NoInt-ed
LastChar DB 0			; Last char we read in NPSEND

Number	DB 8 DUP (?)		; Place to hold a number

DataS	ENDS


Code	SEGMENT	PUBLIC

    EXTRN cmblnk:near, locate:near, logout:near, Drop_DTR:NEAR
    EXTRN bye:near, telnet:near, finish:near, comnd:near, Telnet2:NEAR
    EXTRN read:near, remote:near, send:near, status:near, get:near
    EXTRN dodisk:near, serrst:near, setcom:near, OutChr:NEAR
    EXTRN clscpi:near, clscpt:near, getbaud:near, dodef:near, setcpt:near
    EXTRN docom:near, Set_up_script_processor:NEAR, server:near, lclini:near
    EXTRN shokey:near, shomac:near, XReceive:NEAR, XSend:NEAR
    EXTRN Script_File:NEAR, Do_Script:NEAR, Menu:NEAR, Quick_menu:NEAR

    ASSUME cs:Code, ds:Datas, ss:Stack

Critical DD ?			; Address of original Critical Error Handler

Error_handling_installed DB 0	; Flag set if error handling has been installed
Last_Critical_Error DW 00FFh	; Most recent critical error, if any

Start   PROC FAR

	push ds			; Save system data area
	sub ax,ax	 	; Get a zero
	push ax			; Put zero return addr on stack

	mov ax, SEG DataS	; Initialize DS
	mov ds,ax
	sub ax,ax

	mov oldstk, sp		; Save old stack pointer

	mov ax,es:[2]		; In program segment prefix
	mov siz,ax		; Pick up memory size
	mov psp,es

	mov ax, ds		; Copy ds
	mov es, ax		;  to es

    ASSUME es:datas

	mov ah, 30h		; Code to get DOS version number
	int Dos			; Do it
	cmp al, 2		; DOS 2.0 or higher?
	 jae DOS_is_Ok		;  Not a problem

	mov ah, PrStr		; Code to print a string
	mov dx, OFFSET ErMs34	; Message complaining about lousy DOS
	int Dos			; Type it

	ret			; Return to DOS

DOS_is_Ok:
	mov ax, (33H * 256) + 1	; Code to set BREAK state
	sub dl, dl		; Force it off NOW!
	int Dos			; We use ^C heavily, we have to control it

	push es			; Save es register
	mov es, psp		; Point to psp again
	mov ax, es:[Env]	; Get environment ptr
	mov exearg, ax		; Put into argument block

; Give back memory which we don't need from our initial allocation
;  (DOS gives us all available memory)

	mov bx, OFFSET Stk + 15	; End of program
	mov cl, 4
	shr bx, cl		; Compute # of paragraphs in last segment

	mov ax, SEG stack	; Last segment, either alphabetically or
				;  sequentially
	sub ax, Psp		; Minus beginning..

	add bx, ax		; # of paragraphs occupied
	mov ah, SetBlk		; Try to change size of our memory block
	int Dos			; Success?
	 jnc SHS_1		;  Nope..

	pushf			; Save flags
	mov ah, PrStr
	mov dx, OFFSET ErMs36	; Complain
	int Dos
	popf			; Restore flags

SHS_1:	pop es			; Unclobber register

; Before beginning, establish our own quick-o shortcut in case a drive
;  on the PATH is without disk ... in that case, just bomb the call, don't
;  do the usual "Abort, Retry or Ignore"

	call Set_up_error_handling ; Establish our routine as the one to use

	mov ah, prstr		; Print start of version header
	mov dx, OFFSET Versio
	int Dos


; Get the DOS version number and the "vendor number" for the vendor of this
;  copy of DOS ... anytime we are running PC-DOS (on whatever machine) we think
;  we are on "an IBM".  If the user thinks PC-DOS runs on his machine, lets
;  assume that the machine is a perfect enough clone for our purposes ...

	PUBLIC See_what_machine_we_are_on

See_what_machine_we_are_on:
	mov ah, 30h		; Code to get DOS version & vendor numbers
	int Dos			; Do it

	mov dosnum, al		; Remember major Dos version
	mov Dos_Minor, ah	; Store minor version number also

	mov Force_mono, 0	; Default to "use color on color display"
	mov ah, PrStr		; Set up to type strings

	PUBLIC Check_vendor

Check_vendor:
	cmp bh, 0FFh		; Generic MS-DOS?
	 jne Not_MSDOS		;  No

	mov dx, OFFSET MSDOS	; "under MS-DOS"
	int Dos			; Type it
	jmp SHORT Finish_banner	; Go finish it up

Not_MSDOS:
	mov dx, OFFSET On	; "on "
	int Dos			; Type it
	cmp bh, 0		; Is the vendor IBM?
	 jne Not_IBM		;  No

	call Vendor_is_IBM	; Use special rtn to determine type of IBM
	jmp SHORT Finish_banner	; Go finish it up

Not_IBM: cmp bh, 16h		; Is the vendor DEC?
	  jne Not_DEC		;  No

	mov PC_Type, 2		; Type is DEC
	mov dx, OFFSET RbMsg	; DEC Rainbow
	int Dos
	jmp SHORT Finish_banner

Not_DEC: cmp bh, 2Eh		; Is the vendor GRiD?
	  jne Not_GRiD		;  No

	mov PC_Type, 4		; Type is GRiD
	mov dx, OFFSET GrMsg	; GRiD GRiDCase
	int Dos
	jmp SHORT Finish_banner

Not_GRiD: cmp bh, 77h		; Is the vendor AT&T?
	  jne Not_Att		;  No

	mov PC_Type, 6		; Type is AT&T 6300
	mov dx, OFFSET Att6300Msg
	int Dos
	jmp SHORT Finish_banner

	PUBLIC No_body

Not_Att: cmp bh, 60		; Is the vendor QuadRam?
	  jne No_body		;  No

	mov PC_Type, 8		; Type is QuadRam DataVue 25
	mov dx, OFFSET QuadRamsg
	int Dos
	jmp SHORT Finish_banner

	PUBLIC No_body

No_body:
	push bx			; Save reg
	mov PC_Type, 20		; Type is unknown
	mov dx, OFFSET UkMsg	; Unknown hardware
	int Dos

	pop bx			; Get back reg
	cld			; Forwards
	mov al, bh		; Copy OEM # to al
	mov ah, 0		; Zero high half

	mov di, OFFSET Number	; Point at Number
	call nout2		; Do the number
	mov al, '$'		; Get a dollar sign
	stosb			; Put it down

	mov ah, PrStr		; Code to type to screen
	mov dx, OFFSET Number	; Point this at Number
	int Dos			; Do it

Finish_banner:
	mov dx,offset EndVersio ; Close it out
	int dos

	mov ah,setdma		; Set disk transfer address
	mov dx,offset DTA
	int dos

	call getbaud		; Get the baud rate. [25]
	call dodisk		; See how many disk drives we have. [21a]
	call setint

	mov ah,gcurdsk		; Get current disk
	int dos

	inc al			; We want 1 == A (not zero)
	mov curdsk,al
	mov origd,al		; Remember original disk we started on

	mov es,psp
	mov ax,es:[env]		; pick up environment address
	push ax
	call getpath		; get the path from the environment
	pop ax			; get environment back
	call getcsp		; get comspec from environment as well

	mov Flags.ExtFlg, 0	; Clear EXIT flag before LCLINI just in case
	call LclIni		; Do local initialization
	cmp Flags.ExtFlg, 0	; Some LclIni routines flag fatal errors
	 je KrmGo		;  No, finish startup

	jmp KrmEnd		; If so jump to KRMEND

KrmGo:	call gcmdlin		; read command line
	call rdinit		; read kermit init file
	call packlen		; Packet length in case do server comand


; This is the main KERMIT loop.  It prompts for and gets the users commands

Kermit:	mov ax,ds
	mov es,ax		; make sure this addresses data segment

	mov dx,prmptr		; get prompt
	call prompt		; Prompt the user
	mov pack.state,0	; Clear the state
	mov flags.cxzflg,0	; Reset each itme
	mov Flags.XFlg, 0	; This one also [tm]

	mov ah, Flags.Real_RemFlg ; Pick this up
	mov Flags.RemFlg, ah	; Copy to here

	mov ah,inichk		; Original or set checksum length
	mov trans.chklen,ah	; Reset just in case
	mov dx,offset comtab
	mov bx,offset tophlp
	mov comand.cmcr,1	; Allow bare CR's
	mov ah,cmkey
	call comnd
	 jmp kermt2
	mov comand.cmcr,0	; Not anymore
	call bx			; Call the routine returned
krmret:  jmp kermt3
	cmp flags.extflg,0	;  Check if the exit flag is set
	jne krmend		;  If so jump to KRMEND
	jmp kermit		; Do it again

kermt2:	mov dx,offset ermes1	;  Give an error
	jmp short kermt4

kermt3:	mov dx,offset ermes3	;  Give an error
kermt4:	cmp flags.cxzflg,'C'	; some sort of abort?
	je kermit		; yes, don't print error message
	mov ah,prstr
	int dos
	mov flags.droflg,0	; Reset drive override flag
	mov flags.nmoflg,0	; Reset filename override flag
	mov flags.getflg,0	; May as well do this one
	mov flags.cmrflg,0	; This one too
	jmp kermit

krmend: call serrst		; Just in case the port wasn't reset. [21c]

	mov dl,origd		; Original disk drive
	dec dl			; Want A == 0
	mov ah,seldsk		; Reset original disk just in case
	int dos

	call Return_all_memory	; Clean up after ourselves

	call Restore_normal_error_handling ; Return to normal situation

	mov sp, OldStk		; Fix up stack just in case

	mov ax, 4C00h		; Code to exit with no error
	int Dos			; Quit to DOS

	ret			; Should not return here!

START	ENDP


; Special routine to figure out what kind of IBM flavor PC we are running on

Vendor_is_IBM:

	mov PC_Type, 0		; Type is IBM
	mov dx, OFFSET IBMsg	; IBM PC
	int Dos

	mov bx, 0FFFFh		; Highest segment
	push es			; Save es
	mov es, bx		; Set up es
	mov al, es:[0Eh]	; "Machine-Sensitive Code", says IBM
	pop es			; Restore es

	cmp al, 0FFh		; Vanilla PC?
	 jne IBM_0		;  No, keep working

	ret			; Yes, done here

IBM_0:	cmp al, 0FEh		; PC/XT?
	 jne NotXT

; Its down to /XT versus Portable PC (same BIOS)

	mov bx, 040h		; DOS's data segment
	push es			; Save es
	mov es, bx		; Set up es
	mov al, es:[075h]	; Count of fixed disk drives
	pop es			; Restore es

	cmp al, 0		; Are there any fixed disks on this machine?
	 jne IsXT		;  Yes there are, must be an XT

	mov PC_Subtype, 4	; Type is Portable PC
	mov dx, OFFSET Portable_str
	mov Force_mono, 1	; Portable has monochrome amber screen
	jmp SHORT Suffix

IsXT:	mov PC_Subtype, 1	; Type is PC/XT
	mov dx, OFFSET XT_str
	jmp SHORT Suffix

NotXT:	cmp al, 0FDh		; PCjr?
	 jne Notjr

	mov PC_Subtype, 2	; Type is PCjr
	mov dx, OFFSET jr_str
	jmp SHORT Suffix

Notjr:	cmp al, 0FCh		; PC AT?
	 jne NotAT

	mov PC_Subtype, 3	; Type is PC AT
	mov dx, OFFSET AT_str
	jmp SHORT Suffix

NotAT:	cmp al, 02Dh		; Compaq?
	 jne NotCompaq

	mov PC_Subtype, 90	; Type is Compaq
	mov dx, OFFSET Compaq_str
	mov Force_mono, 1	; Compaq has green monochrome screen
	jmp SHORT Suffix

NotCompaq:
	mov PC_Subtype, 99	; Type is PC clone
	mov dx, OFFSET Clone_str

Suffix:	int Dos			; Finish up DOS call
	ret			; Done here


; CLS command -- used to clear the screen

Clear_screen PROC

	mov ah,cmcfm
	call comnd		; Get a confirm
	 jmp r

	call CmBlnk		; Call routines to clear the screen
	call Locate		;  then home the cursor ...

	jmp RSkp		; Get the prompt

Clear_screen ENDP


; DROP-DTR command -- used to hang up the line

DoDrop	PROC

	mov ah, cmcfm
	call comnd		; Get a confirm
	 jmp r

	call SerRst		; Reset serial port (uninstall interrupt rtn)
	call Drop_DTR		; Call routine to drop DTR
	jmp RSkp

DoDrop	ENDP


; ECHO command -- display some text for the user to see

Echo_command PROC

	mov ah, CMTXT		; Read in a line of text
	mov bx, OFFSET TFile	; Temp area for text, max 100 chars
	mov dx, OFFSET Echo_help ; Simple message for help
	call comnd		; Do it
	 jmp RSkp

	mov di, OFFSET TFile	; Point to start of message
	mov bl, ah		; Copy length to bl
	sub bh, bh		; Clear high half
	add di, bx		; Move to end of message
	mov al, Cr		; Return
	stosb			; Store it
	mov al, Lf		; Line feed
	stosb			; Store it
	mov al, '$'		; Dollar sign
	stosb			; Store it

	mov dx, OFFSET TFile	; Point to text again
	mov ah, PrStr		; Code to type a string
	int Dos			; Type it

	jmp rskp

Echo_command ENDP


; This is the 'exit' command.  It leaves KERMIT and returns to DOS

EXIT	PROC	NEAR

	mov ah, cmcfm
	call comnd		; Get a confirm
	 jmp r

	test flags.capflg,0FFH	; capturing?
	 jz exit2		; no, keep going

	mov dx,offset tmsg5
	mov ah,prstr
	int Dos

	call clscpi
	 nop			; this skip returns..
	 nop
	 nop

exit2:	mov flags.extflg, 1	; Set the exit flag
	jmp rskp		; Then return to system

EXIT	ENDP


; This is the 'help' command.  It gives a very brief intro to the program

HELP	PROC	NEAR
	mov ah,cmcfm
	call comnd		; Get a confirm
	 jmp r
	mov ah,prstr		; Print a string to the console
	mov dx,offset Help_text	; The address of the help message
	int dos
	jmp rskp
HELP	ENDP

lclcmd	proc	near
	mov ah,cmkey
	mov dx,offset loctab
	mov bx,offset lochlp
	call comnd
	 jmp r
	call bx
	nop
	nop
	nop
	jmp rskp
lclcmd	endp

    PUBLIC SetInt

; Don't ignore ^C when in debug mode
SETINT	PROC	NEAR

	push es			; Save reg
	mov ax, 3523h		; Want addr for int 23h
	int Dos			; Get address of this interrupt

	mov WORD PTR in3ad, bx	; Store offset (IP)
	mov ax, es		; Copy es to ax
	mov WORD PTR in3ad+2, ax ; Store the segment (CS)
	pop es			; Get back normal es

	push ds			; Save ds
	mov ax, cs		; Copy cs ...
	mov ds, ax		;  ... to ds
	mov ax, 2523h		; Install new addr for int 23
	mov dx, OFFSET IntBrk	; Addr of routine
	int Dos			; Store addr of our routine for this interrupt
	pop ds			; Restore ds
	ret

SETINT	ENDP

; NPSEND command -- Non-Protocol SEND of a file

Non_protocol_send PROC

	cld			; Forwards
	cmp taklev, maxtak	; Hit our limit?
	 jl NPS_1		;  Continue if still OK

	mov ah,prstr
	mov dx,offset erms38	; Complain
	int dos
	jmp RSkp

NPS_1:	mov di,takadr
	add di,size takinfo
	mov Hold_di, di		; Save reg

	mov ah,cmtxt
	lea bx,[di].takbuf	; convenient place to parse name into
	mov dx,offset filmsg	; Help in case user types "?"
	call comnd		; Parse for a text string
	 jmp RSkp		;  Can't get text string (?), give up

	mov di, Hold_di		; Restore reg
	lea si,[di].takbuf	; get buffer back
	mov bl,ah		; length of thing parsed
	sub bh, bh		; Zero top half
	mov byte ptr [bx+si],0	; make it asciz
	mov ax,si		; point to name again
	call spath		; is it around?
	 jc NPS_2		; no, go complain

	mov di, Hold_di		; Restore reg
	mov dx,ax		; point to name from spath
	mov ah,open2		; 2.0 open call
	mov al,0		; open for reading
	int dos
	 jnc NPS_3		; open ok, keep going

NPS_2:	mov ah,prstr
	mov dx,offset erms31
	int dos
	jmp RSkp

NPS_3:	inc taklev
	mov takadr,di
	mov word ptr [di].takfcb+1,ax	; save file descriptor
	mov byte ptr [di].takfcb,0feh	; mark as 2.0 file descriptor

	mov bx, ax		; need descriptor here
	mov ax, (LSeek*256)+2
	sub cx, cx
	mov dx, cx		; seek 0 bytes from end
	int dos

	mov [di].takcnt,ax
	mov [di].takcnt+2,dx	; store length

	mov ax, (LSeek*256)+0
	sub cx, cx
	mov dx, cx		; now seek back to beginning
	int dos

	call takrd		; Get a buffer full of data

	mov ax, OFFSET cs:Our_entry_point ; Get address of our entry point
	call Set_up_script_processor ; Tell emulator to call us (and where)

	call Telnet2		; Enter terminal emulator
	 nop			;  Ignore skip return
	 nop
	 nop

	jmp RSkp		; All done


;  *****      Enter here on exit hooks from Terminal Emulator     *****

Our_entry_point:
	cld			; Forwards
	or ah, ah		; Is the code 0 (keyboard)?
	 jnz OEP_1		;  No
	jmp SHORT NPS_loop	; Yes, go do our stuff

OEP_1:	cmp ah, 1		; Code 1 (host character)?
	 je OEP_3		;  Yes

	cmp ah, 2		; Code 2 (shutdown)?
	 jne OEP_3		;  No
	jmp NPS_end		; Yes, close files and be ready to be gone

OEP_3:	ret			; Unknown, do nothing for now

NPS_loop:
	call Get_a_char		; Get the next character
	 jc NPS_end		;  No more, go close the file

	mov ah, al		; Copy to ah
	call OutChr		; Send this character
	 jmp NPS_end		;  Failed, give up

	ret			; Return to terminal emulator for now

NPS_end:
	sub ax, ax		; Make a zero
	call Set_up_script_processor ; Make emulator forget us

	mov bx, TakAdr		; Addr of TAKE frame
	mov bx, WORD PTR [bx].TakFcb+1 ; This is where file handle is stored
	mov ah, CLOSE2		; Close file the file handle way
	int Dos

	dec TakLev		; Untake this file
	sub TakAdr, SIZE TakInfo ; Back off space on stack also
	ret			; Done here

Get_a_char:
	mov bx, TakAdr		; Addr of our TAKE frame
	mov ax, [bx].TakCnt	; Get low half of doubleword char count
	or ax, [bx].TakCnt+2	; Merge with high half of doubleword
	 jz GAC_EOF		;  No chars left, flag as end-of-file

	cmp [bx].TakChl, 0	; Disk file, any chars left in buffer?
	 jne GAC_2		;  Yes, don't need more yet

	call TakRd		; Reload buffer from disk file
	cmp [bx].TakChl, 0	; Any chars now?
	 je GAC_EOF		;  No chars even after call to TakRd, EOF!

GAC_2:	dec [bx].TakChl		; Decrement for the char
	sub [bx].TakCnt, 1	; DEC doesn't set carry!!
	sbb [bx].TakCnt+2, 0

	mov si, [bx].TakPtr	; Pick up ptr into buffer
	lodsb			; Pick up next char
	mov [bx].TakPtr, si	; Store the updated ptr

	cmp al, Lf		; Is this a linefeed?
	 jne GAC_3		;  No

	cmp LastChar, Cr	; Was the previous character a return?
	 jne GAC_3		;  This Lf was not preceded by a Cr

	mov LastChar, al	; Update this
	jmp Get_a_char		; Char is Lf following Cr, so don't send it

GAC_3:	mov LastChar, al	; Update this for next time
	cmp al,CtlZ		; Maybe control-z?
	 je GAC_EOF		;  Yes, bomb out

	clc			; No error
	ret

GAC_EOF:
	stc			; Error (EOF)
	ret

Non_protocol_send ENDP


; take commands from a file, but allow a path name
Take	PROC
	cmp taklev,maxtak		; Hit our limit?
	 jl Take1			; Continue if still OK

	mov ah,prstr
	mov dx,offset erms30		; Complain
	int dos
	jmp RSkp

Take1:	mov di,takadr
	add di,size takinfo
	mov ah,cmtxt
	lea bx,[di].takbuf		; convenient place to parse name into
	mov dx,offset filmsg		; Help in case user types "?"
	call comnd
	 jmp RSkp

	call Setup_take_file		; Call common routine to do the work
	 jnc TAK_Got_it			;  Ok so far

	mov ah,prstr
	mov dx,offset erms31
	int dos
	jmp RSkp

TAK_Got_it:
	cmp flags.takflg,0
	 je Take4

	mov ah,prstr
	mov dx,offset crlf
	int dos

Take4:	jmp rskp

Take	ENDP


; Setup_take_file -- routine to set up a new TAKE file in a new TAKE frame
;
;  call with ...
;	di/ New frame address
;	ah/ Length of file name
;	[di].TakBuf/ File name with optional path ... SPATH is used to fix up

	PUBLIC Setup_take_file

Setup_take_file PROC

	mov Hold_di, di		; Save frame address
	lea si, [di].TakBuf	; Get buffer back
	mov bl, ah		; Length of thing parsed
	sub bh, bh		; Zero high half
	mov BYTE PTR [bx+si], 0	; Make it ASCIZ
	mov ax, si		; Point to name again
	call SPath		; Is it around?
	 jc Take2		;  No, go complain

	mov dx, ax		; Point to name from spath
	mov ax, (Open2*256) + 0	; DOS 2.0 open call for read
	int Dos
	 jnc Take3		; Open ok, keep going

Take2:	ret			; Return with carry on ...

Take3:	inc TakLev		; Bump our nested TAKE count
	mov di, Hold_di		; Restore frame address
	mov TakAdr, di		; Advance to next TAKE frame

	mov BYTE PTR [di].TakFcb, 0FEh ; Mark as 2.0 file descriptor
	mov WORD PTR [di].TakFcb+1, 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 [di].TakCnt, ax	; Store length
	mov [di].TakCnt+2, dx	;  (doubleword)

	mov ax, (LSeek*256) + 0	; Seek back to the beginning
	sub cx, cx
	sub dx, dx
	int Dos

	call TakRd		; Get a buffer full of data
	clc			; Flag no carry (no error)
	ret			; Done here

Setup_take_file ENDP


TAKRD	PROC	NEAR
	push bx
	push cx
	push dx
	mov bx,takadr
	cmp byte ptr [bx].takfcb,0feh	; is it a 2.0 file handle?
	jne takrd1			; no, handle differently
	push bx				; save frame address
	lea dx,[bx].takbuf		; buffer to read into
	mov cx,dmasiz			; # of bytes to read
	mov ah,readf2			; 2.0 read call
	mov bx,word ptr [bx].takfcb+1	; file handle is stored here
	int dos
	pop bx				; restore frame address
	jmp takrd2			; rejoin common exit

takrd1:	mov ah,setdma
	lea dx,[bx].takbuf
	int dos
	mov ah,readf
	lea dx,[bx].takfcb
	int dos
	mov ah,setdma
	lea dx, DTA
	int dos
takrd2:	mov [bx].takchl,dmasiz
	lea ax,[bx].takbuf
	mov [bx].takptr,ax
	pop dx
	pop cx
	pop bx
	ret

TAKRD	ENDP

; copy the path into pthbuf
; enter with ax/ environment segment address
; works in 2.0 only
getpath	proc	near
	push	es
	mov	bx,ds
	mov	es,bx			; address data segment
	mov	bx,offset pthnam	; thing to find
	mov	cx,pthlen		; length of it
	mov	dx,offset pthbuf	; place to put it
	mov	byte ptr pthbuf,0	; initialize to null..
	call	getenv			; get environment value
	pop	es
	ret				; and return
getpath	endp

; copy the comspec into cmspbuf
; enter with ax/ environment segment address
; works in 2.0 only
getcsp	proc	near
	push	es
	mov	bx,ds
	mov	es,bx			; address data segment
	mov	bx,offset cmspnam	; thing to find
	mov	cx,cmsplen		; length of it
	mov	dx,offset cmspbuf	; place to put it
	call	getenv			; get environment value
	pop	es
	ret				; and return
getcsp	endp

; find a path variable.  Enter with ax/ environment segment,
; bx/ variable to find (incl =), cx/ length of variable name,
; dx/ address to store value at
; The buffer given in dx is unchanged if the variable isn't found
getenv	proc	near
	push	ds
	push	es
	mov	es,ax			; address segment
	mov	di,0			; offset in segment
geten1:	cmp	es:byte ptr [di],0	; end?
	je	geten4			; yes, forget it
	push	cx			; save counter
	push	di			; and offset
	mov	si,bx
	repe	cmpsb			; is it the one?
	pop	di
	pop	cx			; restore these
	je	geten2			; found it, break loop
	push	cx			; preserve again
	mov	cx,0ffffh		; bogus length
	mov	al,0			; marker to look for
	repne	scasb			; search for it
	pop	cx			; restore length
	jmp	geten1			; loop thru rest of environment
geten2:	add	di,cx			; skip to definition
	mov	si,di			; this is source
	mov	di,dx			; destination as given
	mov	ax,ds
	mov	bx,es
	mov	ds,bx
	mov	es,ax			; exchange segment regs for copy
geten3:	lodsb				; get a byte
	stosb				; drop it off
	cmp	al,0			; end of string
	jne	geten3			; no, go on
geten4:	pop	es
	pop	ds			; restore registers
	ret				; and return
getenv	endp


 %OUT >> About half way through source file


; Put xxxxxxxx.INI onto Take stack if it exists.  Just like
;  the Take command, except it doesn't read a filename

RdInit	PROC

	mov bx, TakAdr		; Pick up "current" take address
	add bx, SIZE TakInfo	; Bump to next frame
	mov si, OFFSET IniNm2	; Name to try
	lea di, [bx].TakBuf	; Ptr to where to put it
	mov cx, IniNm2_len	; Length of name
	cld			; Forwards
	rep movsb		; Copy the string

	mov di, bx		; Copy pointer to TAKE frame to di
	mov ah, IniNm2_len	; Length in ah

	call Setup_take_file	; Call common routine to do the work
	 jnc RDI_Got_it		;  Ok so far

	ret			; If no file, give up !

RDI_Got_it:
	jmp rskp

RdInit	ENDP


; get command line into a macro buffer

gcmdlin	proc	near
	push	ds
	push	es
	cld
	mov	es,psp			; address psp
	mov	ch,0
	mov	cl,es:[cline]		; length of cmd line
	mov	di,cline+1		; point to actual line
	mov	al,' '
	jcxz	gcmdl3			; no command line, forget it
	repe	scasb			; skip over spaces
	je	gcmdl3			; all spaces, forget it
	mov	si,di			; this is first non-space
	dec	si			; pre-incremented..
	inc	cx
	inc	taklev			; bump take level
	add	takadr,size takinfo	; address new take frame
	mov	bx,takadr
	mov	byte ptr [bx].takfcb,0ffh ; mark as a macro
	push	cx			; save length
	push	ds			; and segment
	lea	di,[bx].takbuf		; into take buffer
	mov	ax,ds
	mov	ds,psp
	mov	es,ax			; switch segments for copy
gcmdl1:	lodsb				; get a byte
	cmp	al,','			; comma?
	jne	gcmdl2			; no, keep going
	mov	al,cr			; convert to cr
gcmdl2:	stosb				; deposit it
	loop	gcmdl1			; copy whole cmd
	pop	ds			; restore segment
	pop	cx			; restore len

	lea	ax,[bx].takbuf
	mov	[bx].takptr,ax		; init buffer ptr
	mov	[bx].takchl,cl		; chars remaining
	mov	[bx].takcnt,cx		; and all chars
	mov	[bx].takcnt+2,0		; clear high order
gcmdl3:	pop	es
	pop	ds
	ret
gcmdlin	endp

;	This routine prints the prompt and specifies the reparse address

PROMPT	PROC  NEAR
	mov comand.cmprmp,dx	; save the prompt
	pop bx			; Get the return address
	mov comand.cmrprs,bx	; Save as addr to go to on reparse
	mov comand.cmostp,sp	; Save for later restoral
	push bx			; Put it on the stack again
	mov bx,offset comand.cmdbuf
	mov comand.cmcptr,bx	; Initialize the command pointer
	mov comand.cmdptr,bx
	mov ah,0
	mov comand.cmaflg,ah	; Zero the flags
	mov comand.cmccnt,ah
	mov comand.cmsflg,0FFH
	cmp flags.takflg,0	; look at take flag
	jne promp1		; supposed to echo, skip this check..
	cmp taklev,0		; inside a take file?
	je promp1		; no, keep going

	ret			; yes, return

promp1:	mov ah,prstr		; Print the prompt
	mov dx,comand.cmprmp
	int dos

	ret

PROMPT	ENDP

; Erase specified file(s)
DELETE	PROC	NEAR
	mov comand.cmcr,0	; Filename must be specified
	mov ah,cmifi		; Parse an input filespec
	mov dx,offset fcb
	mov bx,offset filhlp2	; Text of help message
	call comnd
	 jmp r			; Bad filename
	mov ah,cmcfm		; Parse a confirm
	call comnd
	 jmp r
	mov flags.nmoflg, 0	; Function CMIFI leaves this set, clear it
	cld
	mov di,offset fcb+1
	mov al,'?'
	mov cx,11		; # of chars in a name
	repe scasb		; are they all ?'s?
	jne del1		; no, skip message
	mov dx,offset infms1
	call prompt
	mov ah,cmkey
	mov dx,offset yestab
	mov bx,0
	call comnd
	 jmp r
	push bx
	mov ah,cmcfm
	call comnd
	 pop bx
	 ret
	 nop
	pop bx
	cmp bx,0
	jne del1
	jmp rskp
del1:	mov dx,offset fcb
	mov ah,sfirst		; See if any files match this specification
	int dos
	cmp al,0FFH		; No matches?
	jne del2
	mov ah,prstr
	mov dx,offset erms32
	int dos
	jmp rskp
del2:	mov dx,offset fcb
	mov ah,delf		; Erase the file(s)
	int dos
	mov dx,offset infms8
	mov ah,prstr		; Say we did so
	int dos
	jmp rskp
DELETE	ENDP


CHKDSK	PROC

	mov ah,cmcfm
	call comnd
	 jmp r

	mov ah, PrStr			; Code to type string
	mov dx, OFFSET CrLf		; A CrLf
	int Dos				; Type it

	mov si,offset chkdcmd		; point to cmd
	mov cx,chkdlen			; and length
	jmp crun			; and go execute it nicely

CHKDSK	ENDP


DIRECT	PROC
	mov ah,dosver		; See what level of DOS we're at
	int dos
	cmp al,0		; Level 2.0 or above?
	jne dir4		; Yes - get directory the easy way
	mov comand.cmcr,1	; Allow plain CR (so DIR == DIR *.*)
	mov ah,cmifi		; Get input file spec
	mov dx,offset fcb	; Give the address for the FCB
	mov bx,offset filhlp3
	call comnd
	 jmp r
	mov ah,cmcfm		; Parse a confirm
	call comnd
	 jmp r
	mov comand.cmcr,0	; Reset this
	push es
	mov ax,ds
	mov es,ax
	mov temp1,0FFH
	mov di,offset nambuf
dir0:	call getfn		; Get a matching file name
	cmp al,0FFH		; Retcode -- are we done?
	je dir1			; Yes, just leave
	call dumpit		; Print it or dump to buffer
	jmp dir0
dir1:	pop es
	jmp rskp

dir4:	mov si,offset cmspbuf	; command processor
	mov di,offset dirnam
dir5:	lodsb			; get a byte
	or al,al
	jz dir6			; stop on the null
	stosb			; otherwise copy it in
	jmp dir5		; and keep going
dir6:	mov si,offset dircmd	; add directory command to it
	mov cx,dirclen
	rep movsb
	mov ah,cmtxt		; parse with cmtxt so we can have paths..
	mov bx,di		; next available byte
	mov dx,offset filwmsg	; In case user wants help
	mov Hold_di, di
	call comnd
	 jmp r
	mov di, Hold_di
	mov cl,ah
	mov ch,0		; length of name
	sub di,offset dirnam	; compute # of bytes used
	add cx,di
	mov si,offset dirnam	; dir cmd
	jmp crun		; join run cmd from there
DIRECT	ENDP

getfn:	cmp temp1,0FFH
	jne gtfn1
	mov ah,sfirst		; Any matches?
	mov dx,offset fcb
	int dos
	cmp al,0FFH		; Means no matches
	je gtfn5
	call savfcb
	mov temp1,0
	jmp gtfn4
gtfn1:	cmp flags.wldflg,0FFH	; Wilcard seen?
	je gtfn2		; Yes, get next file
	mov al,0FFH		; No, set retcode
	ret
gtfn2:	call rstfcb
	mov ah,snext
	mov dx,offset fcb
	int dos
	cmp al,0		; Any more matches?
	je gtfn3		; Yes keep going
	mov al,0FFH		; OK return code
	ret
gtfn3:	call savfcb
gtfn4:	push di
	mov si,offset DTA	; Data is here
	mov di,offset fcb	; Copy to here
	mov cx,37
	repne movsb
	pop di
	call nicnam		; Make name nice for printing
	mov al,0
	ret
gtfn5:	mov ah,prstr		; Don't print if a server
	mov dx,offset erms32	; Say no matches
	int dos
	mov al,0FFH		; Failure return code
	ret

savfcb:	push di
	mov si,offset fcb	; Data is here
	mov di,offset cpfcb	; Copy to here
	mov cx,37
	repne movsb
	pop di
	ret

rstfcb:	push di
	mov si,offset cpfcb	; Data is here
	mov di,offset fcb	; Copy to here
	mov cx,37
	repne movsb
	pop di
	ret

nicnam:	mov al,CR		; Add CRLF before print names
	stosb
	mov al,LF
	stosb
	mov cx,8
	mov si,offset fcb+1
	repne movsb		; Get the file name
	mov al,' '
	stosb
	mov cx,3
	repne movsb
	mov al,tab
	stosb
	mov al,' '
	stosb
	mov ah,openf
	mov dx,offset fcb
	int dos
	mov bx,offset fcb+18	; Get hi order word of file size
	mov ax,[bx]
	mov dx,ax
	mov bx,offset fcb+16	; Get lo order word
	mov ax,[bx]
	call nout2x		; Get it in decimal
	mov al,tab
	stosb
	mov al,' '
	stosb
	mov ah,0
	mov si,offset fcb+20
	lodsb
	mov fildat+1,al
	lodsb
	mov fildat,al		; Date field of fcb
	mov cl,5
	shr fildat+1,cl
	and fildat,1
	mov cl,3
	shl fildat,cl
	mov al,fildat
	or al,fildat+1		; Get the month field
	cmp al,9
	jg nic0
	push ax
	mov al,' '
	stosb
	pop ax
nic0:	call nout2		; Make it decimal
	mov al,'-'
	stosb
	mov si,offset fcb+20	; Get date field
	lodsb
	and al,1FH
	cmp al,10		; Only one digit?
	jge nic0x
	push ax
	mov al,'0'		; Make it two digits
	stosb
	pop ax
nic0x:	call nout2		; Make it decimal
	mov al,'-'
	stosb
	mov si,offset fcb+21	; Get the year field
	lodsb
	shr al,1
	add al,80
	cmp al,100		; At the year 2000 or above?
	js nic0y		; No, just go on
	sub al,100		; Go back to two digits
nic0y:	cmp al,10		; Only one digit?
	jge nic0z
	push ax
	mov al,'0'		; Make it two digits
	stosb
	pop ax
nic0z:	call nout2		; Make it decimal
	mov al,tab
	stosb
	mov si,offset fcb+23	; Get time field of fcb
	lodsb
	mov cl,3		; Get the hour field
	shr al,cl
	mov tmp,'a'		; For AM
	cmp al,12		; Before noon?
	jl nic1
	mov tmp,'p'		; It's PM
	je nic1			; Don't change "12" to "0"
	sub al,12		; Use a 12 hr. clock
nic1:	cmp al,0		; Just after midnight?
	jne nic1x
	add al,12		; Make it "12" instead of "0"
nic1x:	cmp al,10		; Pad with a space?
	jge nic2
	push ax
	mov al,' '
	stosb
	pop ax
nic2:	call nout2		; Make it decimal
	mov al,':'		; Separate hours and minutes
	stosb
	mov si,offset fcb+23	; Get the minutes field
	lodsb
	and al,07
	mov cl,3
	shl al,cl
	mov ah,al
	mov si,offset fcb+22
	lodsb
	mov cl,5
	shr al,cl
	or al,ah
	mov ah,0
	cmp al,10		; Would there be a leading zero
	jge nic3
	push ax
	mov al,'0'
	stosb
	pop ax
nic3:	call nout2		; Make it decimal
	mov al,tmp		; Add 'a' (AM) or 'p' (PM)
	stosb
	mov ah,closf
	mov dx,offset fcb
	int dos
	ret

; For now, just print it
dumpit:	mov al,'$'
	stosb
	mov ah,prstr
	mov dx,offset nambuf
	int dos
	mov di,offset nambuf
	ret

; Push to an inferior command parser

DoPush	PROC

	mov	ah,cmcfm		; Confirm the command
	call	comnd
	 jmp	r


; Entry to pre-confirmed Push command ...
;  so we can Push from FT screen, for example ...

Quick_Push:
	mov	si,offset cmspbuf	; name of parser
	push	si			; save beginning
	sub	cx,cx			; initial length
dopus2:	lodsb
	inc	cx			; count this
	or	al,al			; at end?
	jnz	dopus2			; no, keep going
	pop	si			; restore cmd
	dec	cx			; this is incremented one over
	jmp	short crun		; go run it
dopush	endp

; crun - run an arbitrary program.  Enter with si/address of whole
; cmd, cx/length of cmd
CRUN	proc	near
	push cx			; save length of cmd
	mov ax,ds
	mov es,ax		; address dest segment
	mov di,offset nambuf
	rep movsb		; copy command so we can mess with it
	pop cx
	mov si,offset nambuf	; point to command
	jmp short run3		; and join run code
CRUN	ENDP

RUN	PROC	NEAR
	mov ah,cmtxt		; Get program name
	mov bx,offset nambuf	; Convenient buffer
	mov dx,offset filmsg	; In case user wants help
	call comnd
	 nop
	 nop
	 nop
	cmp ah,0
	jne run2
	mov ah,prstr
	mov dx,offset erms35
	int dos
	jmp rskp
run2:	mov cl,ah
	mov ch,0
	mov si,offset nambuf

; alternate entry if cmd is already known.  Source cmd ptr in si
; is trashed
run3:	mov bx,cx
	mov byte ptr [si+bx],cr	; end string with a cr for dos
	mov di,offset cmdnam
	mov ax,ds
	mov es,ax
run4:	lodsb
	cmp al,' '
	jne run5
	dec si			; back up over space
	jmp short run6		; and exit loop
run5:	stosb
	loop run4
run6:	mov byte ptr [di],0	; terminate string
	dec si			; point back a byte into argument
	mov [si],cl		; put length of argument here
	mov exearg+2,si		; pointer to argument string
	mov exearg+4,ds		; segment of same
	inc si			; pass length over
	mov al,1		; scan leading separators
	mov di,offset exefcb	; parse into this fcb
	mov ah,prsfcb
	int dos			; go parse the fcb
	mov al,1		; scan leading separators
	mov di,offset exefcb2	; second fcb to fill
	mov ah,prsfcb
	int dos			; parse the fcb
	mov ax,ds
	mov es,ax		; put es segment back
	mov ax,offset cmdnam	; point to cmd name again
	call spath		; look for it
	 jc run8		;  not found, go complain
	mov dx,ax		; point to command name


	call Restore_normal_error_handling ; Return to normal error handler

; At this point, we are about to run the program.  But first, save the
;  current settings of the three "user vectors", "Terminate", "Ctrl-Break Exit"
;  and "Critical Error Handler".  Seems DOS 2.0 and 2.1 don't restore them
;  properly after this call.   The program worked under DOS 3.0 and 3.1
;  without this code

	push es			; First, save the incoming es

	mov ax, 3522h		; Code to Get Vector, starting at 22h
	int Dos			; Get it

	push es			; Save his segment
	push bx			;  and offset

	inc al			; 23h -- second vector to get
	int Dos			; Get it

	push es			; Save his segment
	push bx			;  and offset

	inc al			; 24h -- third vector to get
	int Dos			; Get it

	push es			; Save his segment
	push bx			;  and offset


; Run the program

	mov ax, ds		; Copy ds
	mov es, ax		;  to es
	mov ax, (256*Exec)+0	; Code to load and execute a program
	mov bx, OFFSET exearg	; Point to arguments
	mov ssave, sp		; Save stack ptr
	int Dos			; Go run the program


; Just came back from (maybe) running the program, have to restore some
;  fundamental items first

	mov ax,seg datas
	mov ds, ax		; reset data segment

	mov ax,seg stack
	mov ss,ax		; and stack segment
	mov sp,ssave		; restore stack ptr


; Now restore the things DOS 2.0 and 2.1 let be clobbered

	mov ah, 25h		; Code to Set Interrupt Vector
	mov al, 24h		; Vector to restore
	pop dx			; Get back offset
	pop ds			; Get back segment
	int Dos			; Set it up

	mov al, 23h		; Vector to restore
	pop dx			; Get back offset
	pop ds			; Get back segment
	int Dos			; Set it up

	mov al, 22h		; Vector to restore
	pop dx			; Get back offset
	pop ds			; Get back segment
	int Dos			; Set it up

	pop es			; Get back saved es reg
	mov ax, SEG DataS	; DS just got clobbered
	mov ds, ax		; Fix it up again

	call Set_up_error_handling ; Establish our routine as the one to use

	pushf			; Save flags
	mov ah, SetDMA
	mov dx, OFFSET DTA
	int Dos			; Restore DTA address!!
	popf			; Recover flags

	 jc Run8		;  Error

	mov ah, PrStr
	mov dx, OFFSET CrLf
	int Dos			; Type another blank line

	jmp rskp		; Return

run8:	mov ah,prstr
	mov dx,offset erms37
	int dos

	jmp rskp

RUN	ENDP

; the show command
showcmd	proc	near
	mov	ah,cmkey
	mov	dx,offset shotab
	xor	bx,bx			; no canned help
	call	comnd
	 jmp	r
	call	bx			; call the handler
	 jmp	r
	jmp	rskp			; and return
showcmd	endp

    PUBLIC IntBrk

IntBrk	PROC FAR

	push ax			; Interrupt routine, not allowed to
	push ds			;  clobber registers
	pushf			; Save flags also

	mov ax, SEG Datas
	mov ds, ax

	cmp flags.debug, 1	; Debug mode?
	 je IntB2		;  Yes, then don't ignore the ^C

	mov flags.cxzflg, 'C'	; Say we saw a ^C
	mov pack.state, 'A'	; Set the state to abort
	popf			; Restore flags
	pop ds			; Restore regs
	pop ax

	cmp ah, 09h		; Were we typing a string?
	 jne IntBl1		;  No

	add sp, 6		; We want to return to AFTER the "int Dos" that
				;  started us typing ... otherwise this call
				;  retypes the string completely!

IntBl1:	iret			; That's it for this ^C

IntB2:	popf			; Restore flags
	pop ds			; Restore regs
	pop ax
	jmp in3ad		; Original break interrupt address

IntBrk	ENDP


; Set the maximum data packet size. [21b]

PACKLEN	PROC	NEAR
	mov ah,trans.spsiz	; Maximum send packet size
	sub ah,4		; Size minus control info
	sub ah,trans.chklen	; And minus checksum chars
	sub ah,2		; Leave room at end: 2 for possible #X
	cmp trans.ebquot,'N'	; Doing 8-bit quoting?
	je pack0		; Nope so we've got our size
	cmp trans.ebquot,'Y'
	je pack0		; Not doing it in this case either
	sub ah,1		; Another 1 for 8th-bit quoting
pack0:	mov trans.maxdat,ah	; Save max length for data field
	ret
PACKLEN	ENDP

NOUT2	PROC	NEAR
	push ax
	push dx
	mov temp,10		; Divide quotient by 10
	cwd			; Convert word to doubleword
	div temp		; AX <-- Quo, DX <-- Rem
	cmp ax,0		; Are we done?
	jz nout0		; Yes
	call nout2		; If not, then recurse
nout0:	add dl,'0'		; Make it printable
	mov temp,ax
	mov al,dl
	stosb
	mov ax,temp
	pop dx
	pop ax
	ret			; We're done. [21c]
NOUT2	ENDP

NOUT2X	PROC	NEAR
	push ax
	push dx
	push cx
	mov temp,10		; Divide quotient by 10
	div temp		; AX <-- Quo, DX <-- Rem
	mov cx,dx		; Remember the remainder
	cmp ax,0		; Are we done?
	jz nout0x		; Yes
	mov dx,0
	call nout2		; If not, then recurse
nout0x:	add cl,'0'		; Make it printable
	mov temp,ax
	mov al,cl
	stosb
	mov ax,temp
	pop cx
	pop dx
	pop ax
	ret			; We're done. [21c]
NOUT2X	ENDP

SPATH	PROC

; enter with ax/ ptr to file name.  Searches path for given file,
; returns with ax/ ptr to whole name, or carry on if file isn't
; to be found

; First, see if file is in connected directory

	push ax
	call isfile		; See if file exists in current dir
	pop ax			; AX is clobbered
	 jc spath_0		;  Not here, look down PATH ...

	jmp SPath_Exit		; That was easy ... found file

spath_0:
	push	es
	mov	bx,ds
	mov	es,bx		; address data segment
	mov	bx,ax		; convenient place to keep this
	mov	si,ax
	mov	di,offset tfile ; place to copy to
	mov	dl,0		; no '\' seen yet
	mov	ah,swchar	; get switch character
spath1:	lodsb
	stosb
	cmp	al,ah		; contain path characters?
	jne	spath2		; no, keep going
	mov	dl,1		; remember we've seen them
spath2:	or	al,al
	jnz	spath1		; copy name in
	or	dl,dl		; look at flag
	jz	spath3		; no path embedded, keep going
	pop	es
	mov	ax,offset tfile	; else..
	call	isfile
	mov	ax,offset tfile	; point to right thing..
	jmp SPath_Exit		; let isfile decide and return

; no path, keep going
spath3:	mov	si,offset pthbuf ; path definition
	cmp	byte ptr [si],0	; empty path?
	jne	spath4		; no, keep going
	mov	ah,gcd		; get current dir
	mov	dl,0		; for default drive
	mov	si,offset defpth+1 ; place to put it
	int	dos
	mov	si,offset defpth ; point to the path

spath4:	cmp	byte ptr [si],0	; null, exit loop
	je	spath9
	mov	di,offset tfile	; place to put name

spath5:	lodsb			; get a byte
	cmp	al,';'		; end of this part?
	je	spath7		; yes, break loop
	cmp	al,0		; maybe end of string?
	jne	spath6		; no, keep going
	dec	si		; back up over it
	jmp	short spath7	; and break loop

spath6:	stosb			; else stick in dest string
	jmp	spath5		; and continue

spath7:	push	si		; save this ptr
	mov	si,bx		; this is user's file name
	mov	al,swchar	; get switch character
	cmp	byte ptr [di-1],al ; does it end with switch char?
	je	spath8		; yes, don't put one in
	stosb			; else add one

spath8:	lodsb
	stosb
	or	al,al
	jnz	spath8		; copy rest of name
	pop	si		; restore pos in path def
	mov	ax,offset tfile
	call	isfile		; is it a file?
	jc	spath4		; no, keep looking
	mov	ax,offset tfile
	pop	es
	jmp SPath_Exit		; return success (carry off)

spath9:	pop	es		; restore this
	mov	ax,bx		; not found yet, get original path
	call	isfile		; does it exist?

SPath_Exit:
	ret

spath	endp


Set_up_error_handling PROC

	cmp Error_handling_installed, 0 ; Have this in already?
	 jne SUE_Z		;  Yes, quit

	mov Error_handling_installed, 1 ; Flag as already done

	pushf			; Save these regs
	push ax
	push bx
	push dx

	push es			; Save starting value of es
	mov ax, 3524h		; Code to get current interrupt vector 24h
	int Dos			; Get address of this interrupt

	mov WORD PTR cs:Critical, bx ; Store offset (IP)
	mov ax, es		; Copy es to ax
	mov WORD PTR cs:Critical+2, ax ; Store the segment (CS)
	pop es			; Get back normal es

	push ds			; Save ds
	mov ax, cs		; Copy cs ...
	mov ds, ax		;  ... to ds
	mov ax, 2524h		; Install new addr for int 24
	mov dx, OFFSET Handle_error ; Addr of routine
	int Dos			; Store addr of our routine for this interrupt
	pop ds			; Restore ds

	pop dx			; Restore regs
	pop bx
	pop ax
	popf

SUE_Z:	ret

Set_up_error_handling ENDP


Restore_normal_error_handling PROC

	cmp Error_handling_installed, 0 ; Anything to undo?
	 je RNE_Z		;  No, quit

	mov Error_handling_installed, 0 ; Flag as back to normal

	pushf			; Save these regs
	push ax
	push dx
	push ds

	mov dx, WORD PTR cs:Critical ; Set up the offset
	mov ax, WORD PTR cs:Critical+2 ; Set up the segment
	mov ds, ax		;  in ds ...

	mov ax, 2524h		; Code to set interrupt vector 24h
	int Dos			; Store addr of original routine

	pop ds			; Restore regs
	pop dx
	pop ax
	popf

RNE_Z:	ret

Restore_normal_error_handling ENDP


	PUBLIC Handle_error, Get_Error
;	PUBLIC Set_up_error_handling
;	PUBLIC Restore_normal_error_handling

Handle_error PROC FAR

	push bx			; Save bx
	mov bx, di		; Copy di to bx
	sub bh, bh		; Clear the high half
	mov CS:Last_Critical_Error, bx ; Save for non-interrupt level rtns
	or ah, ah		; Test for disk errors
	 js Not_disk		;  It isn't disk

	cmp bl, 0		; Compare with code for "Write protect"
	 je Fail_It		;  Yes, make the original DOS call fail
	cmp bl, 2		; Compare with code for "Drive not ready"
	 je Fail_It		;  Yes, make the original DOS call fail
	jmp SHORT As_usual	; Treat it like any error

Not_disk:
	cmp bl, 09h		; Compare with code for "Printer out of paper"
	 je Fail_It		;  Yes, make the original DOS call fail
	cmp bl, 0Ah		; Compare with code for "Write fault"
	 je Fail_It		;  Yes, make the original DOS call fail
;	jmp SHORT As_Usual	; Fall thru to ...

As_Usual:
	pop bx			; Restore bx
	jmp cs:Critical		; Go run original critical error handler

Fail_It:
	pop bx			; Restore bx

; We must now set things up as if the original function call had failed ...

	add sp, 6		; Flush DOS's 24h return regs

; IBM PC-DOS 2.0 manual (page D-7) says that you can "return to the program
;  immediately after the INT 21 that experienced the error.  Note that if
;  this is done, DOS will be in an unstable state until a function call
;  higher than 12 is issued."
; Based on this warning, lets do a harmless high numbered DOS call
;  to "stabilize" DOS

	mov ah, 30h		; Code to get vendor number
	int Dos			; Ask Dos to stabilize itself

; Restore registers to what they were before the "failing" call

	pop ax
	pop bx
	pop cx
	pop dx
	pop si
	pop di
	mov bp, sp		; Copy stack ptr to bp
	or WORD PTR [bp+10], 1	; Turn on carry bit also, to cover all cases
	pop bp			; Then restore real bp
	pop ds
	pop es
	mov al, 0FFh		; Set up al to look like a failure
	iret			; Return from interrupt to the original
				;  "failing" Dos call

Handle_error ENDP


Get_Error PROC

	mov ax, Last_Critical_Error ; Pick this up
	mov Last_Critical_Error, 0FFh ; Make this impossible
	ret			; Run with it

Get_Error ENDP


;   Returns with carry off if the file pointed to by ax exists

isfile	PROC
	mov	dx,ax		; copy ptr
	mov	ax, (chmod*100h)+0 ; chmod function, read file attribute
	int	dos
	ret			; dos sets carry
isfile	endp


; Get_memory_block -- Get a single block of memory
;
; Call with ...
;	bx/ number of paragraphs of memory requested
;
; Returns with ...
;	carry flag set if failure
;	ax/ high-order 16 bits of address of block if success

Get_memory_block PROC

	mov ax, Count_of_allocated_blocks ; Get current index
	cmp ax, Max_count_of_blocks ; Too high?
	 jae GMB_bomb		;  Too many already

	mov ah, 48h		; Code to get a memory block
	int Dos			; Ask DOS for the memory
	 jc GMB_bomb		;  No such luck

	mov bx, Count_of_allocated_blocks ; Get current index
	shl bx, 1		; Double for word offset
	mov Block_table[bx], ax	; Store ptr to the block
	inc Count_of_allocated_blocks ; Update index
	ret			; Done here

GMB_bomb:
	stc			; Set error flag if not already set
	ret			; Return failure

Get_memory_block ENDP


; Return_memory_block -- Return a single block of memory
;
; Call with ...
;	bx/ high-order 16 bits of address of block to be returned

Return_memory_block PROC
	ret			; Not sure we need this routine
Return_memory_block ENDP


; Return_all_memory -- Return all allocated blocks of memory

Return_all_memory PROC

	push es			; We modify es
	mov cx, Count_of_allocated_blocks ; The number of blocks we have
	jcxz RAM_done		; Done if no blocks
	sub bx, bx		; Start at index zero

RAM_loop:
	mov ax, Block_table[bx] ; Get the next entry
	mov es, ax		; Copy to es
	mov ah, 49h		; Code to Free Allocated Memory
	int Dos			; Ask DOS to release this memory

	inc bx			; Move to word in table
	inc bx
	loop RAM_loop		; Go do the next one if any

RAM_done:
	pop es			; Restore es
	mov Count_of_allocated_blocks, cx ; Zero the count of blocks
	ret			; Go home

Return_all_memory ENDP


; Routines to disable and reenable interrupts

NoInt	PROC

	cli			; We want interrupts off
	inc NoInt_Count		; Bump our nested count
	ret			; That's it

NoInt	ENDP

OkInt	PROC

	dec NoInt_Count		; Unbump our nested count
	 jnz OkInt_unchanged	;  Still nested in a NOINT, just return

	sti			;  Back to unnested state, turn ints back on

OkInt_unchanged:
	ret			; Done here

OkInt	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 of code section

Stack   SEGMENT PARA STACK 'STACK'
	DW 512 DUP(?)		; Our stack
Stk	EQU THIS WORD
Stack   ENDS

	END Start
