;======================================================================
; POPWATCH 1.00 Copyright (c) 1992, Robert L. Hummel
; PC Magazine Assembly Language Lab Notes
;
; POPWATCH is a memory resident clock, calendar, and stopwatch. It may
; be used interactively by popping up the POPWATCH window using the
; ALT-P keystroke combination. Alternately, it can be invoked from the
; command line or from a batch file to produce date and time stamps
; that can be directed to the active display or to the printer.
;----------------------------------------------------------------------
CSEG            SEGMENT PARA    PUBLIC  'CODE'
        ASSUME  CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG

                ORG     100H                    ;COM file format
ENTPT:          JMP     MAIN

;======================================================================
; General program equates.
;----------------------------------------------------------------------
CR              EQU     0DH             ;ASCII carriage return
LF              EQU     0AH             ;ASCII line feed
SPACE           EQU     20H             ;ASCII blank
ESCAPE          EQU     1BH             ;ASCII ESC
ENDKEY          EQU     4F00H           ;END key scan code
JMPFAR          EQU     0EAH            ;Opcode for jump far

SHIFT_MASK      EQU     08H             ;Mask to pick out ALT
HOTKEY          EQU     19H             ;SCAN code for P key

;======================================================================
; Resident data area - this data remains with the resident portion.
;----------------------------------------------------------------------
; Messages.
;----------------------------------------------------------------------
RES_MARKER      DB      CR,LF,"PopWatch 1.00 Copyright (c) 1992 ",254
                DB      " Robert L. Hummel",26
MARKER_LEN      EQU     $-RES_MARKER
;----------------------------------------------------------------------
; Program variables.
;----------------------------------------------------------------------
DOS_BUSY        DD      0               ;Address of DOS busy flag
REQUEST         DB      0               ;Non-zero if request pending
ACTIVE          DB      0               ;Nonzero if POPWATCH is active
;----------------------------------------------------------------------
; Video data.
;----------------------------------------------------------------------
NROW            EQU     12              ;Number of rows in the window
NCOL            EQU     22              ;Number of cols in the window
BOX_ROW         EQU     02              ;Top row of window on screen
BOX_COL         EQU     05              ;Left col of window on screen

CUR_POS         DW      0               ;Original cursor position
CUR_SIZ         DW      0               ;Original cursor shape
VPAGE           DB      0               ;Current video page
VATTR           DB      0               ;Window color
 BW_ATTR        EQU     70H             ;Monochrome window
 CO_ATTR        EQU     17H             ;Color window

;======================================================================
; INT_9 (ISR)
;
; This routine gets control on each keypress and tests to see if the
; hotkey combination has been typed. If so, it sets a flag indicating
; that POPWATCH should be activated.
;----------------------------------------------------------------------
INT_9           PROC    FAR
        ASSUME  CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING

                PUSH    AX                      ;Save used register
;----------------------------------------------------------------------
; Read the key scan code from the controller and compare to the scan
; code for our hotkey.
;----------------------------------------------------------------------
                IN      AL,60H                  ;Get key scan code
                CMP     AL,HOTKEY               ;Check if hot-key
                JNE     I9_1
;----------------------------------------------------------------------
; Determine if the ALT key is also pressed.
;----------------------------------------------------------------------
                STI                             ;Enable interrupts
                MOV     AH,2                    ;Get shift status fn
                INT     16H                     ; thru BIOS

                AND     AL,0FH                  ;Test shift keys
                CMP     AL,SHIFT_MASK           ;Must match exactly
                JE      I9_2
;----------------------------------------------------------------------
; Restore the altered register and jump to the original Int 9 handler.
;----------------------------------------------------------------------
I9_1:
                POP     AX                      ;Restore register
                CLI                             ;Interrupts off

                DB      JMPFAR                  ;Jump to old interrupt
OLDINT9         DD      -1                      ; located here
;----------------------------------------------------------------------
; Reset the keyboard and interrupt controllers. This tells the system
; to simply forget the key stroke.
;----------------------------------------------------------------------
I9_2:
                IN      AL,61H                  ;These instructions
                MOV     AH,AL                   ; reset the keyboard
                OR      AL,80H                  ; controller
                OUT     61H,AL
                MOV     AL,AH
                JMP     SHORT $+2               ;Flush the prefetch
                JMP     SHORT $+2               ; queue for I/O delay
                OUT     61H,AL

                CLI                             ;Disable interrupts
                MOV     AL,20H                  ;Reset the controller
                OUT     20H,AL
                STI                             ;Enable interrupts
;----------------------------------------------------------------------
; If POPWATCH is already active, ignore the request.
;----------------------------------------------------------------------
                CMP     BYTE PTR CS:[ACTIVE],0  ;If active, ignore
                JNE     I9_3

                MOV     BYTE PTR CS:[REQUEST],-1 ;Set pop up request
I9_3:
                POP     AX                      ;Restore register
                IRET                            ;Return from INT

INT_9           ENDP

;======================================================================
; INT_8 (ISR)
;
; This routine gets control each timer tick. If a pop up request is
; pending and DOS is not busy, it pops up the window. Interrupts are
; not enabled until popping up is certain.
;----------------------------------------------------------------------
INT_8           PROC    FAR
        ASSUME  CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
;----------------------------------------------------------------------
; If no request is pending, simply jump to the original interrupt.
;----------------------------------------------------------------------
                CMP     BYTE PTR CS:[REQUEST],0 ;0 = no request
                JNE     I8_2
I8_1:
                DB      JMPFAR                  ;Jump to old interrupt
OLDINT8         DD      -1                      ; located here
;----------------------------------------------------------------------
; If already popped up, ignore the request.
;----------------------------------------------------------------------
I8_2:
                CMP     BYTE PTR CS:[ACTIVE],0  ;Nonzero is active
                JNE     I8_1
;----------------------------------------------------------------------
; Determine if DOS is in a busy state by examining the DOS BUSY flag.
;----------------------------------------------------------------------
                PUSH    BX                      ;Save used registers
                PUSH    DS

                LDS     BX,CS:[DOS_BUSY]        ;DS:BX -> DOS BUSY flag
        ASSUME  DS:NOTHING

                CMP     BYTE PTR DS:[BX],0      ;Zero is not busy

                POP     DS                      ;Restore registers
        ASSUME  DS:NOTHING
                POP     BX
                JNE     I8_1
