; This is version 1.01 of HILOAD
;
;		See accompanying .DOC file for theory of
;		operation and instructions
;
;		No guarantees, but the author would appreciate
;		information on bugs.
;
;		Larry Shannon 18 Jun 89
;
; Equates
;
		CR	EQU	0D
		LF	EQU	0A
		SPACE	EQU	020
		MON	EQU	MOV	; Common typo
		ONE	EQU	1
		TWO	EQU	2
		DOLLAR	EQU	024
		ZERO	EQU	0
		BELL	EQU	07
;.....
;
; Macros
;
PRINT		 MACRO			; Usage: print MSG1
		PUSH	AX		; All registers preserved
		PUSH	DX
		MOV	AH,9
		LEA	DX, #1
		INT	021
		POP	DX
		POP	AX
		#EM
;
PRINT$		 MACRO			; Usage: print$,"MESSAGE"
		PUSH	AX		; Prints at current cursor position
		PUSH	DX
		JMP	>M1
M0:
		DB	#1
		DB	"$"
M1:
		MOV	AH,9		; All registers preserved
		LEA	DX,M0
		INT	021
		POP	DX
		POP	AX
		#EM
;
QUIT		 MACRO			; Usage: quit 0
		MOV	AH,04C		; Must give some value
		MOV	AL,#1
		INT	021
		#EM
;
NEWLINE		 MACRO			; Equivalent to CR,LF
		PUSH	AX		; Scrolls if off screen
		PUSH	DX
		JMP	> M1
M0:
		DB	0D,0A,'$'
M1:
		LEA	DX,M0
		MOV	AH,9
		INT	021
		POP	DX
		POP	AX
		#EM
;
SPLIT		 MACRO	AAM 16	#EM	; Be careful with these with
;					; Nec v-20/v-30 chips!
UNSPLIT		 MACRO			; This macro will effectively
		PUSH	CX		; Do the same thing, and should be
		MOV	CX,4		; Compatible with all pc's
		ROL	AH,CL
		AND	AH,0F0
		OR	AL,AH
		POP	CX
		#EM
;
HEX_TO_PRNT	 MACRO			; Changes an 8-bit register quantity
		OR	#1,030		; From 0-f to the ascii equivalent
		CMP	#1,039		; Register must be in form 00 to 0f
;
		 IF	A ADD #1,7
		#EM
;
PRINT_REG	 MACRO			; Used to print a register (or any 16-bit
		JMP	> M1		; Quantity) at the current cursor position
M0:
		DB	5 DUP ('$')
M1:
		PUSH	AX		; Usage - print_reg AX
		MOV	AX,#1		; Prints the 16-bit quantity in hex
		XCHG	AH,AL
		PUSH	AX
		MAKE_HEX_ASCII
		XCHG	AH,AL
		MOV	W[M0],AX
		POP	AX
		XCHG	AH,AL
		MAKE_HEX_ASCII
		XCHG	AH,AL
		MOV	W[M0+2],AX
		PRINT	M0
		POP	AX
		#EM
;
PRINT_32_DEC	 MACRO			; Macro to print a 32-bit hex number
		PUSH	AX		; As a decimal string
		PUSH	DX		; Enter with si pointing to two-word
		PUSH	DI		; (four byte) quantity to be converted
		MOV	DX,W[SI]	; Most significant word first
		MOV	AX,W[SI+2]	; Prints at current cursor position
		JMP	> M1		; All registers restored
M0:
		DB	11 DUP ('$')
M1:
		LEA	DI,M0
		ADD	DI,9
		CALL	LARGE_HEX_TO_ASCII
		MOV	DX,DI
		MOV	AH,9
		INT	021
		POP	DI
		POP	DX
		POP	AX
		#EM
;
REG_TO_PRINT	 MACRO			; Converts a 16-bit quantity to ascii
		PUSH	AX		; And puts the resultant string in a
		PUSH	BX		; Place pointed to by ds:si
		MOV	BX,#1		; All registers restored
		MOV	AL,BH
		CALL	HEX2PRNT
		MOV	[SI],AX
		MOV	AL,BL
		CALL	HEX2PRNT
		MOV	[SI+2],AX
		POP	BX
		POP	AX
		#EM
