Metropoli BBS
VIEWER: log.asm MODE: TEXT (ASCII)
;=============================================================================
; LOG maintains a log of system usage in an ASCII file.  Syntax is:
;	LOG [filespec] [/U]
; where filespec = Name of and/or path to log file, /U = Uninstall
;=============================================================================
CODE		SEGMENT	PARA PUBLIC 'CODE'
		ASSUME	CS:CODE
		ORG	100H
BEGIN:		JMP	INITIALIZE

PROGRAM		DB	"LOG 1.0 "
COPYRIGHT	DB	"(c) 1988 Ziff Communications Co.",13,10
AUTHOR		DB	"PC Magazine ",254," Jeff Prosise",13,10,"$",26
HANDLE		DW	?			;file handle
DOSVERSION	DW	?			;DOS version number
INT21H		DD	?			;interrupt 21h vector
LEVEL		DW	0			;EXEC reentrancy level
EXECFLAG	DW	0			;EXEC flag
XERROR_AX	DW	?			;extended error information
XERROR_BX	DW	?
XERROR_CX	DW	?
XERROR_DX	DW	?
XERROR_SI	DW	?
XERROR_DI	DW	?
XERROR_DS	DW	?
XERROR_ES	DW	?
		DW	3 DUP (0)
;=============================================================================
; DOSINT intercepts calls to interrupt 21h.
;=============================================================================
DOSINT		PROC	FAR
		CMP	AX,4B00H		;exit immediately if this
		JNE	EXEC0			;  isn't a call to EXEC
		CMP	CS:[LEVEL],9		;exit if we've exceeded
		JB	EXEC1			;  reentrancy limits
EXEC0:		JMP	INT21H
;--Save registers passed to EXEC by the parent program.
EXEC1:		STI				;interrupts on
		PUSH	AX			;save registers
		PUSH	BX
		PUSH	CX
		PUSH	DX
		PUSH	SI
		PUSH	DI
		PUSH	BP
		PUSH	DS
		PUSH	ES
;--Record the program start/end time and write an entry to the log.
		PUSH	BX			;save registers set for
		PUSH	DX			;  EXEC call
		PUSH	DS
		PUSH	ES
		PUSH	CS			;point DS to code segment
		POP	DS
		ASSUME	DS:CODE
		INC	LEVEL			;increment reentrancy count
		CALL	RECORD_TIME		;record the current time
		MOV	DX,OFFSET FILESPEC
		CALL	OPENFILE		;open the log file
		MOV	SI,LEVEL		;point SI to start time and
		DEC	SI			;  DI to end time
		SHL	SI,1
		SHL	SI,1
		ADD	SI,OFFSET TIMES
		MOV	DI,SI
		ADD	DI,4
		MOV	AL,BYTE PTR LEVEL	;put previous level number
		DEC	AL			;  in AL
		CALL	WRITE_ENTRY		;write log entry
		CALL	CLOSEFILE		;close the log file
		MOV	EXECFLAG,1		;set EXEC flag
		ASSUME	DS:NOTHING
		POP	ES			;restore EXEC register
		POP	DS			;  parameters
		POP	DX
		POP	BX
;--Record the name of the program just EXECed.
		PUSH	ES			;save ES and BX for later
		PUSH	BX
		PUSH	DS			;set ES equal to DS
		POP	ES
		MOV	DI,DX			;scan for terminating zero
		XOR	AL,AL			;  in ASCIIZ filename
		MOV	CX,128
		CLD
		REPNE	SCASB
		MOV	BX,127			;transfer string length
		SUB	BX,CX			;  to CX
		MOV	CX,BX
		MOV	SI,DI			;get ending address in SI
		SUB	SI,2			;set SI to last character
		STD				;set DF for reverse string ops
EXEC2:		LODSB				;scan backwards for first
		CMP	AL,"\"			;  character in filename
		JE	EXEC3
		CMP	AL,":"
		JE	EXEC3
		LOOP	EXEC2
		DEC	SI
EXEC3:		ADD	SI,2			;point SI to first character
		SUB	BX,CX			;calculate length of filename
		MOV	CX,BX			;transfer length to CX
		CLD				;clear DF again
		PUSH	CS			;set ES to code segment
		POP	ES
		MOV	AL,13			;calculate offset into table
		MOV	DL,BYTE PTR CS:[LEVEL]	;  of program names
		MUL	DL
		MOV	DI,AX
		ADD	DI,OFFSET NAMES
		MOV	AL,CL			;write filename character
		STOSB				;  count into table