;----------------------------------------------------------------------
; It's okay for us to pop up. Cancel (acknowledge) the request. Set the
; active flag, then service the old Int 8.
;----------------------------------------------------------------------
                MOV     BYTE PTR CS:[REQUEST],0 ;Cancel request
                MOV     BYTE PTR CS:[ACTIVE],-1 ;Nonzero means busy

                STI                             ;Enable interrupts

                PUSHF                           ;Simulate interrupt
                CALL    DWORD PTR CS:[OLDINT8]  ;Perform old Int 8

                JMP     POPUP                   ;Go pop up

INT_8           ENDP

;======================================================================
; INT_28 (ISR)
;
; Gets control each time DOS issues its idle interrupt. This proc
; checks to see if a pop up request is pending. Interrupts are not
; enabled until popping up is certain.
;----------------------------------------------------------------------
INT_28          PROC    FAR
        ASSUME  CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
;----------------------------------------------------------------------
; If no request is pending, simply jump to the original interrupt.
;----------------------------------------------------------------------
                CMP     BYTE PTR CS:[REQUEST],0 ;0 = no request
                JNE     I28_2
I28_1:
                DB      JMPFAR                  ;Jmp to old interrupt
OLDINT28        DD      -1                      ; located here
;----------------------------------------------------------------------
; Cancel the request. If POPWATCH is already busy, we'll ignore it.
;----------------------------------------------------------------------
I28_2:
                MOV     BYTE PTR CS:[REQUEST],0 ;Cancel request
                CMP     BYTE PTR CS:[ACTIVE],0  ;Non-zero = active
                JNE     I28_1
;----------------------------------------------------------------------
; Set the active flag to indicate that we're popping up. Before we do,
; however, service the old Int 8.
;----------------------------------------------------------------------
                MOV     BYTE PTR CS:[ACTIVE],-1 ;Nonzero means busy

                STI                             ;Enable interrupts
                PUSHF                           ;Simulate interrupt

                CALL    DWORD PTR CS:[OLDINT28] ;Perform old Int 28

                JMP     SHORT POPUP             ;Go pop up

INT_28          ENDP

;======================================================================
; POPUP (Near)
;
; Control is tranferred to this routine to pop up the timer window on
; the screen. Other routines have determined that it is safe to do so.
;----------------------------------------------------------------------
; Entry: None
; Exit : None
;----------------------------------------------------------------------
; Changes: None (ISR protocols)
;----------------------------------------------------------------------
L1              DB      BOX_COL+2 ,BOX_ROW+1,"DATE",0
L2              DB      BOX_COL+2 ,BOX_ROW+2,"TIME",0
L4              DB      BOX_COL+2 ,BOX_ROW+4,"ELAPSED",0
L6              DB      BOX_COL+2 ,BOX_ROW+6,"PRINTER IS O"
PRN_STAT        DB      "FF",0
L7              DB      BOX_COL+2 ,BOX_ROW+7,"CONSOLE IS O"
CON_STAT        DB      "N ",0

L9              DB      BOX_COL+3 ,BOX_ROW+9 ,"GO   STOP   RESET",0
L10             DB      BOX_COL+3 ,BOX_ROW+10,"PRINTER   CONSOLE",0

MSG_TBL         DW      L1, L2, L4
PC_TBL          DW      L6, L7, L9, L10
MSG_QTY         EQU     ($-MSG_TBL)/2

;----------------------------------------------------------------------
POPUP           PROC    NEAR
        ASSUME  CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING

                PUSH    AX                      ;Save registers
                PUSH    BX
                PUSH    CX
                PUSH    DX
                PUSH    SI
                PUSH    DI
                PUSH    BP
                PUSH    DS
                PUSH    ES

                MOV     AX,CS                   ;Point DS and ES
                MOV     DS,AX                   ; to this segment
        ASSUME  DS:CSEG
                MOV     ES,AX
        ASSUME  ES:CSEG

                CLD                             ;String moves forward
;----------------------------------------------------------------------
; Check that we're in a text video mode. Determine our colors.
;----------------------------------------------------------------------
                MOV     CL,CO_ATTR              ;Assume color window

                MOV     AH,0FH                  ;Get current video mode fn
                INT     10H                     ; thru BIOS

                CMP     AL,3                    ;All CGA text modes
                JBE     P_1

                MOV     CL,BW_ATTR              ;Set mono colors
                CMP     AL,7                    ;Mono text mode?
                JE      P_1
;----------------------------------------------------------------------
; Return to whoever issued the original Int 8 or Int 28h.
;----------------------------------------------------------------------
P_EXIT:
                POP     ES                      ;Restore all registers
        ASSUME  ES:NOTHING
                POP     DS
        ASSUME  DS:NOTHING
                POP     BP
                POP     DI
                POP     SI
                POP     DX
                POP     CX
                POP     BX
                POP     AX

                MOV     BYTE PTR CS:[ACTIVE],0  ;Turn off active flag
                IRET
;----------------------------------------------------------------------
; Save the current video details for later restoration.
;----------------------------------------------------------------------
        ASSUME  DS:CSEG, ES:CSEG
P_1:
                MOV     [VATTR],CL              ;Save color scheme
                MOV     [VPAGE],BH              ;Save current page

                MOV     AH,3                    ;Get cursor position fn
                INT     10H                     ; thru BIOS

                MOV     [CUR_SIZ],CX            ;Save cursor size
                MOV     [CUR_POS],DX            ;Save cursor position
;----------------------------------------------------------------------
; This BIOS call makes the cursor invisible on most video adapters.
; Some EGAs, however, have trouble with it.
;----------------------------------------------------------------------
                MOV     AH,1                    ;Set cursor shape
                MOV     CX,2000H                ;Invisible on most PCs
                INT     10H                     ; thru BIOS
;----------------------------------------------------------------------
; Save the section of the screen we will be writing over.
;----------------------------------------------------------------------
                MOV     SI,-1                   ;Tell proc to save
                MOV     DI,OFFSET SCREEN_BUF    ;Destination for save
                CALL    SCREEN                  ;Save user's screen
;----------------------------------------------------------------------
; Clear the screen and draw the popup window and shadow.
;----------------------------------------------------------------------
                CALL    DRAW_BOX                ;Create frame
;----------------------------------------------------------------------
; Fill in the text labels.
;----------------------------------------------------------------------
                MOV     BX,OFFSET MSG_TBL       ;Message located here
                MOV     CX,MSG_QTY              ;Display this many
                CALL    WR_STRINGS