;
MAKE_CAP	 MACRO			; Makes letters capitals
		CMP	#1,061		; Useage  make_cap AH
		JB	> M1		; Ah contains a candidate ascii letter
		CMP	#1,07A		; From A-Z or a-z
		JA	> M1		; Returns A-Z
		AND	#1,0DF
M1:
		NOP
		#EM
;
MAKE_HEX_ASCII	 MACRO			; Makes al in hex into two ascii
		SPLIT			; Characters in ah:al
		HEX_TO_PRNT AH
		HEX_TO_PRNT AL
		#EM
;.....
;
; Data and strings
;
;
		ORG	0100
;
;
HILOAD:		JMP	LONG > L0

LOAD_BLOCK	DW	0,0
FILE_STRING	DB	65 DUP('$')
TAIL_PTR	DW	0
FILE_PTR	DW	0
OLD_INT_27	DW	0,0
OLD_INT_21	DW	0,0
START_ADDR	DW	0100,0
TAIL_BFR	DB	127 DUP	('020')
SW2127		DB	0
TEMPWRDS	DW	0,0
BAR		DB	40 DUP ('*'),'$'
RESET_MSG	DB	CR,LF,'Resetting ICA area',CR,LF,'$'
DEFAULT_ENV	DB	'HILOAD',0
INT_MSG		DB	'Interupt '
INT_NUM		DB	0,0,'H is now located at '
INT_SEG		DB	0,0,0,0,':'
INT_OFF		DB	0,0,0,0,CR,LF,'$'
TEMP		DW	0
LOAD_POINT	DW	0,0
LP_PLUS_BYTES	DW	0,0
INT_ADDR_1	DW	0
INT_ADDR_2	DW	0
;
NEW_INT21:
        	CMP	AH,031		; New address for int 21h
		JE	> Z21		; Is it TSR termination?
		CMP	AX,02521	; Setting vector for int 21h?
		JE	> Z25		; If so, save it
		CMP	AX,03521	; Request for int 21h address?
		JE	> Z35		; If so, give him original address
		CS	JMP D[OLD_INT_21] ; Otherwise, do real int 21h
Z25:
		CS	MOV OLD_INT_21,DX ; Here we save int 21h address info
		CS	MOV OLD_INT_21+2,DS
		IRET			; And return to caller
Z35:
		CS	MOV BX,[OLD_INT_21] ; Here we give him original address
		CS	MOV ES,[OLD_INT_21+2]
		IRET			; And return to caller
Z21:
		CS	MOV SW2127,0	; Exiting via int 21 - set switch for
		JMP	> Z22		; Storage return handling
;.....
;
NEW_INT27:
		CS	MOV SW2127,1	; Trap for int 27h - set switch
Z22:
		MOV	AX,CS		; Get our code segment
		MOV	BX,DX		; Save return size info
		MOV	DS,AX		; Set up local addressability
		MOV	ES,AX		; Make sure other registers are
		MOV	SS,AX		; What they should be
		MOV	DX,[OLD_INT_27]	; Find original address of int 27h
		MOV	AX,[OLD_INT_27+2] ; Segment portion
		MOV	ES,0		; Addressing bottom page
		ES	MOV W[09C],DX	; Store vector addresses directly
		ES	MOV W[09E],AX	; Since int 21h cannot be used to do
		MOV	DX,[OLD_INT_21]	; The job - we've trapped it above!
		MOV	AX,[OLD_INT_21 + 2] ; Do the same with int 21h
		MOV	ES,0
		ES	MOV W[084],DX	; And store its address in the
		ES	MOV W[086],AX	; Proper place in the table
		NEWLINE			; Space a line
		PRINT$	'Saved bytes = ' ; Start of message
		CMP	SW2127,1	; See how we terminated; 1=int 27h so
		JE	> Q2		; Returns bytes - int 21h returns para-
		MOV	AX,BX		; Graphs - here we're converting
		XOR	DX,DX		; Paragraphs to bytes by multiplying
		MOV	CX,4		; By 4 (shift left 4 places)
Q1:
		RCL	AX,1		; Have to allow for greater than 64k
		PUSHF			; Save flags with state of carry bit
		ROL	DX,1
		POPF			; Get flags back
		ADC	DX,0		; Add in and include carry
		LOOP	Q1		; Carry on
		JMP	> Q3
Q2:
		MOV	AX,BX		; Size info in bx - do setup for
		XOR	DX,DX		; Conversion