EXEC5:		LODSB				;convert lowercase characters
		CMP	AL,"a"			;  to upper and copy filename
		JB	EXEC6			;  into table
		CMP	AL,"z"
		JA	EXEC6
		AND	AL,0DFH
EXEC6:		STOSB
		LOOP	EXEC5
;--Record the command line passed to the program just EXECed.
		POP	BX			;restore ES and BX
		POP	ES
		LDS	SI,ES:[BX+2]		;load parm string address
		MOV	CL,[SI]			;get length
		XOR	CH,CH
		INC	CX
		PUSH	CS			;point ES:DI to command line
		POP	ES			;  buffer in code segment
		MOV	DI,OFFSET COMLINE
		REP	MOVSB			;copy command line
;--Restore register values originally passed by the parent program.
		POP	ES
		POP	DS
		POP	BP
		POP	DI
		POP	SI
		POP	DX
		POP	CX
		POP	BX
		POP	AX
;--Save registers again since the DOS 2.X EXEC function destroys them.
		PUSH	AX
		PUSH	BX
		PUSH	CX
		PUSH	DX
		PUSH	SI
		PUSH	DI
		PUSH	BP
		PUSH	DS
		PUSH	ES
;--Save the values of SS and SP, call EXEC, and restore the stack.
		MOV	CS:[HANDLE],BX		;calculate an index into
		MOV	BX,CS:[LEVEL]		;  the STACKSEG and
		DEC	BX			;  STACKPTR tables
		SHL	BX,1
		CLI					;disable interrupts and
		MOV	WORD PTR CS:[STACKSEG][BX],SS	;  save the SS and SP
		MOV	WORD PTR CS:[STACKPTR][BX],SP	;  registers
		MOV	BX,CS:[HANDLE]
		PUSHF				;PUSH the flags register and
		CALL	INT21H			;  call DOS EXEC
		LAHF				;store flags temporarily in AH
		MOV	BX,CS:[LEVEL]		;recalculate table index
		DEC	BX
		SHL	BX,1
		CLI					;disable interrupts and
		MOV	SS,WORD PTR CS:[STACKSEG][BX]	;  restore SS and SP
		MOV	SP,WORD PTR CS:[STACKPTR][BX]
		STI
		SAHF				;restore flags
;--Restore the registers to their conditions before EXEC was called.
		POP	ES
		POP	DS
		POP	BP
		POP	DI
		POP	SI
		POP	DX
		POP	CX
		POP	BX
		POP	AX
;--Save registers again while post-processing is performed.
		PUSHF				;then save it for exit
		PUSH	AX			;save general registers
		PUSH	BX
		PUSH	CX
		PUSH	DX
		PUSH	SI
		PUSH	DI
		PUSH	BP
		PUSH	DS
		PUSH	ES
;--Save extended error information if DOS version is 3.10 or later.
		PUSH	CS			;set DS to code segment
		POP	DS
		ASSUME	DS:CODE
		CMP	DOSVERSION,030AH	;skip if not 3.10 or later
		JB	EXEC7
		PUSH	DS			;save DS
		MOV	AH,59H			;get extended error
		XOR	BX,BX			;  information
		INT	21H
		MOV	CS:[XERROR_DS],DS	;save return value of DS
		POP	DS			;set DS to code segment again
		MOV	XERROR_AX,AX		;save remaining register
		MOV	XERROR_BX,BX		;  values in XERROR array
		MOV	XERROR_CX,CX
		MOV	XERROR_DX,DX
		MOV	XERROR_SI,SI
		MOV	XERROR_DI,DI
		MOV	XERROR_ES,ES
;--Record the program start/end time and write an entry to the log.
EXEC7:		DEC	LEVEL			;decrement reentrancy count
		CALL	RECORD_TIME		;record the current time
		MOV	DX,OFFSET FILESPEC
		CALL	OPENFILE		;open the log file
		MOV	SI,LEVEL		;point SI to start time and
		INC	SI			;  DI to end time
		SHL	SI,1
		SHL	SI,1
		ADD	SI,OFFSET TIMES
		MOV	DI,SI
		SUB	DI,4
		MOV	AL,BYTE PTR LEVEL	;put previous level number
		INC	AL			;  in AL
		CALL	WRITE_ENTRY		;write log entry
		CALL	CLOSEFILE		;close the log file
		MOV	EXECFLAG,0		;clear EXEC flag