;----------------------------------------------------------------------
; Display timer info and act on commands.
;----------------------------------------------------------------------
                CALL    CLOCK                   ;Activate clocks
;----------------------------------------------------------------------
; Restore the screen to its original state.
;----------------------------------------------------------------------
                MOV     SI,OFFSET SCREEN_BUF    ;Point to data
                CALL    SCREEN                  ;Restore screen

                MOV     AH,1                    ;Set cursor shape
                MOV     CX,[CUR_SIZ]            ;Restore original shape
                INT     10H                     ; thru BIOS

                MOV     AH,2                    ;Set Cursor position
                MOV     DX,[CUR_POS]            ;Restore old cursor
                INT     10H                     ; thru BIOS

                JMP     P_EXIT

POPUP           ENDP

;======================================================================
; SCREEN (Near)
;
; Saves or restores the screen data in our window area based on the
; values passed to it. Uses BIOS functions to read and write screen.
;----------------------------------------------------------------------
; Entry:
;       SI = -1, copy from screen to buffer
;            ES:DI = destination buffer address
;
;       SI != -1, copy from buffer at DS:SI to screen
; Exit:
;       None
;----------------------------------------------------------------------
; Changes: AX BX CX DX SI DI
;----------------------------------------------------------------------
SCREEN          PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:CSEG, SS:NOTHING

                MOV     BH,[VPAGE]              ;Active video page
                MOV     CX,NROW+1               ;Row loop counter
                MOV     DH,BOX_ROW              ;Init row pointer
;----------------------------------------------------------------------
; Loop here for each row.
;----------------------------------------------------------------------
S_1:
                PUSH    CX                      ;Save row counter

                MOV     CX,NCOL+2               ;Set up column loop
                MOV     DL,BOX_COL              ;Column Pointer
;----------------------------------------------------------------------
; Loop here for each column.
;----------------------------------------------------------------------
S_2:
                PUSH    CX                      ;Save column counter

                MOV     AH,2                    ;Position cursor fn
                INT     10H                     ; thru BIOS

                CMP     SI,-1                   ;SI =FFFF if SAVE
                JE      S_3
;----------------------------------------------------------------------
; Restore the screen from the buffer at DS:SI.
;----------------------------------------------------------------------
                LODSW                           ;Get char and attribute
                MOV     BL,AH                   ;Put attribute where needed
                MOV     AH,9                    ;Write char fn
                MOV     CX,1                    ;Write one copy of char
                INT     10H                     ; thru BIOS
                JMP     SHORT S_4
;----------------------------------------------------------------------
; Copy from the screen to the buffer at ES:DI.
;----------------------------------------------------------------------
S_3:
                MOV     AH,8                    ;Get char & attribute fn
                INT     10H                     ; thru BIOS
                STOSW                           ;Write to buffer
;----------------------------------------------------------------------
; Loop for each column and row.
;----------------------------------------------------------------------
S_4:
                INC     DL                      ;Next column
                POP     CX                      ;Restore column counter
                LOOP    S_2                     ;Close Inner loop

                INC     DH                      ;Next row
                POP     CX                      ;Restore row counter
                LOOP    S_1                     ;Close Outer loop

                RET

SCREEN          ENDP

;======================================================================
; DRAW_BOX (Near)
;
; Clears a window (box) on the screen and builds the popup window.
;----------------------------------------------------------------------
; Entry: None
; Exit : None
;----------------------------------------------------------------------
; Changes: AX BX CX DX SI
;----------------------------------------------------------------------
BOX_MSG         DB      BOX_COL+3,BOX_ROW,181,"PopWatch  1.00",198,0
BOX_CHARS       DB      201,205,187     ;Top row
                DB      186,196,186     ;Middle rows
                DB      200,205,188     ;Bottom row

;----------------------------------------------------------------------
DRAW_BOX        PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:CSEG, SS:NOTHING
;----------------------------------------------------------------------
; Clear two overlapping rectangles on the screen to create a shadow
; box effect.
;----------------------------------------------------------------------
                MOV     AX,0600H                ;Scroll window fn
                MOV     CX,(BOX_ROW+1)*100H+BOX_COL+1
                MOV     DX,(BOX_ROW+NROW)*100H+BOX_COL+NCOL+1
                MOV     BH,[VATTR]              ;Window color
                INT     10H                     ; thru BIOS

                MOV     AX,0600H                ;Scroll window fn
                MOV     CX,BOX_ROW*100H+BOX_COL
                MOV     DX,(BOX_ROW+NROW-1)*100H+BOX_COL+NCOL-1
                MOV     BH,[VATTR]              ;Window color
                INT     10H                     ; thru BIOS
;----------------------------------------------------------------------
; Draw box as one top line, repeated middle lines, and one end line.
;----------------------------------------------------------------------
                MOV     DH,CH                   ;Cur row from last call
                MOV     BH,[VPAGE]              ;Active video page
                MOV     SI,OFFSET BOX_CHARS     ;Chars to draw
                MOV     CX,NROW                 ;Number of rows to draw
CB_1:
                PUSH    CX                      ;Save counter

                MOV     AH,2                    ;Position cursor
                MOV     DL,BOX_COL              ; to this row,column
                INT     10H                     ; thru BIOS

                LODSB                           ;Get leftmost char
                MOV     AH,0EH                  ;Write char TTY
                INT     10H                     ; thru BIOS

                LODSB                           ;Get middle char
                MOV     AH,0AH                  ;Write repeated char
                MOV     CX,NCOL-2               ;This many copies
                INT     10H                     ; thru BIOS

                MOV     AH,2                    ;Position cursor
                MOV     DL,BOX_COL+NCOL-1       ;Col = righthand edge
                INT     10H                     ; thru BIOS

                LODSB                           ;Get rightmost char
                MOV     AH,0EH                  ;Write char TTY
                INT     10H                     ; thru BIOS

                INC     DH                      ;Next row
                POP     CX                      ;Restore counter
;----------------------------------------------------------------------
; The first and last rows are drawn once. For the middle rows, SI is
; backed up and the chars are used again.
;----------------------------------------------------------------------
                CMP     CL,NROW                 ;Jump if 1st row
                JE      CB_2

                CMP     CL,2                    ;Jump if last row
                JE      CB_2

                SUB     SI,3                    ;Repeat row chars
CB_2:
                LOOP    CB_1