Q3:
		MOV	TEMPWRDS,DX	; Temporarily store upper and lower
		MOV	TEMPWRDS + 2,AX	; Halves of return size info (32 bits)
		PUSH	DX		; Save dx
		MOV	DX,[LOAD_POINT]	; Get where we started
		MOV	LP_PLUS_BYTES,DX ; Put it here
		POP	DX		; Retrieve dx
		ADD	LP_PLUS_BYTES,DX ; Add size info
		MOV	LP_PLUS_BYTES+2,AX ; Store upper half here
		LEA	SI,TEMPWRDS	; Point to temporary storage
		PRINT_32_DEC		; Print out size in bytes
		NEWLINE			; Do new line
		MOV	DX,[TEMPWRDS]	; Get back size info
		MOV	AX,[TEMPWRDS+2]
		CLC			; Clear the carry
		MOV	CX,4		; We're going to convert to a 32-bit
Q7:
		SHR	DX,1		; Number here and store it in
		PUSHF			; The ica area
		RCR	AX,1
		POPF
		LOOP	Q7
		MOV	ES,040		; Point to segment of ica
		ES	MOV BX,[0F0]	; Get what was there
		ADD	DX,BX		; Add new size
		ES	MOV [0F0],DX	; And put it back
		ES	MOV BX,[0F2]	; Get old lower half
		ADD	AX,BX		; Add new bytes saved data
		INC	AX		; Allow for truncation
		ES	MOV [0F2],AX	; And put it back

		NEWLINE			; Start of trap info
		PRINT$	'This TSR traps the following interrupts:'
		NEWLINE			; Some spaces
		NEWLINE
;
; Here is where we look to see what interrupts are trapped
;
		MOV	CX,0FF		; Look at 256 interrupts
		MOV	AX,03500	; Set up call
H1:
		PUSH	CX		; Save registers
		PUSH	AX
		INT	021		; Get interrupt addresses
		MOV	AX,BX		; Ax is offset
		MOV	DX,ES		; Dx:ax is addr of interrupt
		MOV	INT_ADDR_1,AX	; Store offset away
		MOV	INT_ADDR_2,DX	; Store segment
		CALL	ADD_SEG_OFF	; Convert to 32-bit number
		XCHG	BX,DX		; Now bx:cx is int addr
		XCHG	CX,AX		; In 32-bit form
		MOV	DX,[LOAD_POINT]	; Where do we start
		XOR	AX,AX		; No offset from start
		CALL	ADD_SEG_OFF	; Make 32-bitter
		CALL	CMP_32		; Is int address beyond start point?
		JNC	> H2		; If below, can't be us - get out
		MOV	DX,[LP_PLUS_BYTES]
		MOV	AX,[LP_PLUS_BYTES + 2] ; Dx:ax now address of end of pgm
		CALL	ADD_SEG_OFF	; Make 32-bits
		CALL	CMP_32		; Compare them
		JC	> H2		; If greater - beyond me
		POP	AX		; We got one - retrieve interrupt
		PUSH	AX		; And save function and int number
		CALL	HEX2PRNT	; Get interrupt number in ascii
		LEA	SI,INT_NUM	; Point to proper place
		MOV	W[SI],AX	; Store it
		LEA	SI,INT_SEG	; Point to place
		REG_TO_PRINT [INT_ADDR_2] ; Get segment in ascii
		LEA	SI,INT_OFF	; Point to place
		REG_TO_PRINT [INT_ADDR_1] ; Print offset in ascii
		PRINT	INT_MSG		; Print whole message
H2:
		POP	AX		; Get registers back
		INC	AX
		POP	CX
		DEC	CX
		JCXZ	> H3		; Are we done?
		JMP	LONG H1		; No - go back for more
H3:
		QUIT	0		; We're done - exit
;.....
;
;=======================================================================
; Main program starts here
;=======================================================================
;
L0:
		NEWLINE			; Give us some space
		PRINT	BAR		; Print banner bar
		NEWLINE			; Another space
		MOV	AH,[080]	; See if any comand tail
		CMP	AH,1
		JA	> L1
L00:
		MOV	ES,040		; If no tail, reset ica
		ES	MOV W[0F0],0
		ES	MOV W[0F2],0
		PRINT	RESET_MSG	; Write message
		QUIT	0		; And get out - normal exit
L1:
		MOV	DI,080		; Point to command tail