;--Restore extended error information if DOS version is 3.10 or later.
		CMP	DOSVERSION,030AH	;skip if not 3.10 or later
		JB	EXEC8
		MOV	AX,5D0AH		;restore information with
		MOV	DX,OFFSET XERROR_AX	;  undocumented function 5Dh
		INT	21H
;--Restore registers a final time and exit to the parent program.
		ASSUME	DS:NOTHING
EXEC8:		POP	ES			;restore registers
		POP	DS
		POP	BP
		POP	DI
		POP	SI
		POP	DX
		POP	CX
		POP	BX
		POP	AX
		POPF
DOSEXIT:	IRET
DOSINT		ENDP
;-----------------------------------------------------------------------------
; OPENFILE opens an existing log file or creates a new one.
; Entry:  DS - code segment
; Exit:   CF: clear-opened/created, set-file could not be opened/created
;-----------------------------------------------------------------------------
ADDRSPEC	DW	?
CALLFLAG	DB	0

OPENFILE	PROC	NEAR
		ASSUME	DS:CODE
		MOV	ADDRSPEC,DX		;save filespec address
		MOV	AX,3D02H		;attempt to open an existing
		INT	21H			;  log file
		JC	OPEN3			;branch if call failed
		MOV	HANDLE,AX		;store file handle
		MOV	BX,AX			;transfer it to BX
		MOV	AX,4202H		;position file pointer at
		XOR	CX,CX			;  the end of the file
		XOR	DX,DX
		INT	21H
		CMP	CALLFLAG,0		;write date to log file
		JNE	OPEN1			;  if this is the first
		INC	CALLFLAG		;  call to OPENFILE
OPEN0:		CALL	WRITE_DATE
		CALL	WRITE_HEADER		;then write header line
OPEN1:		CLC				;clear CF for exit
OPEN2:		RET				;exit
;--Call to open an existing file failed.  Create a new one and initialize it.
OPEN3:		MOV	AH,3CH			;attempt to create a new
		XOR	CX,CX			;  file of the same name
		MOV	DX,ADDRSPEC
		INT	21H
		JC	OPEN2			;exit on error
		MOV	HANDLE,AX		;store file handle
		MOV	BX,AX			;transfer it to BX
		MOV	CALLFLAG,1		;set entry flag
		MOV	AH,40H			;write copyright text
		MOV	CX,70
		MOV	DX,OFFSET PROGRAM
		INT	21H
		JMP	OPEN0			;exit
OPENFILE	ENDP
;-----------------------------------------------------------------------------
; CLOSEFILE closes the log file.
;-----------------------------------------------------------------------------
CLOSEFILE	PROC	NEAR
		ASSUME	DS:CODE
		MOV	AH,3EH
		MOV	BX,HANDLE
		INT	21H
		RET
CLOSEFILE	ENDP
;-----------------------------------------------------------------------------
; WRITE_ENTRY writes a one-line entry to the log file.
;   Entry:  DS:SI - start time
;           DS:DI - end time
;           AL    - last reentrancy level
;-----------------------------------------------------------------------------
LASTLEVEL	DB	?

WRITE_ENTRY	PROC	NEAR
		ASSUME	DS:CODE
		MOV	LASTLEVEL,AL		;store last level number
		CALL	WRITE_TIME		;write start time
		MOV	CX,4
		CALL	WRITE_SPACES
		PUSH	SI			;save start time
		MOV	SI,DI			;write end time
		CALL	WRITE_TIME
		MOV	CX,3
		CALL	WRITE_SPACES
		POP	SI			;retrieve start time
		CALL	WRITE_DIFF		;write elapsed time
		MOV	CX,5
		CALL	WRITE_SPACES
		MOV	AL,LASTLEVEL		;write reentrancy level
		MOV	BL,1
		CALL	WRITE_NUM
		MOV	CX,6
		CALL	WRITE_SPACES
		MOV	AL,13			;calculate offset into
		MUL	LASTLEVEL		;  program name table
		MOV	DX,AX
		ADD	DX,OFFSET NAMES
		MOV	AH,40H			;write program name
		MOV	BX,DX
		MOV	CL,[BX]
		XOR	CH,CH
		PUSH	CX
		MOV	BX,HANDLE
		INC	DX
		INT	21H
		POP	BX
		CMP	EXECFLAG,0		;exit now if this entry is
		JE	NOPARMS			;  a return from EXEC
		MOV	CX,12			;calculate number of spaces
		SUB	CX,BX			;  to skip
		ADD	CX,5
		CALL	WRITE_SPACES
		MOV	BX,OFFSET COMLINE	;write command line passed
		MOV	CL,[BX]			;  to the last program
		XOR	CH,CH			;  EXECed to the log file
		JCXZ	NOPARMS
		MOV	AH,40H
		MOV	BX,HANDLE
		MOV	DX,OFFSET COMLINE+1
		INT	21H