;----------------------------------------------------------------------
; Display the program's name in the box border.
;----------------------------------------------------------------------
                MOV     SI,OFFSET BOX_MSG       ;Message to write
                CALL    WR_MESSAGE

                RET

DRAW_BOX        ENDP

;======================================================================
; WR_MESSAGE (Near)
;
; Write a string to the screen using the BIOS TTY function.
;----------------------------------------------------------------------
; Entry:
;       DS:SI = string to write
;         String format is:
;         DB BIOS screen column
;         DB BIOS screen row
;         DB text,0
; Exit:
;       None
;----------------------------------------------------------------------
; Changes: AX BX DX SI
;----------------------------------------------------------------------
WR_MESSAGE      PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING

                MOV     BH,[VPAGE]              ;Get active page
                LODSW                           ;Get cursor position
                MOV     DX,AX                   ; in DX

                MOV     AH,2                    ;Position cursor fn
WM_1:
                INT     10H                     ; thru BIOS

                MOV     AH,0EH                  ;Write TTY
                LODSB                           ;Get character
                OR      AL,AL                   ;0 if end of string
                JNZ     WM_1

                RET

WR_MESSAGE      ENDP

;======================================================================
; WR_STRINGS (Near)
;
; Copy a list of strings to the console.
;----------------------------------------------------------------------
; Entry:
;       DS:BX = offset of table of near string pointers.
; Exit:
;       None
;----------------------------------------------------------------------
; Changes: BX CX SI
;----------------------------------------------------------------------
WR_STRINGS      PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING
WS_1:
                PUSH    CX                      ;Save string count

                MOV     SI,[BX]                 ;Get pointer
                INC     BX                      ;Point to next
                INC     BX                      ; pointer

                PUSH    BX                      ;Save across call
                CALL    WR_MESSAGE              ;Write to screen
                POP     BX                      ;Restore pointer

                POP     CX                      ;Restore counter
                LOOP    WS_1

                RET

WR_STRINGS      ENDP

;======================================================================
; CLOCK (Near)
;
; Display the stopwatch interface on the screen.
;----------------------------------------------------------------------
; Entry: None
; Exit : None
;----------------------------------------------------------------------
; Changes: AX BX CX SI DI
;----------------------------------------------------------------------
CON_FLAG        DB      -1              ;Console print, on
PRN_FLAG        DB      0               ;Printer flag, off
REFRESH         DB      0               ;Nonzero when refreshing window

REF_TICKS       DD      0               ;32-bit elapsed tick reference

DATE_B          DB      BOX_COL+12,BOX_ROW+1,"MM/DD/YY",0 ;Date

TIME_B          DB      BOX_COL+12,BOX_ROW+2,"HH:MM:SS",0 ;Time
ET_B            DB      BOX_COL+12,BOX_ROW+4,"HH:MM:SS",0 ;Elapsed time

UPD_TBL         DW      TIME_B, ET_B
UPD_QTY         EQU     ($-UPD_TBL)/2

;----------------------------------------------------------------------
CLOCK           PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:CSEG, SS:NOTHING
;----------------------------------------------------------------------
; Display the date once. This never changes.
;----------------------------------------------------------------------
                MOV     SI,OFFSET DATE_B        ;Pointer to message
                CALL    WR_MESSAGE              ;Write to CRT
;----------------------------------------------------------------------
; Begin the refresh cycle.
;----------------------------------------------------------------------
CL_1A:
                NOT     BYTE PTR [REFRESH]      ;Non-zero = refresh
CL_1B:
                MOV     AH,1                    ;Check if key ready
                INT     16H                     ; Thru BIOS
                JNZ     CL_1C
;----------------------------------------------------------------------
; No key is waiting. Call the DO_CMD proc to write the new time and
; elapsed time values to the strings. The strings are then copied to
; the screen.
;----------------------------------------------------------------------
                MOV     DI,OFFSET TIME_B+2      ;String destination
                MOV     AL,"t"                  ;Update time
                CALL    DO_CMD

                MOV     DI,OFFSET ET_B+2        ;String destination
                MOV     AL,"e"                  ;Update elapsed time
                CALL    DO_CMD

                MOV     CX,UPD_QTY              ;Number of strings
                MOV     BX,OFFSET UPD_TBL       ;Pointer to pointers
                CALL    WR_STRINGS              ;Write to screen
                JMP     CL_1B
;----------------------------------------------------------------------
; The BIOS reports a key is ready. End refresh mode, fetch the key, and
; process it.
;----------------------------------------------------------------------
CL_1C:
                NOT     BYTE PTR [REFRESH]      ;0 = Refresh complete

                SUB     AH,AH                   ;Get the key
                INT     16H                     ; thru BIOS
;----------------------------------------------------------------------
; Check the keystroke and act on it.
;----------------------------------------------------------------------
                CMP     AL,ESCAPE               ;ESC means exit
                JE      CL_2A

                CMP     AX,ENDKEY               ;END means exit
                JNE     CL_2B
CL_2A:
                RET
;----------------------------------------------------------------------
; Let DO_CMD determine if this is a valid command.
;----------------------------------------------------------------------
CL_2B:
                OR      AL,20H                  ;Make lower case
                SUB     AH,AH                   ;Indicate key input

                CALL    DO_CMD                  ;Act on the command
                JMP     CL_1A

CLOCK           ENDP

;======================================================================
; DO_CMD (Near)
;
; Interprets commands that are passed to it to turn on and off options
; and display information. This procedure acts somewhat differently
; when running in REFRESH mode.
;----------------------------------------------------------------------
; Entry:
;       ES:DI = string destination if refreshing
; Exit:
;       None
;
; Note:
;   When called from the resident portion, CS=DS=ES=resident segment.
;   When called from the currently executing copy CS=DS=seg of current
;     copy and ES=seg of resident copy.
;----------------------------------------------------------------------
; Changes: BX CX DX DI
;----------------------------------------------------------------------
STDOUT          EQU     1               ;System file handles
STDPRN          EQU     4

ET_FLAG         DB      0               ;Elapsed timer, defaults to off

TIME_MSG        DB      "PopWatch -> "
TIME_SLOT       DB      "XX:XX:XX",CR,LF
TIME_LEN        EQU     $-TIME_MSG

;----------------------------------------------------------------------
DO_CMD          PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING

                PUSH    AX                      ;Save registers
                PUSH    SI