L22:
		INC	DI
		MOV	AH,[DI]
		CMP	AH,020		; Bumping past the spaces here
		JE	L22
		MOV	FILE_PTR,DI	; Point to file name
L222:
		MOV	AH,[DI]
		CMP	AH,SPACE
		JE	> L555
		CMP	AH,CR
		JE	> L555
		INC	DI
		JMP	L222
		MOV	AL,CR		; Look for carriage return
		MOV	CX,0100		; Look for a long time!
		CLD
		REPNE	SCASB
		JCXZ	L00		; Get out if no find one
		DEC	DI
L555:
		MOV	TAIL_PTR,DI	; Save pointer to command tail
		PUSH	DI
		LEA	BX,TAIL_BFR	; Point to command tail (saved)
L16:
		MOV	AH,[DI]		; Get element
		CMP	AH,CR		; End of tail?
		JE	> L17
		MOV	[BX],AH		; Stuff it away
		INC	DI		; Bump pointers
		INC	BX
		JMP	L16		; Do again
L17:
		MOV	B[BX],CR	; Terminate tail
		POP	DI
		LEA	BX,FILE_STRING	; Point to buffer to store file name
		MOV	DI,[FILE_PTR]	; Get pointer to program in command tail
L33:
		MOV	AH,[DI]		; Build the name, char by char
		MAKE_CAP AH		; Make sure all caps
		MOV	[BX],AH		; Stuff it away
		INC	BX		; Bump pointers
		INC	DI
		CMP	DI,[TAIL_PTR]	; Are we done?
		JB	L33		; If not, carry on
		MOV	W[BX],'C.'	; Stick in ".COM" extension
		MOV	W[BX+2],'MO'	; Reversed - that's the way intel works
		MOV	B[BX+4],0	; End with 0 to make asciiz string
		LEA	DX,FILE_STRING	; Point dx to the file
		MOV	AH,04E		; Find file
		MOV	CX,027		; File attribute byte - this works
		INT	021		; Do it
		JC	> L77		; Did we find it?
		JMP	LONG L78	; Got it! go process it
L77:
		NEWLINE			; Couldn't find file
		NEWLINE
		PRINT$	'Cant find file ' ; So say so
		MOV	B[DI-1],'$'	; Put in string delimiter and print
		PRINT	FILE_STRING	; What we were looking for
		PRINT$	' ...ABORTING'	; Sorry charlie
		NEWLINE
		NEWLINE
		QUIT	3		; Quit with error level = 3
L78:
		MOV	AL,' '		; Looking for a space
		MOV	CX,100		; Up to 256 bytes back
		STD			; Scan backwards
		REPNE	SCASB		; And search
		LEA	DX,FILE_STRING	; Ok, now at start of file string
		PUSH	DI		; Save pointer
		MOV	B[DI-1],0	; Make asciiz string
		PUSH	BX
		PUSH	CX
		MOV	AH,030		; Check dos version level
		INT	021
		POP	CX
		POP	BX
		CMP	AL,2		; At least version 2?
		JAE	> V1
		NEWLINE			; If not, dump out
		PRINT$	'Requires DOS 2.0 or above ... ABORTING'
		NEWLINE
		QUIT	2		; Bad dos version - error level = 2
V1:
		CMP	AL,3		; Version 3.0 or above?
		JAE	> V2		; If it is, we're ok
		LEA	SI,DEFAULT_ENV	; Set up default envir var name
		JMP	> V3
V2:
		CALL	WHOAMI		; Find out this programs'name
V3:
		CALL	GET_ENV_VAR	; Get the environment value
		JC	> L88		; Did we get a match?
		JMP	LONG L888	; Yes, we did
L88:
		NEWLINE			; No we didn't
		PRINT$	'No address given ... ABORTING'
		QUIT	1		; Bad environment variable - error