NOPARMS:	CALL	WRITE_CRLF		;end line with CR/LF
		RET
WRITE_ENTRY	ENDP
;-----------------------------------------------------------------------------
; WRITE_DATE writes the current date to the log file.
;-----------------------------------------------------------------------------
MONTHS		DB	"JanFebMarAprMayJunJulAugSepOctNovDec"
CENTURY		DB	"19"

WRITE_DATE	PROC	NEAR
		ASSUME	DS:CODE
		CALL	WRITE_CRLF		;skip one line
		MOV	AH,2AH			;get date from DOS
		INT	21H
		PUSH	CX			;save it
		PUSH	DX
		MOV	AL,DL			;write day of the month to
		XOR	BL,BL			;  the log file
		CALL	WRITE_NUM
		MOV	CX,1			;skip a space
		CALL	WRITE_SPACES
		POP	DX			;retrieve month from stack
		DEC	DH			;calculate offset into MONTH
		MOV	CL,DH			;  table of text for the
		XOR	CH,CH			;  current month
		MOV	DX,OFFSET MONTHS
		JCXZ	WRDATE2
WRDATE1:	ADD	DX,3
		LOOP	WRDATE1
WRDATE2:	MOV	AH,40H			;write month to log file
		MOV	BX,HANDLE
		MOV	CX,3
		INT	21H
		MOV	CX,1			;skip a space
		CALL	WRITE_SPACES
		MOV	AH,40H			;write "19" portion of year
		MOV	CX,2
		MOV	DX,OFFSET CENTURY
		INT	21H
		POP	CX			;retrieve year from stack
		SUB	CX,1900			;subtract century portion
		MOV	AL,CL			;write year to the log file
		XOR	BL,BL
		CALL	WRITE_NUM
		CALL	WRITE_CRLF		;finish with CRLF pairs
		CALL	WRITE_CRLF
		RET
WRITE_DATE	ENDP

;-----------------------------------------------------------------------------
; WRITE_TIME writes the time to the log file.  Entry:  DS:SI - time
;-----------------------------------------------------------------------------
COLON		DB	":"

WRITE_TIME	PROC	NEAR
		ASSUME	DS:CODE
		MOV	AL,[SI+1]		;write hours to log file
		MOV	BL,1
		CALL	WRITE_NUM
		MOV	AH,40H			;write ":" to separate
		MOV	BX,HANDLE		;  hours and minutes
		MOV	CX,1
		MOV	DX,OFFSET COLON
		INT	21H
		MOV	AL,[SI]			;write minutes to log file
		XOR	BL,BL
		CALL	WRITE_NUM
		RET
WRITE_TIME	ENDP
;-----------------------------------------------------------------------------
; WRITE_DIFF writes the elapsed time to the log file.
;   Entry:  DS:SI - start time,  DS:DI - end time
;-----------------------------------------------------------------------------
DELTA		DB	3 DUP (?)

WRITE_DIFF	PROC	NEAR
		ASSUME	DS:CODE
		MOV	AL,[DI]			;retrieve starting and ending
		MOV	BL,[SI]			;  seconds, minutes, and
		MOV	CL,[DI+1]		;  hours
		MOV	DL,[SI+1]
		MOV	AH,[DI+2]
		MOV	BH,[SI+2]
		CMP	AH,BH			;if ending seconds is less
		JAE	WRDIFF1			;  than starting, add 60 to
		ADD	AH,60			;  ending seconds and
		DEC	AL			;  decrement ending minutes
WRDIFF1:	CMP	AL,BL			;if ending minutes is less
		JGE	WRDIFF2			;  than starting, add 60 to
		ADD	AL,60			;  ending minutes and
		DEC	CL			;  decrement ending hours
WRDIFF2:	CMP	CL,DL			;if ending hours is less
		JGE	WRDIFF3			;  than starting, add 24
		ADD	CL,24			;  to ending hours