;----------------------------------------------------------------------
; The P and C functions can be processed during both popup and command
; line execution.
;----------------------------------------------------------------------
                CMP     AL,"p"                  ;Printer status
                JE      DC_1A

                CMP     AL,"c"                  ;Console status
                JNE     DC_2
;----------------------------------------------------------------------
; C = change the console output status.
;----------------------------------------------------------------------
                MOV     BX,OFFSET CON_FLAG      ;Point BX to flag
                MOV     DI,OFFSET CON_STAT      ;Point DI to string
                JMP     SHORT DC_1B
;----------------------------------------------------------------------
; P = change the printer output status.
;----------------------------------------------------------------------
DC_1A:
                MOV     BX,OFFSET PRN_FLAG      ;Point BX to flag
                MOV     DI,OFFSET PRN_STAT      ;Point DI to string
;----------------------------------------------------------------------
; If a + or - was present, set the status to ON or OFF, respectively.
; If not, just toggle the current state.
;----------------------------------------------------------------------
DC_1B:
                MOV     AL,BYTE PTR ES:[BX]     ;Get current flag
                NOT     AL                      ;Toggle it
                OR      AH,AH                   ;0 = toggle request
                JZ      DC_1C

                SUB     AL,AL                   ;Turn flag off
                CMP     AH,"-"                  ;Was that the request?
                JE      DC_1C

                NOT     AL                      ;Turn flag on
                CMP     AH,"-"                  ;Was that the request?
                JNE     DC_EXIT
DC_1C:
                MOV     BYTE PTR ES:[BX],AL     ;Save new status

                OR      AL,AL                   ;Check final status
                MOV     AX,"FF"                 ;Change string to OFF
                JE      DC_1D

                MOV     AX," N"                 ; or to ON
DC_1D:
                STOSW                           ;Write word to ES:DI

                CMP     BYTE PTR ES:[ACTIVE],0  ;If command line mode
                JE      DC_EXIT                 ; return
;----------------------------------------------------------------------
; Update the window in popup mode.
;----------------------------------------------------------------------
                MOV     BX,OFFSET PC_TBL        ;Table of pointers
                MOV     CX,2                    ;P and C strings only
                CALL    WR_STRINGS              ;Write to screen
                JMP     SHORT DC_EXIT
;----------------------------------------------------------------------
; Process the options related to the elapsed timer.
;----------------------------------------------------------------------
DC_2:
                MOV     BX,OFFSET REF_TICKS     ;Point to reference
;----------------------------------------------------------------------
; R = reset the elapsed timer to 0 and start it.
;----------------------------------------------------------------------
                CMP     AL,"r"                  ;Reset timer?
                JNE     DC_3

                MOV     AH,-1                   ;Turn timer on
                CALL    GET_TICKS               ;Get ticks in CX:DX
                JMP     SHORT DC_4C
;----------------------------------------------------------------------
; G = start the elapsed timer, retaining cumulative value.
;----------------------------------------------------------------------
DC_3:
                MOV     AH,ES:[ET_FLAG]         ;Get timer status in AH

                CMP     AL,"g"                  ;G = turn timer on
                JNE     DC_4A

                OR      AH,AH                   ;Ignore if already on
                JNZ     DC_EXIT

                NOT     AH                      ;AH = FF
                JMP     SHORT DC_4B
;----------------------------------------------------------------------
; S = stop the timer at its current value.
;----------------------------------------------------------------------
DC_4A:
                CMP     AL,"s"                  ;Turn timer off?
                JNE     DC_5A

                OR      AH,AH                   ;Ignore if already off
                JZ      DC_EXIT

                SUB     AH,AH                   ;Turn timer off
;----------------------------------------------------------------------
; Get the current ticks. Calculate the difference between the reference
; and the current and store the result back into the reference.
;----------------------------------------------------------------------
DC_4B:
                CALL    GET_TICKS               ;Get ticks in CX:DX

                SUB     DX,WORD PTR ES:[BX][0]  ;Low word
                SBB     CX,WORD PTR ES:[BX][2]  ;High word
DC_4C:
                MOV     WORD PTR ES:[BX][0],DX  ;Save low word
                MOV     WORD PTR ES:[BX][2],CX  ; and high word
DC_4D:
                MOV     ES:[ET_FLAG],AH         ;Update timer status
;----------------------------------------------------------------------
; Exit the routine.
;----------------------------------------------------------------------
DC_EXIT:
                POP     SI                      ;Restore registers
                POP     AX
                RET                             ;Return from proc
;----------------------------------------------------------------------
; The D command can only be performed in command line mode.
; It is ignored when popped up and never called during refresh.
;----------------------------------------------------------------------
DC_5A:
                CMP     AL,"d"                  ;Report date
                JNE     DC_6A

                CMP     BYTE PTR ES:[ACTIVE],0  ;Must be inactive
                JNE     DC_EXIT
;----------------------------------------------------------------------
; D = display the current date.
;----------------------------------------------------------------------
                MOV     SI,OFFSET DATE_B+2      ;ES:SI = source
                MOV     DI,OFFSET TIME_SLOT     ;DS:DI = destination
                MOV     CX,8                    ;String length
DC_5B:
                MOV     AL,ES:[SI]              ;Get date digit
                MOV     DS:[DI],AL              ;Put in local string
                INC     DI                      ;Advance pointers
                INC     SI
                LOOP    DC_5B
                JMP     SHORT DC_8B
;----------------------------------------------------------------------
; The T and E commands can be performed in command line mode or during
; a resident refresh, but not from the keyboard while popped up.
;----------------------------------------------------------------------
DC_6A:
                CMP     BYTE PTR ES:[ACTIVE],0  ;Must be command line
                JE      DC_6B

                CMP     BYTE PTR ES:[REFRESH],0 ; or refreshing
                JE      DC_EXIT
;----------------------------------------------------------------------
; E = report or refresh elapsed time.
;----------------------------------------------------------------------
DC_6B:
                CMP     AL,"e"                  ;Report elapsed time
                JNE     DC_7A
;----------------------------------------------------------------------
; If the elapsed timer is off, simply convert the stored number.
;----------------------------------------------------------------------
                CMP     BYTE PTR ES:[ET_FLAG],0 ;Check timer status
                JNE     DC_6C

                MOV     DX,WORD PTR ES:[REF_TICKS][0]   ;Low word
                MOV     CX,WORD PTR ES:[REF_TICKS][2]   ;High word
                JMP     SHORT DC_7B