L888:
		PUSH	DS
		PUSH	ES
		POP	DS
		CALL	ASC_2_NUMS	; Convert the ascii to hex values
		POP	DS
		MOV	START_ADDR+2,AX	; That's our starting address
		MOV	ES,040
		ES	ADD AX,[0F0]	; Add the last loads' space
		ES	ADD AX,[0F2]
		ADD	AX,010		; Allow for psp
		MOV	LOAD_BLOCK,AX	; This is out new loading address
		SUB	AX,010		; Remove psp allowance
		NEWLINE			; Starting load message
		PRINT$	'Loading '
		POP	DI
		MOV	B[DI-1],'$'	; Make printable
		PRINT	FILE_STRING	; And print it
		PRINT$	'at segment '
		PRINT_REG AX		; Print segment address
		LEA	BX,LOAD_BLOCK	; Where we load
		PUSH	ES
		PUSH	DS
		POP	ES
		MOV	AH,04B		; Dos exec function
		MOV	AL,3		; Load but don't execute
		INT	021		; Do it
		POP	ES
		MOV	AX,[LOAD_BLOCK]	; Loading address again
		SUB	AX,010		; Allow for psp
		MOV	START_ADDR + 2,AX ; Stuff it away
		MOV	LOAD_POINT,AX	; And here
		MOV	LP_PLUS_BYTES,AX ; And here, too
		MOV	ES,AX		; Segment of where tsr is loaded
		MOV	CX,0100		; Going to transfer 256 bytes
		XOR	SI,SI		; Of the psp (we're using the one
		XOR	DI,DI		; We got from dos)
		CLD			; Set direction flag
		REP	MOVSB		; Transfer it
		MOV	DI,081		; Point to com tail in new psp
		LEA	SI,TAIL_BFR
		XOR	AL,AL		; Count of command tail length
L98:
		MOV	AH,[SI]
		CMP	AH,CR		; Any command tail at all?
		JE	> L99
		ES	MOV [DI],AH	; If so, start transferring the
		INC	AL		; Tsr's command tail - he might
		INC	DI		; Need it
		INC	SI
		JMP	L98
L99:
		MOV	B[SI],'$'	; Again, for dos int 21h printing
		NEWLINE
		PRINT$	'Command tail = ' ; Printing out the supplied
		PRINT	TAIL_BFR	; Command tail
		MOV	B[SI],CR	; Terminate
		ES	MOV B[DI],CR	; Terminate new tail
		ES	MOV [080],AL	; Put in count
		ES	MOV B[081],SPACE ; And normal space
;
; Here we start revectoring the appropriate interrupts
;
		MOV	AH,035		; The 'gimme address' function
		MOV	AL,027		; For interrupt 27h
		INT	021		; Go get it
		MOV	AX,ES		; Its segment
		MOV	OLD_INT_27,BX	; Store offset value
		MOV	(OLD_INT_27 + 2),AX ; Store segment value
		LEA	DX,NEW_INT27	; Where we're going to point to
		MOV	AH,025		; Tell dos about it
		MOV	AL,027		; For int 27h
		INT	021		; Do it
		MOV	AH,035		; Get address function again
		MOV	AL,021		; This time for int 21h
		INT	021		; Do it
		MOV	AX,ES
		MOV	OLD_INT_21,BX	; Save offset
		MOV	(OLD_INT_21 + 2),AX ; And segment
		LEA	DX,NEW_INT21	; Point to my routine
		MOV	AH,025		; And tell dos
		MOV	AL,021
		INT	021
		MOV	AX,[START_ADDR+2] ; Get address of routine
		MOV	ES,AX		; Set up registers
		MOV	DS,AX		; But don't change stack reg (ss)!!
		CS	JMP D[START_ADDR] ; And execute the program
;...
;
; This is the end of the regular program.  We exit when the TSR executes
; and INT 27H or an INT 21H with function 31H.
;
; The following are the various subroutines used
;
;	routine to compare two 32-bit quantities
;	if BX:CX > DX:AX, carry bit is set
;	if BX:CX < DX:AX, carry bit is cleared
;	if BX:CX = DX:AX, zero flag is set
;
;	all registers unchanged
;
CMP_32:
		CMP	BX,DX
		JAE	> L0
		JMP	> L1
L0:
		JA	> L2
		CMP	CX,AX
		JB	> L1
L2:
		STC
		RET
L1:
		CLC
		RET
;.....
;
; Routine to form 32-bit sum of (typically) a segment and offset pair
;
;	enter with "segment" value in DX, "offset" in AX
;	returns with sum in DX:AX
;
;	all other registers restored
;
ADD_SEG_OFF:
        	PUSH	BX,CX
		XOR	BX,BX
		MOV	CX,4
L0:
		SHL	BX,1
		SHL	DX,1
		ADC	BX,0
		LOOP	L0
		ADD	AX,DX
		ADC	BX,0
		MOV	DX,BX
		POP	CX,BX
		RET