WRDIFF3:	SUB	AH,BH			;calculate seconds difference
		MOV	DELTA+2,AH		;store it
		SUB	AL,BL			;calculate minutes difference
		MOV	DELTA,AL		;store it
		SUB	CL,DL			;calculate hours difference
		MOV	DELTA+1,CL		;store it
		MOV	SI,OFFSET DELTA		;write hours and minutes to
		CALL	WRITE_TIME		;  the log file
		MOV	AH,40H			;write ":" to log file
		MOV	BX,HANDLE
		MOV	CX,1
		MOV	DX,OFFSET COLON
		INT	21H
		MOV	AL,[SI+2]		;write seconds to log file
		XOR	BL,BL
		CALL	WRITE_NUM
		RET
WRITE_DIFF	ENDP
;-----------------------------------------------------------------------------
; WRITE_NUM converts a binary byte value to ASCII and writes it to the log
; file.  Value must be between 0 and 99, inclusive.
; Entry:  AL - byte value, BL - 0 = include leading zeroes, 1 = don't include
;-----------------------------------------------------------------------------
FILEWORD	DW	?

WRITE_NUM	PROC	NEAR
		ASSUME	DS:CODE
		AAM				;convert to BCD in AX
		ADD	AX,3030H		;convert to ASCII
		OR	BL,BL			;convert leading zero to
		JZ	WRNUM1			;  space if BL = 1 on
		CMP	AH,30H			;  entry
		JNE	WRNUM1
		MOV	AH,20H
WRNUM1:		XCHG	AH,AL			;swap bytes
		MOV	FILEWORD,AX		;store them
		MOV	AH,40H			;then write them to the
		MOV	BX,HANDLE		;  log file
		MOV	CX,2
		MOV	DX,OFFSET FILEWORD
		INT	21H
		RET
WRITE_NUM	ENDP
;-----------------------------------------------------------------------------
; WRITE_HEADER writes the column titles to the log file.
;-----------------------------------------------------------------------------
TITLES		DB	"START",5 DUP (32),"END",5 DUP (32)
		DB	"ELAPSED",4 DUP (32),"LEVEL",4 DUP (32)
		DB	"PROGRAM",10 DUP (32),"PARAMETERS",13,10
EQUALSIGN	DB	"="

WRITE_HEADER	PROC	NEAR
		ASSUME	DS:CODE
		MOV	AH,40H			;write column titles
		MOV	BX,HANDLE
		MOV	CX,67
		MOV	DX,OFFSET TITLES
		INT	21H
		MOV	CX,79			;write row of "=" symbols
WRHEAD1:	PUSH	CX
		MOV	AH,40H
		MOV	CX,1
		MOV	DX,OFFSET EQUALSIGN
		INT	21H
		POP	CX
		LOOP	WRHEAD1
		CALL	WRITE_CRLF		;end it with a CR/LF pair
		RET
WRITE_HEADER	ENDP
;-----------------------------------------------------------------------------
; WRITE_CRLF writes a carriage return / line feed to the log file.
;-----------------------------------------------------------------------------
CRLF		DB	13,10

WRITE_CRLF	PROC	NEAR
		ASSUME	DS:CODE
		MOV	AH,40H
		MOV	BX,HANDLE
		MOV	CX,2
		MOV	DX,OFFSET CRLF
		INT	21H
		RET
WRITE_CRLF	ENDP
;-----------------------------------------------------------------------------
; WRITE_SPACES writes CX spaces to the log file.
;-----------------------------------------------------------------------------
SPACE		DB	32

WRITE_SPACES	PROC	NEAR
		ASSUME	DS:CODE
		PUSH	CX			;save counter
		MOV	AH,40H			;write one space
		MOV	BX,HANDLE
		MOV	CX,1
		MOV	DX,OFFSET SPACE
		INT	21H
		POP	CX			;retrieve count
		LOOP	WRITE_SPACES		;loop until done
		RET
WRITE_SPACES	ENDP
;-----------------------------------------------------------------------------
; RECORD_TIME records the current time in the slot designated by LEVEL.
;-----------------------------------------------------------------------------
RECORD_TIME	PROC	NEAR
		ASSUME	DS:CODE
		MOV	AH,2CH			;get current time
		INT	21H
		MOV	BX,LEVEL		;calculate offset into table
		SHL	BX,1			;  of start/end times
		SHL	BX,1
		MOV	WORD PTR TIMES[BX],CX	;store current time
		MOV	BYTE PTR TIMES[BX+2],DH
		RET