;----------------------------------------------------------------------
; If the timer is on, get the difference between the current time and
; the reference time.
;----------------------------------------------------------------------
DC_6C:
                CALL    GET_TICKS               ;Get ticks in CX:DX

                SUB     DX,WORD PTR ES:[REF_TICKS][0]   ;Low word
                SBB     CX,WORD PTR ES:[REF_TICKS][2]   ;High word
                JMP     SHORT DC_7B
;----------------------------------------------------------------------
; T = report/refresh current time.
;----------------------------------------------------------------------
DC_7A:
                CMP     AL,"t"                  ;Report current time
                JNE     DC_EXIT

                CALL    GET_TICKS               ;Get ticks in CX:DX
DC_7B:
                CALL    TICK2HMS                ;Convert CX:DX to time
;----------------------------------------------------------------------
; If we're in refresh mode, put the string in the desired location.
;----------------------------------------------------------------------
                CMP     ES:[REFRESH],0          ;Check refresh status
                JE      DC_8A

                MOV     CX,2                    ;Want HH:MM:SS
                CALL    ASC_TIME                ;Perform the conversion
                JMP     DC_EXIT
;----------------------------------------------------------------------
; For command line mode, format the info into a displayable message.
;----------------------------------------------------------------------
DC_8A:
                PUSH    ES                      ;Save resident segment

                MOV     DI,OFFSET TIME_SLOT     ;Point ES:DI to string
                PUSH    DS                      ;Assume nothing so
                POP     ES                      ; always consistent
        ASSUME  ES:NOTHING

                MOV     CX,2                    ;Want HH:MM:SS
                CALL    ASC_TIME                ;Perform the conversion

                POP     ES                      ;Restore segment
        ASSUME  ES:NOTHING
;----------------------------------------------------------------------
; In command line mode, the display may be going to the console...
;----------------------------------------------------------------------
DC_8B:
                CMP     BYTE PTR ES:[CON_FLAG],0 ;0 = don't write
                JE      DC_8C

                MOV     AH,40H                  ;Write to handle
                MOV     BX,STDOUT               ;Console
                MOV     CX,TIME_LEN             ;This many bytes
                MOV     DX,OFFSET TIME_MSG      ;From this location
                INT     21H                     ; thru DOS
;----------------------------------------------------------------------
; ...and it may go to the printer.
;----------------------------------------------------------------------
DC_8C:
                CMP     BYTE PTR ES:[PRN_FLAG],0 ;0 = don't write
                JE      DC_8D

                MOV     AH,40H                  ;Write to handle
                MOV     BX,STDPRN               ;Printer
                MOV     CX,TIME_LEN             ;This many bytes
                MOV     DX,OFFSET TIME_MSG      ;From this location
                INT     21H                     ; thru DOS
DC_8D:
                JMP     DC_EXIT

DO_CMD          ENDP

;======================================================================
; GET_TICKS (Near)
;
; This routine performs the same function as the BIOS Get Tick Count
; (Int 1Ah, AH=0), but takes about half the time.
;----------------------------------------------------------------------
; Entry:
;       None
; Exit:
;       CX:DX = 32-bit timer tick clock count
;----------------------------------------------------------------------
; Changes: CX DX
;----------------------------------------------------------------------
TIMER_ADR       DD      0000046CH               ;Address of clock count

;----------------------------------------------------------------------
GET_TICKS       PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING

                PUSH    BX                      ;Preserve registers
                PUSH    DS

                LDS     BX,[TIMER_ADR]          ;Point to tick count
        ASSUME  DS:NOTHING

                LDS     DX,[BX]                 ;Get count in DS:DX
        ASSUME  DS:NOTHING

                MOV     CX,DS                   ;Put high count in CX

                POP     DS                      ;Restore registers
        ASSUME  DS:CSEG
                POP     BX
                RET

GET_TICKS       ENDP

;======================================================================
; TICK2HMS (Near)
;
; Convert BIOS timer ticks to HH:MM:SS.ss. The math in this routine
; was "borrowed" from the internal DOS routines that convert timer
; ticks to time representation.
;----------------------------------------------------------------------
; Entry:
;       CX:DX = 32-bit timer tick count
; Exit:
;       AH = hours
;       AL = minutes
;       BH = seconds
;       BL = hundredths of seconds
;----------------------------------------------------------------------
; Changes: AX BX
;----------------------------------------------------------------------
TICK2HMS        PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING

                PUSH    CX                      ;Save used registers
                PUSH    DX

                MOV     AX,CX
                MOV     BX,DX
                SHL     DX,1
                RCL     CX,1
                SHL     DX,1
                RCL     CX,1
                ADD     DX,BX
                ADC     AX,CX
                XCHG    AX,DX
                MOV     CX,0E90BH
                DIV     CX
                MOV     BX,AX
                XOR     AX,AX
                DIV     CX
                MOV     DX,BX
                MOV     CX,00C8H
                DIV     CX
                CMP     DL,64H
                JB      TICK1
                SUB     DL,64H
TICK1:
                CMC
                MOV     BL,DL
                RCL     AX,1
                MOV     DL,00
                RCL     DX,1
                MOV     CX,003CH
                DIV     CX
                MOV     BH,DL
                DIV     CL
                XCHG    AL,AH
                ADD     BL,5                    ;Correct for round off

                POP     DX                      ;Restore registers
                POP     CX
                RET

TICK2HMS        ENDP

;======================================================================
; ASC_TIME (Near)
;
; Converts HH:MM:SS.ss time in AX:BX registers to an ASCII string in
; HH:MM:SS.ss format. The precision of the conversion can be specified.
;----------------------------------------------------------------------
; Entry:
;       AH = hours
;       AL = minutes
;       BH = seconds
;       BL = hundredths
;       ES:DI = destination for ASCII string
;       CX = parsing
;         0 - HH
;         1 - HH:MM
;         2 - HH:MM:SS
;         3 - HH:MM:SS.ss
; Exit:
;       None
;----------------------------------------------------------------------
; Changes: CX DI
;----------------------------------------------------------------------
ASC_TIME        PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING

                PUSH    DX                      ;Preserve register