;.....
;
; Routine to convert a HEX digit to a two byte word containing the
; equivalent ASCII characters
;
;	Enter with the byte to be converted in AL
;	Return with the ASCII string in AX in 'backwords' format
;	i.e., an AL value of 47 would return AX = 3734
;	so a MOV W[SI],AX for example would store the values
;	in memory in the proper order
;
;	All other registers restored
;
;	This is essentially identical to the macro MAKE_HEX_ASCII
;	except the macro does not interchange AH and AL
;
HEX2PRNT:
		XOR	AH,AH		; Zero out upper half
		AAM	16		; Split into two
		ADD	AX,03030	; Make ASCII
		CMP	AH,039		; Check for a - f
		 IF	A ADD AH,7
		CMP	AL,039
		 IF	A ADD AL,7
		XCHG	AH,AL		; Put in proper order
		RET			; For a MOV innstruction
;.....
;
; Routine to find out the program name.
;	no parameters on entry.
;	return with SI pointing to a string containing the file name
;	with no extent or period.
;	the string is terminated with a 0.
;	all registers (except SI) are restored
;
WHOAMI:
		JMP	> L0
MY_NAME:
		DB	9 DUP (0)
L0:
		PUSH	AX
		PUSH	BX
		PUSH	CX
		PUSH	ES
		PUSH	DI
		MOV	BX,[02C]	; Get seg addr of env
		MOV	ES,BX		; Put in es
		XOR	DI,DI		; Zero out di
		XOR	AX,AX		; Looking for double zeros
		MOV	CX,08000	; Max environment length = 32k
		CLD			; Set direction flag
L3:
		REPNE	SCASB		; Look for zero
		ES	MOV BX,W[DI-1]
		CMP	BX,0		; Got two bytes of zero?
		JNE	L3
		MOV	AL,'.'
		REPNE	SCASB		; Looking for extent
		MOV	BX,DI		; Save end of string pointer
L6:
		ES	MOV AL,[DI]
		CMP	AL,'\'
		JE	> L7
		CMP	AL,0
		JE	> L7
		DEC	DI
		JMP	L6
L7:
		MOV	SI,DI
		INC	SI		; Point to first byte of asciiz
		LEA	DI,MY_NAME	; Will store it in this segment
L2:
		ES	MOV AH,[SI]
		CMP	AH,'.'
		JE	> L1
		MAKE_CAP AH		; Make sure all names are capitals
		MOV	[DI],AH
		INC	DI
		INC	SI
		JMP	L2
L1:
		LEA	SI,MY_NAME
		POP	DI
		POP	ES
		POP	CX
		POP	BX
		POP	AX
		RET
;.....
;
; Routine to find a match between a given string and an environment
; variable.
;
;	Enter with DS:SI pointing to a string, terminated by a 0,
;	which contains the string to be matched.
;
;	Returns with SI pointing to the end of the matching
;	string in in the environment.
;
;	The environment variables are of the form NAME=string
;	This routine returns SI pointing to the string, just
;	after the equal sign.
;
;	ES points to the environment segment on return
;
;	If a matching string is found, the carry bit will be
;	clear. If no match is found, the carry will be set.
;
;	All other registers are restored; SI not guaranteed.
;
GET_ENV_VAR:
		JMP	> L0
STRING_COUNT:
		DW	0
L0:
		PUSH	AX
		PUSH	CX
		PUSH	DI
		XOR	CX,CX		; Zero out counter
		PUSH	SI		; Save pointer
L1:
		MOV	AL,[SI]
		CMP	AL,0		; At end of string?
		JE	> L2
		INC	SI		; Bump pointer
		INC	CX		; Bump counter
		JMP	L1
L2:
		CS	MOV STRING_COUNT,CX ; Save counter
		POP	SI		; Restore pointer
		CALL	GET_ENV_LENGTH	; Get length of environment
		MOV	CX,AX		; Set up count
		XOR	DI,DI		; Start at beginning
		MOV	AX,[02C]	; Environment segment
		MOV	ES,AX
		CLD			; Scan forward
		MOV	AL,[SI]		; Look for first letter
L3:
		REPNE	SCASB		; Scan for it
		DEC	DI		; Back off to point to match
		JCXZ	> L99		; Get out if not there
		PUSH	CX		; Save counter
		CS	MOV CX,[STRING_COUNT]
		PUSH	SI		; Save my old place
		REPE	CMPSB		; See if they're all equal
		POP	SI		; Restore my place
		JZ	> L4
		POP	CX		; No match, so carry on
		JMP	L3