RECORD_TIME	ENDP
;=============================================================================
; 418-byte buffer area used after LOG becomes resident.
;=============================================================================
PC		=	$
FILESPEC	=	PC			;filespec buffer
PC		=	PC + 80
NAMES		=	PC			;storage array for program
PC		=	PC + 130		;  names
TIMES		=	PC			;storage array for program
PC		=	PC + 40			;  start/end times
STACKSEG	=	PC			;storage array for SS
PC		=	PC + 20			;  register values
STACKPTR	=	PC			;storage array for SP
PC		=	PC + 20			;  register values
COMLINE		=	PC			;command line parameter buffer
PC		=	PC + 128
LASTBYTE	=	PC
;=============================================================================
; INITIALIZE installs or uninstalls the program.
;=============================================================================
ERRMSG1		DB	"Usage: LOG [filespec] [/U]$"
ERRMSG2		DB	"Not Installed$"
ERRMSG3		DB	"Cannot Uninstall$"
ERRMSG4		DB	"Already Installed$"
ERRMSG5		DB	"Invalid Filespec$"
OUTTEXT		DB	"Uninstalled$"
DEFNAME		DB	"\USAGE.LOG",13
IDLETEXT	DB	6,"<idle>"
INSTALLED	DB	0

INITIALIZE	PROC	NEAR
		ASSUME	CS:CODE, DS:CODE
;--See if a copy of LOG is already resident in memory.
		CLD				;clear DF for string ops
		MOV	WORD PTR [BEGIN],0	;initialize fingerprint
		XOR	BX,BX			;zero BX for start
		MOV	AX,CS			;keep CS value in AX
INIT1:		INC	BX			;increment search segment value
		MOV	ES,BX
		CMP	AX,BX			;not installed if current
		JE	PARSE1			;  segment is reached
		MOV	SI,OFFSET BEGIN		;search this segment for ASCII
		MOV	DI,SI			;  fingerprint
		MOV	CX,16
		REPE	CMPSB
		JNE	INIT1			;loop back if not found
		MOV	INSTALLED,1		;set installed flag
;--Parse the command line for entries.
PARSE1:		MOV	SI,81H			;point SI to command line
PARSE2:		LODSB				;get a character
		CMP	AL,20H			;skip it if it's a space
		JE	PARSE2
		CMP	AL,0DH			;exit loop when a carriage
		JE	NOSPEC			;  return is encountered
		CMP	AL,"/"			;check for forward slash
		JNE	QUALIFY			;anything else is a filespec
		LODSB				;get the next character
		AND	AL,0DFH			;capitalize it
		CMP	AL,"U"			;branch to uninstall code if
		JE	UNINSTALL		;  character is a "U"
;--An error was encountered in parsing.  Display error message and exit.
		MOV	DX,OFFSET ERRMSG1	;load message address
ERROR_EXIT:	MOV	AH,9			;display error message
		INT	21H
		MOV	AX,4C01H		;exit with ERRORLEVEL = 1
		INT	21H
;--Uninstall the program.
UNINSTALL:	MOV	DX,OFFSET ERRMSG2	;error if program isn't
		CMP	INSTALLED,0		;  installed
		JE	ERROR_EXIT
		CALL	REMOVE			;call uninstall routine
		MOV	DX,OFFSET ERRMSG3	;error if uninstall failed
		JC	ERROR_EXIT
		MOV	DX,OFFSET OUTTEXT	;display "Uninstalled"
		MOV	AH,9			;  message
		INT	21H
		MOV	AX,4C00H		;exit with ERRORLEVEL = 0
		INT	21H
;--Convert the filespec into a fully qualified filename.
NOSPEC:		MOV	SI,OFFSET DEFNAME+1	;Use default filespec
QUALIFY:	MOV	DX,OFFSET ERRMSG4	;error if program is already
		CMP	INSTALLED,0		;  installed
		JNE	ERROR_EXIT
		DEC	SI			;point DS:SI to filespec
		PUSH	CS			;point ES:DI to temporary
		POP	ES			;  filespec buffer
		MOV	DI,OFFSET CWDIR+80
		CALL	GENSPEC			;generate complete filespec
		MOV	DX,OFFSET ERRMSG5	;exit if error flag is set
		JC	ERROR_EXIT		;  on return
		MOV	DI,OFFSET CWDIR+80	;find the terminating zero in
		XOR	AL,AL			;  the ASCIIZ string
		MOV	CX,80
		REPNE	SCASB
		DEC	DI
		CMP	BYTE PTR [DI-1],"\"	;append default filename if
		JNE	TESTSPEC		;  last character is a
		MOV	SI,OFFSET DEFNAME+1	;  backslash
		MOV	CX,9
		REP	MOVSB
		XOR	AL,AL			;write ASCIIZ terminator
		STOSB