;----------------------------------------------------------------------
; Convert the time numbers to ASCII and store in the string at ES:DI.
;----------------------------------------------------------------------
                MOV     DL,AL                   ;Save MM
                MOV     DH,":"                  ;Save colon char

                MOV     AL,AH                   ;Get hours
                CALL    STO_ASC                 ;Convert it
                JCXZ    AT_EXIT

                DEC     CX                      ;Change parse flag
                MOV     AL,DH                   ;Write a colon
                STOSB                           ; to ES:DI

                MOV     AL,DL                   ;Get minutes
                CALL    STO_ASC                 ;Convert it
                JCXZ    AT_EXIT

                DEC     CX                      ;Change parse flag
                MOV     AL,DH                   ;Write a colon
                STOSB                           ; to ES:DI

                MOV     AL,BH                   ;Get seconds
                CALL    STO_ASC                 ;Convert it
                JCXZ    AT_EXIT

                MOV     AL,"."                  ;Write a decimal point
                STOSB                           ; to ES:DI

                MOV     AL,BL                   ;Get hundredths
                CALL    STO_ASC                 ;Convert it
AT_EXIT:
                POP     DX                      ;Restore register
                RET

ASC_TIME        ENDP

;======================================================================
; STO_ASC (Near)
;
; Convert a number in AL to two denary ASCII characters and write to
; ES:DI. The number must fall within the range 0-99.
;----------------------------------------------------------------------
; Entry:
;       AL = number to convert. 0-99 only.
; Exit:
;       None
;----------------------------------------------------------------------
; Changes: AX DI
;----------------------------------------------------------------------
STO_ASC         PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:NOTHING

                AAM                             ;Split denary digits
                OR      AX,"00"                 ;Convert to ASCII
                XCHG    AL,AH                   ;Reverse digits
                STOSW                           ;Write them
                RET

STO_ASC         ENDP

;======================================================================
; Data here is allocated after the program loads into memory to save
; space in the COM file. The PC assembler variable is used to keep
; track of relative addresses.
;----------------------------------------------------------------------
PC              =       $                       ;Set imaginary counter

SCREEN_BUF      =       PC                      ;DB NROW*NCOL*2 DUP(?)
PC              =       PC+(NROW+1)*(NCOL+2)*2

LAST_BYTE       =       PC

;======================================================================
; MAIN (Near)
;
; Execution begins here when POPWATCH is run from the command line.
;----------------------------------------------------------------------
; Transient data.
;----------------------------------------------------------------------
RESIDENT        DB      -1              ;Cleared to 0 if not resident
UMB_LINK        DB      -1              ;Holds UMB link status

;----------------------------------------------------------------------
; Messages.
;----------------------------------------------------------------------
COPYRIGHT$      DB      CR,LF,"PopWatch 1.00 Copyright (c) 1992 ",254
                DB      " Robert L. Hummel",CR,LF,"PC Magazine "
                DB      "Assembly Language Lab Notes",LF,CR,LF,"$"

RESIDENT$       DB      LF,"PopWatch 1.00 Already Resident",CR,LF,LF
USAGE$          DB      "Interactive : Pop up with ALT-P",CR,LF
                DB      "Command Line: PopWatch [D][T][E][G][S][R]"
                DB      "[C[+|-]][P[+|-]]",CR,LF,LF
                DB      "where",CR,LF
                DB      "  D = display date",CR,LF
                DB      "  T = display time",CR,LF
                DB      "  E = display elapsed time",CR,LF
                DB      "  G = start elapsed timer",CR,LF
                DB      "  S = stop elapsed timer",CR,LF
                DB      "  R = reset, then start elapsed timer",CR,LF
                DB      "  C = toggle console output",CR,LF
                DB      "  P = toggle printer output",CR,LF,LF
                DB      "  + = force output on",CR,LF
                DB      "  - = force output off",CR,LF,"$"

;----------------------------------------------------------------------
MAIN            PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG

                CLD                             ;String moves forward
;----------------------------------------------------------------------
; Determine if a copy of POPWATCH is already resident in memory by
; searching for a duplicate of the copyright notice.
; Modify marker string to avoid false matches when searching memory.
;----------------------------------------------------------------------
                NOT     WORD PTR [RES_MARKER]   ;Modify marker
;----------------------------------------------------------------------
; Get the DOS version. If ver 5 or later, save the current UMB state,
; then link them to search all memory.
;----------------------------------------------------------------------
                MOV     AH,30H                  ;Get DOS version in AX
                INT     21H                     ; thru DOS

                CMP     AL,5                    ;Dos 5 or later
                JB      M_1

                MOV     AX,5802H                ;Get current UMB link
                INT     21H                     ; thru DOS
                JC      M_1

                MOV     [UMB_LINK],AL           ;Save it

                MOV     AX,5803H                ;Set UMB to
                MOV     BX,1                    ; linked in chain
                INT     21H                     ; thru DOS
M_1:
;----------------------------------------------------------------------
; Get the segment address of the first MCB in BX.
;----------------------------------------------------------------------
                MOV     AH,52H                  ;Get ES:BX -> IVARS
                INT     21H                     ; thru DOS
        ASSUME  ES:NOTHING

                MOV     BX,ES:[BX-2]            ;Get first MCB
;----------------------------------------------------------------------
; Point ES to the segment in BX and look for the modified copyright.
;----------------------------------------------------------------------
                MOV     AX,DS                   ;Current seg in AX
M_2A:
                MOV     ES,BX                   ;Point ES to MCB
        ASSUME  ES:NOTHING
                INC     BX                      ;Point to block

                MOV     SI,OFFSET RES_MARKER    ;Compare DS:SI
                LEA     DI,[SI+10H]             ; to ES:DI

                MOV     CX,MARKER_LEN           ;Compare full string
                REPE    CMPSB                   ;CMP DS:SI TO ES:DI
                JNE     M_2B

                CMP     AX,BX                   ;If not this copy, done
                JNE     M_2C
M_2B:
;----------------------------------------------------------------------
; No match. Move to the next memory block.
;----------------------------------------------------------------------
                ADD     BX,ES:[3]               ;Add block length

                CMP     BYTE PTR ES:[0],"Z"     ;This block the last?
                JNE     M_2A
;----------------------------------------------------------------------
; If we get here, no resident copy was found in all of memory. Point ES
; to this copy, but assume NOTHING to consistently address resident
; copy.
;----------------------------------------------------------------------
                MOV     BYTE PTR [RESIDENT],0   ;Say none resident
                MOV     BX,AX                   ;Set ES to this seg