L99:
		STC			; Set the carry
		JMP	> L5
L4:
		POP	CX		; Clear stack
		MOV	SI,DI		; Put pointer in si
		INC	SI		; Skip past equal sign
		CLC			; Clear carry
L5:
		POP	DI
		POP	CX
		POP	AX
		RET
;.....
;
; Routine to find length of environment
;
;	IF CARRY IS CLEAR, RESULT RETURNED IN AX
;	IF CARRY IS SET, ERROR CONDITION
;
;	IN EITHER EVENT, ALL REGISTERS OTHER THAN AX ARE RESTORED
;
GET_ENV_LENGTH:
		PUSH	CX
		PUSH	ES
		PUSH	DI
		MOV	AX,[02C]	; Get segment of environment
		MOV	ES,AX
		XOR	DI,DI		; Start at beginning of env
		MOV	CX,08000	; Maximum env string = 32k
L9:
		ES	MOV AX,W[DI]
		CMP	AX,0
		JE	> L0
		INC	DI
		LOOP	L9
		JCXZ	>L1		; If cx=0, we're in big trouble
L0:
		MOV	AX,DI		; Si is length of env - put it in ax
		CLC			; Clear carry bit - result ok
		JMP	> L2
L1:
		STC			; Set the carry - error
L2:
		POP	DI
		POP	ES
		POP	CX
		RET
;.....
;
; Routine to convert a 2-digit ASCII pair into a hex number e.g.,
; convert 3741 into 7A.
;
;	Enter with the ASCII pair in AX, return with the hex digit
;	in AL
;
;	If all is well, the carry bit is clear
;	If the carry bit is set, at least one of the proposed digits
;	was not in the range (0-9) snd (A-F) (Routine accepts small
;	letters and makes them caps)
;
;	All other registers unaffected.
;
ASC_2_HEX:
		XCHG	AH,AL
		CMP	AH,039
		JBE	> L0
		AND	AH,0DF		; Make caps
		CMP	AH,'F'
		JA	> L3
		SUB	AH,7
L0:
		CMP	AH,030
		JB	> L3
		CMP	AL,039
		JBE	> L1
		AND	AL,0DF
		CMP	AL,'F'
		JA	> L3
		SUB	AL,7
L1:
		CMP	AL,030
		JB	> L3
		SUB	AX,03030
		UNSPLIT
		CLC
		RET
L3:
		STC
		RET
;.....
;
; Routine to convert a 4-digit ASCII number representation into a
; 4-digit hex number e.g., 31374230 -> 17B0
;
;	Enter with DS:SI pointing to the string.
;	Return with the number in AX, in the proper order
;	i.e., 31374230 -> AH = 17, AL = B0
;
;	Carry bit set indicates bad ASCII number (out of range) see above
;
ASC_2_NUMS:
		PUSH	BX
		MOV	AX,W[SI]
		CALL	ASC_2_HEX
		JC	> L1
		MOV	BX,AX
		MOV	AX,W[SI+2]
		CALL	ASC_2_HEX
		JC	> L1
		MOV	AH,BL
		POP	BX
		CLC
L1:
		RET
;.....
;
; Routine to convert a 32-bit number to a decimal ASCII string enter
; with the number to be converted in DX:AX and DI pointing to the last
; byte of the result string area.  Return with DI pointing to first
; non-zero character of resultant conversion.
;
;	BX, CX, and SI unchanged
;
;	This routine was cribbed from FREE by Art Merrill - I don't really
;	understand it all, but it works!
;
LARGE_HEX_TO_ASCII:
		PUSH BX
		PUSH	CX
		XCHG	CX,DX
		MOV	BX,10
L1:
		CMP	CX,0
		JE	> L2
		XCHG	AX,CX
		XOR	DX,DX
		DIV	BX
		XCHG	AX,CX
		DIV	BX
		OR	DL,030
		MOV	[DI],DL
		DEC	DI
		JMP	L1
L2:
		XOR	DX,DX
		DIV	BX
		OR	DL,030
		MOV	[DI],DL
		DEC	DI
		CMP	AX,0
		JNE	L2
		INC	DI		; Back up pointer to first char
		POP	CX
		POP	BX
		RET
;
                end
