;=============================================================================
; 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      "DOS Log v2.0b - Network Version"
COPYRIGHT       DB      "(c) 1989 M.P.S., All Rights Reserved",13,10
AUTHOR          DB      "Author ",254," Michael Shadduck",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