M_2C:
                MOV     ES,BX                   ;Set to memory block
        ASSUME  ES:NOTHING
;----------------------------------------------------------------------
; Restore the UMB link to its previous state.
;----------------------------------------------------------------------
                MOV     BL,CS:[UMB_LINK]        ;Original link state
                CMP     BL,-1                   ;Was it recorded?
                JE      M_2D

                SUB     BH,BH                   ;Link in BX
                MOV     AX,5803                 ;Set UMB link
                INT     21H                     ; thru DOS
M_2D:
;----------------------------------------------------------------------
; Update the date buffer. The DOS Get Date function returns with
; CX=YEAR, DH=MONTH, DL=DAY.
;----------------------------------------------------------------------
                MOV     AH,2AH                  ;Get date
                INT     21H                     ; thru DOS

                SUB     CX,1900                 ;Want last 2 digits

                MOV     DI,OFFSET DATE_B+2      ;Destination
                MOV     AL,DH
                CALL    STO_ASC                 ;Write month

                INC     DI
                MOV     AL,DL
                CALL    STO_ASC                 ;Write day

                INC     DI
                MOV     AL,CL
                CALL    STO_ASC                 ;Write year
;----------------------------------------------------------------------
; Read the command line and process any parameters in the order in
; which they appear.
;----------------------------------------------------------------------
                MOV     SI,81H                  ;Command line address
M_3A:
                LODSB                           ;Get character

                CMP     AL,SPACE                ;Skip blanks
                JE      M_3A

                CMP     AL,0DH                  ;CR means end
                JE      M_3D

                MOV     AH,AL                   ;Save switch in AH
                LODSB                           ;Get next char

                CMP     AL,"+"                  ;+ means turn on
                JE      M_3B

                CMP     AL,"-"                  ;- means turn off
                JE      M_3B

                DEC     SI                      ;Backup character
                SUB     AL,AL                   ;Clear AL to 0
M_3B:
                XCHG    AH,AL                   ;Put switch in AL
                OR      AL,20H                  ;Make it lower case
                CALL    DO_CMD                  ;Act on the command
                JMP     M_3A
M_3D:
;----------------------------------------------------------------------
; If there was no resident copy, go resident.
;----------------------------------------------------------------------
                CMP     BYTE PTR [RESIDENT],0   ;0 = not resident
                JE      M_5
;----------------------------------------------------------------------
; If no command line parameters were given, print the usage message.
; If command line parameters were present, simply terminate.
;----------------------------------------------------------------------
                MOV     DX,OFFSET RESIDENT$     ;Just print usage

                CMP     BYTE PTR DS:[80H],0     ;0 if no parameters
                JNE     M_4

                MOV     AH,9                    ;Display string fn
                INT     21H                     ; thru DOS
M_4:
                MOV     AH,4CH                  ;Terminate
                INT     21H                     ; thru DOS
;----------------------------------------------------------------------
; Get a pointer to the DOS Critical Flag, a one-byte location in low
; memory that is set when DOS is in an uninterruptable state. Location
; is returned in ES:BX.
;----------------------------------------------------------------------
M_5:
                MOV     AH,34H                  ;Get DOS BUSY flag ptr
                INT     21H                     ; thru DOS
        ASSUME  ES:NOTHING

                MOV     WORD PTR DOS_BUSY[0],BX ;offset
                MOV     WORD PTR DOS_BUSY[2],ES ;segment
;----------------------------------------------------------------------
; Hook Int 9 for the hot-key detection routine.
; Hook Int 8 and Int 28h to pop up.
;----------------------------------------------------------------------
                PUSH    DS                      ;Reset ES to point to same
                POP     ES                      ; segment as DS
        ASSUME  ES:CSEG

                MOV     AL,8                    ;Interrupt number
                MOV     DI,OFFSET OLDINT8       ;Store vector here
                MOV     DX,OFFSET INT_8         ;New interrupt procedure
                CALL    SET_INT                 ;Make change
        ASSUME  ES:NOTHING

                MOV     AL,9                    ;Interrupt number
                MOV     DI,OFFSET OLDINT9       ;Store vector here
                MOV     DX,OFFSET INT_9         ;New interrupt procedure
                CALL    SET_INT                 ;Make change
        ASSUME  ES:NOTHING

                MOV     AL,28H                  ;Interrupt number
                MOV     DI,OFFSET OLDINT28      ;Store vector here
                MOV     DX,OFFSET INT_28        ;New interrupt procedure
                CALL    SET_INT                 ;Make change
        ASSUME  ES:NOTHING
;----------------------------------------------------------------------
; Deallocate the copy of the environment loaded with the program.
;----------------------------------------------------------------------
                MOV     AX,WORD PTR DS:[2CH]    ;Address of environment
                MOV     ES,AX                   ;In ES register
        ASSUME  ES:NOTHING

                MOV     AH,49H                  ;Release allocated memory
                INT     21H                     ; thru DOS
;----------------------------------------------------------------------
; Display copyright notice and usage message.
;----------------------------------------------------------------------
                MOV     AH,9                    ;Display string
                MOV     DX,OFFSET COPYRIGHT$    ;Copyright notice
                INT     21H                     ; thru DOS

                MOV     AH,9                    ;Display string
                MOV     DX,OFFSET USAGE$        ;Show usage
                INT     21H                     ; thru DOS

                MOV     DX,(OFFSET LAST_BYTE - CSEG + 15) SHR 4
                MOV     AH,31H                  ;Keep (TSR)
                INT     21H                     ; thru DOS

MAIN            ENDP

;======================================================================
; SET_INT (Near)
;
; Get, save, and set an interrupt vector.
;----------------------------------------------------------------------
; Entry:
;       AL = vector number.
;       ES:DI = destination for old address.
;       DS:DX = new interrupt address.
; Exit:
;       None
;----------------------------------------------------------------------
; Changes: AX BX ES
;----------------------------------------------------------------------
SET_INT         PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG

                PUSH    AX                      ;Save vector # in AL

                MOV     AH,35H                  ;Get Int vector
                INT     21H                     ; thru DOS
        ASSUME  ES:NOTHING

                MOV     [DI+0],BX               ;Save address in ES:DI
                MOV     [DI+2],ES

                POP     AX                      ;Get AL back
                MOV     AH,25H                  ;Set Int vector
                INT     21H                     ; thru DOS

                RET

SET_INT         ENDP

CSEG            ENDS
                END     ENTPT