;--Test the filespec and create the log file if it doesn't already exist.
TESTSPEC:	MOV	DX,OFFSET CWDIR+80
		CALL	OPENFILE		;open/create log file
		MOV	DX,OFFSET ERRMSG5	;error if file could not
		JC	ERROR_EXIT		;  be opened
		CALL	CLOSEFILE		;close log file
		CALL	RECORD_TIME		;record starting time
		MOV	AX,3000H		;record version of DOS
		INT	21H			;  LOG is running under
		XCHG	AH,AL
		MOV	DOSVERSION,AX
;--Hook into interrupt 21h and deallocate the program's environment block.
		MOV	AX,3521H		;save old interrupt vector
		INT	21H
		MOV	WORD PTR INT21H,BX
		MOV	WORD PTR INT21H[2],ES
		MOV	AX,2521H		;then set the new vector
		MOV	DX,OFFSET DOSINT
		INT	21H
		MOV	AX,DS:[2CH]		;deallocate the program's
		MOV	ES,AX			;  environment block
		MOV	AH,49H
		INT	21H
;--Initialize buffer areas.
		PUSH	CS			;point ES to code segment
		POP	ES
		MOV	SI,OFFSET CWDIR+80	;copy filepsec string to
		MOV	DI,OFFSET FILESPEC	;  filespec buffer
COPYLOOP:	LODSB
		STOSB
		OR	AL,AL
		JNZ	COPYLOOP
		MOV	SI,OFFSET IDLETEXT	;copy "<idle>" string to
		MOV	DI,OFFSET NAMES		;  program names buffer
		MOV	CX,7
		REP	MOVSB
;--Display copyright notice, then terminate and remain resident in memory.
		MOV	AH,9			;display copyright
		MOV	DX,OFFSET PROGRAM
		INT	21H
		MOV	AX,3100H
		MOV	DX,(OFFSET LASTBYTE - OFFSET CODE + 15) SHR 4
		INT	21H
INITIALIZE	ENDP
;-----------------------------------------------------------------------------
; REMOVE deallocates the memory block addressed by ES and restores the
; interrupt vector displaced on installation.
; Entry:  ES - segment to release
; Exit:   CF clear - program uninstalled, CF set   - can't uninstall
;-----------------------------------------------------------------------------
REMOVE          PROC    NEAR
		MOV	CX,ES			;abort if the interrupt 21h
		MOV	AX,3521H		;  vector has been altered
		INT	21H			;  since installation
		MOV	AX,ES
		CMP	AX,CX
		JNE	REMOVE_ERROR
		MOV	ES,CX
		MOV     AH,49H                  ;free memory given to
		INT     21h                     ;  original program block
		JC	REMOVE_ERROR		;branch on error
		PUSH    DS			;restore interrupt vector
		ASSUME  DS:NOTHING
		MOV	AX,2521H
		LDS	DX,ES:[INT21H]
		INT	21H
		POP     DS
		ASSUME  DS:CODE
		NOT     WORD PTR ES:[BEGIN]     ;destroy ASCII fingerprint
		CLC				;clear CF for exit
		RET
REMOVE_ERROR:	STC				;set CF to indicate program
		RET				;  couldn't be uninstalled
REMOVE          ENDP
;-----------------------------------------------------------------------------
; GENSPEC generates a fully qualified filename.
; Entry: DS:SI - filename, ES:DI - filespec buffer
; Exit:  CF clear - drive/directory valid, CF set - drive/directory invalid
;-----------------------------------------------------------------------------
ADDRIN		DW	?			;input string address
ADDROUT		DW	?			;output string address
DEFDRIVE	DB	?			;default drive
ADDRSLASH	DW	?			;address of last backslash

GENSPEC		PROC	NEAR
		MOV	ADDROUT,DI		;save output buffer address
;--Save the current drive and directory.
		PUSH	SI			;save SI
		MOV	AH,19H			;get current drive and
		INT	21H			;  save it
		MOV	DEFDRIVE,AL
		MOV	AH,47H			;get current directory and
		XOR	DL,DL			;  save it
		MOV	SI,OFFSET CWDIR+1
		INT	21H
		POP	SI			;restore SI
;--Check for a drive designator and supply default drive code if needed.
		CMP	BYTE PTR [SI+1],":"	;see if drive designator
		JNE	GEN1			;  appears in filespec
		LODSW				;  buffer and capitalize
		AND	AL,0DFH			;  drive letter if it does
		JMP	SHORT GEN2
GEN1:		MOV	AL,DEFDRIVE		;get default drive letter
		ADD	AL,"A"			;convert to ASCII
		MOV	AH,":"			;append colon
GEN2:		STOSW				;and output drive spec
;--Scan the input filespec for the rightmost backslash character.
		MOV	ADDRIN,SI		;save filespec address
		XOR	CX,CX			;initialize counter
GEN3:		LODSB				;get next byte
		CMP	AL,0DH			;scan finished if character
		JE	GEN4			;  is a carriage return or
		CMP	AL,20H			;  space
		JE	GEN4
		CMP	AL,"\"			;loop back if it's anything
		JNE	GEN3			;  but a backslash
		MOV	CX,SI			;save address of backslash
		DEC	CX			;  character and return to
		JMP	GEN3			;  scan loop
;--Copy everything up to the last backslash to the output buffer.
GEN4:		MOV	ADDRSLASH,CX		;save backslash address
		MOV	SI,ADDRIN		;point SI back to filespec
		OR	CX,CX			;terminate string if no
		JZ	GEN6			;  backslashes found
		CMP	SI,CX			;copy leading backslash if
		JE	GEN5			;  if it was the only one
		SUB	CX,SI			;otherwise copy everything
		DEC	CX			;  up to the last backslash
		JCXZ	GEN5
		REP	MOVSB
GEN5:		MOVSB
GEN6:		XOR	AL,AL			;append binary zero to form
		STOSB				;  ASCIIZ string
;--Change to the drive and directory specified and get a full pathname.
		MOV	AH,0EH			;switch current drive
		MOV	DI,ADDROUT
		MOV	DL,[DI]
		SUB	DL,"A"
		INT	21H
		CMP	BYTE PTR [DI+2],0
		JE	GEN7
		MOV	AH,3BH			;set current directory to
		MOV	DX,ADDROUT		;  the one just formulated
		ADD	DX,2			;  and exit if the call fails
		INT	21H
		JC	GEN_EXIT
GEN7:		MOV	AH,47H			;now request a complete
		MOV	SI,ADDROUT		;  directory string from
		MOV	DL,[SI]			;  DOS
		SUB	DL,40H
		ADD	SI,3
		MOV	BYTE PTR [SI-1],"\"
		INT	21H
		JC	GEN_EXIT		;exit on error
;--Reset the default drive and directory.
		MOV	AH,0EH			;reset default drive
		MOV	DL,DEFDRIVE
		INT	21H
		MOV	AH,3BH			;finish up by setting the
		MOV	DX,OFFSET CWDIR		;  current directory to what
		INT	21H			;  it was when we started
		JC	GEN_EXIT
;--Append the filename to the directory specification if one was entered.
		MOV	DI,ADDROUT		;find ASCIIZ terminator byte
		XOR	AL,AL			;  in output buffer
		MOV	CX,80
		REPNE	SCASB
		DEC	DI
		CMP	BYTE PTR [DI-1],"\"	;insert a backslash if
		JE	GEN8			;  there's not one at the
		MOV	AL,"\"			;  end of the string
		STOSB
GEN8:		MOV	SI,ADDRSLASH		;point SI to the first
		OR	SI,SI			;  character in the
		JNZ	GEN9			;  input filename
		MOV	SI,ADDRIN
		JMP	SHORT GEN10
GEN9:		INC	SI
GEN10:		LODSB				;copy characters until a
		CMP	AL,0DH			;  carriage return or space
		JE	GEN11			;  delimiter is reached
		CMP	AL,20H
		JE	GEN11
		STOSB
		JMP	GEN10
GEN11:		XOR	AL,AL			;append terminating zero byte
		STOSB				;  to string
		CLC				;clear CF and exit
GEN_EXIT:	RET
GENSPEC		ENDP
CWDIR		DB	"\"			;directory string buffer
CODE		ENDS
		END	BEGIN
[ RETURN TO DIRECTORY ]