Metropoli BBS
VIEWER: blitzkey.asm MODE: TEXT (ASCII)
;======================================================================
; BLITZKEY 1.00 Copyright (c) 1992, Robert L. Hummel
; PC Magazine Assembly Language Lab Notes
;
; BlitzKey is a keyboard macro facility and type-ahead buffer enhancer.
;======================================================================
; This segment is mapped onto the BIOS data area. Do not change the
; size or position of data elements -- they are fixed by the BIOS.
;----------------------------------------------------------------------
BIOSMEM         SEGMENT AT      40H             ;0040:0000

                ORG     01AH                    ;Location in the seg
KEYPTRS         LABEL   DWORD
 KEYHEAD        DW      ?
 KEYTAIL        DW      ?

KEYBUF          DW      16 DUP (?)
KEYEND          EQU     $

BIOSMEM         ENDS

;======================================================================
; Code segment.
;----------------------------------------------------------------------
CSEG            SEGMENT PARA    PUBLIC  'CODE'
        ASSUME  CS:CSEG,DS:CSEG,ES:CSEG,SS:CSEG
;----------------------------------------------------------------------
; Code entry point is here, at 100h. Jump over resident routines.
;----------------------------------------------------------------------
                ORG     100H                    ;COM file format
ENTPT:
                JMP     MAIN
;----------------------------------------------------------------------
; General program equates.
;----------------------------------------------------------------------
CR              EQU     13                      ;Common equates
LF              EQU     10
BLANK           EQU     32
SLASH           EQU     47

INSK    EQU 52H         ;Extended ASCII values
DEL             EQU     53H
F7KEY           EQU     41H
HOME            EQU     47H
ENDKEY          EQU     4FH
PGUP            EQU     49H
PGDN            EQU     51H
RARROW          EQU     4DH
LARROW          EQU     4BH
UARROW          EQU     48H
DARROW          EQU     50H
BS              EQU     0E08H                   ;Scan/Ascii code

JMPFAR          EQU     0EAH                    ;Opcodes that MASM
CALLFAR         EQU     09AH                    ; can't handle
RETFAR          EQU     0CBH

U_SW            EQU     1                       ;Request to Uninstall
E_SW            EQU     2                       ;Edit macros

;----------------------------------------------------------------------
; Resident messages.
;----------------------------------------------------------------------
RES_MARKER      DW      0                       ;Altered when resident
COPYRIGHT$      DB      CR,LF,"BlitzKey 1.00 ",254," Copyright (c) "
                DB      "1992, Robert L. Hummel",CR,LF,"PC Magazine "
                DB      "Assembly Language Lab Notes",LF,CR,LF,"$"
MARKER_LEN      EQU     $-RES_MARKER

;----------------------------------------------------------------------
; Enhanced buffer data. Changing ESIZE changes all dependent constants.
; Note that each char is stored as a 2-byte key code.
;----------------------------------------------------------------------
ESIZE           EQU     128                     ;Buffer size (keys)

EBUF            DW      ESIZE DUP (?)           ;Our key buffer
EMAX            EQU     $                       ;Maximum offset

EOUT            DW      EBUF                    ;Chars out here
EIN             DW      EBUF                    ; and in here
EFREE           DW      ESIZE                   ;Chars free

;----------------------------------------------------------------------
; Macro data. Note that the strings use a FAR pointer -- they can be
; located anywhere in memory. The length of the strings is stored as a
; variable and updated when a modified copy of the program is written
; to disk.
;----------------------------------------------------------------------
STR_LOC         DW      OFFSET STRINGS,0        ;FAR ptr to strings
STR_LEN         DW      OFFSET STRING_END - OFFSET STRINGS ;Str len

MOUT            DW      0                       ;Get macro keys here
EXPANDING       DB      0                       ;Non-zero if expanding
DISABLED        DB      0                       ;Non-zero to disable

;======================================================================
; INT_9 (ISR)
;
; The Int 9 routine in the BIOS translates keys and places them in the
; BBUF. Intercepting this interrupt gives us an opportunity to
; grab the key after it's put in the BIOS buffer and put it in EBUF.
;
; By checking before calling Int 9, we'll also get keys that were
; stuffed into the buffer by other programs and keys left there if
; EBUF overflows.
;----------------------------------------------------------------------
INT_9           PROC    FAR
        ASSUME  CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
;----------------------------------------------------------------------
; If any chars are present in BBUF, transfer them. Call with interrupts
; disabled.
;----------------------------------------------------------------------
                CALL    XFERBUF
                STI                             ;Enable interrupts
;----------------------------------------------------------------------
; To simulate an interrupt, push the flags, then make a far call to the
; original interrupt routine.
;----------------------------------------------------------------------
                PUSHF                           ;Save w/ints on

                CLI                             ;Disable interrupts
                DB      CALLFAR                 ;Call
OLD9            DD      -1                      ; old Int 9
;----------------------------------------------------------------------
; Check BBUF again, in case the Int 9 just processed put a key there.
;----------------------------------------------------------------------
                CLI                             ;Disable interrupts
                CALL    XFERBUF

                IRET                            ;Return to caller

INT_9           ENDP

;======================================================================
; INT_16 (ISR)
;
; This procedure replaces a portion of the original BIOS interrupt and
; also acts as a front end for the macro expander. Keys are fed to
; requestors from EBUF or from a macro string.
;
; If a program calls with the old 0 or 1 functions, extended scan codes
; are converted to their non-extended equivalent by changing the lower
; 8 bits to 0 from E0h.
;----------------------------------------------------------------------
; Scan codes returned for ALT+A through ALT+Z.
;----------------------------------------------------------------------
SCAN_TBL        DB      1EH, 30H, 2EH, 20H, 12H, 21H, 22H, 23H
                DB      17H, 24H, 25H, 26H, 32H, 31H, 18H, 19H
                DB      10H, 13H, 1FH, 14H, 16H, 2FH, 11H, 2DH
                DB      15H, 2CH

;----------------------------------------------------------------------
INT_16          PROC    FAR
        ASSUME  CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
;----------------------------------------------------------------------
; If keys are available in BBUF, transfer them to EBUF. Call with
; interrupts disabled.
;----------------------------------------------------------------------
                CALL    XFERBUF
                STI                             ;Enable interrupts
                CLD                             ;String moves forward
;----------------------------------------------------------------------
; If the keyboard function requested is not 0, 10h, 1, or 11h, simply
; pass the request on to the original handler.
;----------------------------------------------------------------------
                PUSH    AX                      ;Preserve original fn
                AND     AH,NOT 10H              ;Ignore extended bit

                CMP     AH,1                    ;Accept 0 or 1
                POP     AX                      ;(Restore original fn)
                JBE     I16_1

                CLI                             ;Disable interrupts
                DB      JMPFAR                  ;Far jump
OLD16           DD      -1                      ; to old handler
;----------------------------------------------------------------------
; Prepare to service the interrupt ourself.
;----------------------------------------------------------------------
I16_1:
                PUSH    BX                      ;Save used registers
                PUSH    CX
                PUSH    SI
                PUSH    DS
                PUSH    ES

                PUSH    CS                      ;Address local data
                POP     DS                      ; using DS
        ASSUME  DS:CSEG
                MOV     BX,AX                   ;Save original fn in BX
;----------------------------------------------------------------------
; If a macro expansion is in progress, all keys come from the macro
; expander, not EBUF.
;----------------------------------------------------------------------
                CMP     BYTE PTR [EXPANDING],0  ;Non-zero = use macro
                JE      I16_3A
;----------------------------------------------------------------------
; Macros are expanded here. Point ES:SI to macro string and load next
; key into AX.
;----------------------------------------------------------------------
I16_2A:
                MOV     ES,[STR_LOC][2]         ;Get segment
        ASSUME  ES:NOTHING
                MOV     SI,[MOUT]               ; and offset
                MOV     AX,ES:[SI]              ;Load next key
;----------------------------------------------------------------------
; If end of macro, turn expander off and go back to normal key stream.
;---------------------------------------------------------------------
                OR      AX,AX                   ;0 = end of macro
                JNZ     I16_2B

                MOV     [EXPANDING],AL          ;0 = expander off
                JMP     SHORT I16_3A
;----------------------------------------------------------------------
; If function 0 or 10h, move the pointer.
;----------------------------------------------------------------------
I16_2B:
                TEST    BH,1                    ;Set rtn flag & test
                JNZ     I16_EXIT

                ADD     [MOUT],2                ;Store new pointer
                JMP     SHORT I16_EXIT
;----------------------------------------------------------------------
; Point SI to the (possibly) next key to read from EBUF.
;----------------------------------------------------------------------
I16_3A:
                MOV     SI,[EOUT]               ;Get ptr to next key
;----------------------------------------------------------------------
; If no key available, then there's no need to check for a macro key.
;----------------------------------------------------------------------
                MOV     CX,ESIZE                ;Max it holds -
                SUB     CX,[EFREE]              ; # free = keys
                JZ      I16_4B
;----------------------------------------------------------------------
; Read the next key from EBUF. If not an extended char (and therefore
; not a macro key) process normally.
;----------------------------------------------------------------------
                LODSW                           ;Get key from EBUF

                OR      AL,AL                   ;AL=0 if extended
                JNZ     I16_4B
;----------------------------------------------------------------------
; If macro expansion is disabled, process the key normally.
;----------------------------------------------------------------------
                CMP     BYTE PTR [DISABLED],0   ;0 = enabled
                JNE     I16_4B
;----------------------------------------------------------------------
; If extended key, check for scan code of ALT+A through ALT+Z.
;----------------------------------------------------------------------
                PUSH    CX                      ;Preserve registers
                PUSH    DI

                PUSH    CS                      ;Set up for scan
                POP     ES                      ;Point ES:DI
        ASSUME  ES:CSEG

                XCHG    AH,AL                   ;Put scan code in AL
                MOV     DI,OFFSET SCAN_TBL      ;Match to this table
                MOV     CX,26                   ;Bytes to scan
                REPNE   SCASB                   ;Scan for match

                POP     DI                      ;Restore register
                JNE     I16_4A

                POP     AX                      ;Discard old CX
;----------------------------------------------------------------------
; The key was one of our macro keys. Delete the key from EBUF.
;----------------------------------------------------------------------
                INC     [EFREE]                 ;Indicate char now free
                CMP     SI,OFFSET EMAX          ;Past buffer end?
                JB      I16_3B
                MOV     SI,OFFSET EBUF          ;Wrap to beginning
I16_3B:
                MOV     [EOUT],SI               ;Store new pointer
;----------------------------------------------------------------------
; Load a pointer to the indicated macro, then restart with macro
; expansion on.
;----------------------------------------------------------------------
                XCHG    BX,CX                   ;CX=fn, BX=count
                NEG     BL                      ;Make BX into...
                ADD     BL,26-1                 ;... macro number
                ADD     BL,BL                   ;Double for indexing
                INC     BL                      ;Point to macro

                PUSH    DS                      ;Save register
                LDS     SI,DWORD PTR [STR_LOC]  ;Point DS:SI to strings
        ASSUME  DS:NOTHING
                CALL    GET_POINTER             ;Get pointer
                POP     DS                      ;Restore register
        ASSUME  DS:CSEG
                MOV     [MOUT],SI               ;Give to expander
                MOV     BYTE PTR [EXPANDING],-1 ;Expander on
                MOV     BX,CX                   ;Put function in BX
                JMP     I16_2A
;----------------------------------------------------------------------
; Was not a macro key, process as normal.
;----------------------------------------------------------------------
I16_4A:
                XCHG    AH,AL                   ;Restore AX
                POP     CX                      ;Restore register
;----------------------------------------------------------------------
; If 0 or 1 was called, filter out the extended keystrokes.
;----------------------------------------------------------------------
I16_4B:
                CMP     AL,0E0H                 ;E0 -> extended key
                JNE     I16_4C

                TEST    BH,10H                  ;NZ = extended fn
                JNZ     I16_4C

                SUB     AL,AL                   ;Cancel extended byte
I16_4C:
;----------------------------------------------------------------------
; If the function was 1 or 11h, we're just checking the buffer status.
; If no keys are available, return with ZF=1.
; Otherwise, return ZF=0 and the key in AX.
;----------------------------------------------------------------------
                TEST    BH,1                    ;NZ if 1 or 11h
                JZ      I16_5A

                OR      CX,CX                   ;Test to set zero flag
;----------------------------------------------------------------------
; Return from the interrupt. Discard the old flags.
;----------------------------------------------------------------------
I16_EXIT:
                POP     ES                      ;Restore registers
        ASSUME  ES:NOTHING
                POP     DS
        ASSUME  DS:NOTHING
                POP     SI
                POP     CX
                POP     BX
                RET     2                       ;Discard old flags
;----------------------------------------------------------------------
; AH=0 or 10h is the Wait for key function. If there is a key in the
; buffer, simply continue.
;----------------------------------------------------------------------
        ASSUME  DS:CSEG
I16_5A:
                OR      CX,CX                   ;Nonzero if had keys
                JNZ     I16_5C
;----------------------------------------------------------------------
; No key was in the buffer. Enter a loop to continuosly check the EBUF
; EFREE count until it decreases from the maximum, indicating a char
; was placed in EBUF. When we get one, restart the routine.
;----------------------------------------------------------------------
I16_5B:
                CALL    XFERBUF                 ;Was buffer stuffed?
                CMP     [EFREE],ESIZE           ;Equal means empty
                JE      I16_5B
                JMP     I16_3A
;----------------------------------------------------------------------
; Remove the key from EBUF.
;----------------------------------------------------------------------
I16_5C:
                INC     [EFREE]                 ;One more free

                CMP     SI,OFFSET EMAX          ;Past buffer end?
                JB      I16_5D

                MOV     SI,OFFSET EBUF          ;Wrap to beginning
I16_5D:
                MOV     [EOUT],SI               ;Store new pointer
                JMP     I16_EXIT

INT_16          ENDP

;======================================================================
; XFERBUF (NEAR)
;
; Examine the BBUF and, if any characters are present, move to EBUF.
; Note that because this routine may be called from INT_9 while it is
; already executing a call from INT_16, the XBUSY flag must be set and
; cleared carefully to avoid any chance of re-entrancy.
;----------------------------------------------------------------------
; Entry:
;       Interrupts disabled
; Exit :
;       Interrupts disabled
;----------------------------------------------------------------------
; Changes: FLAGS
;----------------------------------------------------------------------
XBUSY           DB      0                       ;Nonzero when busy

;----------------------------------------------------------------------
XFERBUF         PROC    NEAR
        ASSUME  CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING
;----------------------------------------------------------------------
; Ensure that this routine is never re-entered.
;----------------------------------------------------------------------
                CMP     BYTE PTR CS:[XBUSY],0   ;0=not busy
                JNE     X_2B
                INC     BYTE PTR CS:[XBUSY]     ;Say it's busy now
                STI
;----------------------------------------------------------------------
; Save all registers.
;----------------------------------------------------------------------
                PUSH    AX                      ;Save used registers
                PUSH    BX
                PUSH    CX
                PUSH    SI
                PUSH    DI
                PUSH    DS
                PUSH    ES

                CLD                             ;String moves forward
;----------------------------------------------------------------------
; Point DS:SI to BBUF and ES:DI to EBUF in anticipation of the move.
;----------------------------------------------------------------------
                MOV     AX,BIOSMEM              ;Address BIOS buffer
                MOV     DS,AX                   ; with DS
        ASSUME  DS:BIOSMEM

                PUSH    CS                      ;Address our buffer
                POP     ES                      ; with ES
        ASSUME  ES:CSEG

                MOV     SI,DS:[KEYHEAD]         ;BIOS head pointer
                MOV     CX,DS:[KEYTAIL]         ;BIOS tail pointer
                MOV     DI,CS:[EIN]             ;Write chars here
                MOV     BX,CS:[EFREE]           ;Room in our buf
;----------------------------------------------------------------------
; If EBUF is full, we can't transfer any characters.
;----------------------------------------------------------------------
                OR      BX,BX                   ;BX=chars free
X_1:
                JZ      X_2A
;----------------------------------------------------------------------
; If there are no characters in BBUF, we're done.
;----------------------------------------------------------------------
                CMP     SI,CX                   ;Head=tail=buffer empty
                JNZ     X_3A
;----------------------------------------------------------------------
; Update the pointers and exit.
;----------------------------------------------------------------------
X_2A:
                MOV     CS:[EIN],DI             ;Save new BIN
                MOV     CS:[EFREE],BX           ;And free space
                MOV     DS:[KEYHEAD],SI         ;Update BIOS pointer

                POP     ES                      ;Restore registers
        ASSUME  ES:NOTHING
                POP     DS
        ASSUME  DS:NOTHING
                POP     DI
                POP     SI
                POP     CX
                POP     BX
                POP     AX

                CLI                             ;Disable interrupts
                DEC     BYTE PTR CS:[XBUSY]     ;Say not busy
X_2B:
                RET                             ; after return
;----------------------------------------------------------------------
; At this point, we know there is at least one key in BBUF and room for
; at least one key in EBUF. Remove the key from BBUF.
;----------------------------------------------------------------------
        ASSUME  DS:BIOSMEM, ES:CSEG
X_3A:
                LODSW                           ;Get BIOS key codes

                CMP     AL,0F0H                 ;BIOS filters these out
                JNE     X_3B

                OR      AH,AH                   ;Accept 00F0h
                JZ      X_3B

                SUB     AL,AL                   ;Make normal
X_3B:
                CMP     SI,OFFSET KEYEND        ;If past end
                JB      X_3C

                MOV     SI,OFFSET KEYBUF        ; wrap pointer
X_3C:
;----------------------------------------------------------------------
; Place the key into EBUF.
;----------------------------------------------------------------------
                STOSW                           ;Put in EBUF
                CMP     DI,OFFSET EMAX          ;If past end
                JB      X_4

                MOV     DI,OFFSET EBUF          ; wrap pointer
X_4:
;----------------------------------------------------------------------
; Decrease the number of free chars. This operation sets the flags
; for the conditional jump at X_1.
;----------------------------------------------------------------------
                DEC     BX                      ;Decrease free chars
                JMP     X_1

XFERBUF         ENDP

;======================================================================
; GET_POINTER (Near)
;
; Find a string in the string list. Note that when the strings are
; being edited, they are located in CSEG. When searched during macro
; expansion, however, they can be anywhere.
;----------------------------------------------------------------------
; Entry:
;       CLD
;       BL = number (0-based) of string to find
;       DS:SI -> start of string block
; Exit :
;       DS:SI -> offset of desired string
;----------------------------------------------------------------------
; Changes: AX BX SI
;----------------------------------------------------------------------
GET_POINTER     PROC    NEAR
        ASSUME  CS:CSEG, DS:NOTHING, ES:NOTHING, SS:NOTHING

                SUB     BH,BH                   ;Count strings in BH
GP_0:
                CMP     BH,BL                   ;Equal when we're done
                JNE     GP_1

                RET                             ;Return to caller
GP_1:
                LODSW                           ;Fetch word
                OR      AX,AX                   ;If not zero, continue
                JNZ     GP_1

                INC     BH                      ;Found end of string
                JMP     GP_0

GET_POINTER     ENDP

;======================================================================
; SHRIVEL (NEAR)
;
; This code moves the strings lower in the segment during the first
; installation if there is no room lower in memory. It is really an
; extension of the REPLACE procedure, but has to appear here so that
; the relocated strings don't overwrite it.
;----------------------------------------------------------------------
; Entry:
;       DS:SI = string source
;       ES:DI = string destination
;       DX = number of paragraphs to keep resident
; Exit:
;       None
;----------------------------------------------------------------------
SHRIVEL         PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG

                REP     MOVSB                   ;Transfer the strings

                NOT     WORD PTR [RES_MARKER]   ;Modify for TSR

                MOV     AH,31H                  ;Keep process
                INT     21H                     ; thru DOS

SHRIVEL         ENDP

;======================================================================
; When BLITZKEY becomes resident, everything after CUTOFF is discarded
; or written over by the strings.
;----------------------------------------------------------------------
CUTOFF          EQU     $

;======================================================================
; Transient data -- discarded when resident.
;----------------------------------------------------------------------
ERR_MEMSIZ$     DB      "There's Not Enough Memory To Execute$"
USAGE$          DB      "Usage: BLITZKEY [/U|/E]$"

COM_PTR         DW      STRING_END      ;Cannot exceed 64k-200H
RES_SEG         DW      -1              ;Init to none resident
STACK_TOP       DW      0               ;Top of relocated stack

UMB_LINK        DB      -1              ;Init to not present

;======================================================================
; MAIN (Near)
;
; The MAIN procedure interprets the command line switches, checks for
; previous copies in memory, and instigates all memory management.
;----------------------------------------------------------------------
MAIN            PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG

                CLD                             ;String moves forward
;----------------------------------------------------------------------
; Relocate the stack pointer to just after the end of the program code.
;
; Note: because the program's length will change during use, the
; address of the last byte is accessed as a memory operand (COM_PTR).
;----------------------------------------------------------------------
                MOV     DX,OFFSET ERR_MEMSIZ$   ;Assume failure

                MOV     AX,[COM_PTR]            ;Length of code
                ADD     AX,128*2                ;+ stack space
                CMP     AX,SP                   ;Past end?
                JA      M_3
M_1:
                MOV     [STACK_TOP],AX          ;Save new stack top
                MOV     SP,AX                   ; and load it
;----------------------------------------------------------------------
; Release the copy of the environment allocated to this program. The
; segment address of the env block is located at offset 2Ch in the PSP.
;----------------------------------------------------------------------
                MOV     ES,DS:[2CH]             ;Get seg of environment
        ASSUME  ES:NOTHING
                MOV     AH,49H                  ;Free allocated memory
                INT     21H                     ; thru DOS
;----------------------------------------------------------------------
; Search for a copy of the program already resident in memory.
;----------------------------------------------------------------------
                CALL    FIND_RES
        ASSUME  ES:NOTHING
;----------------------------------------------------------------------
; Process the command line switches and return them bit-packed in AH.
; We can then process them by priority instead of position.
;----------------------------------------------------------------------
                MOV     DX,OFFSET USAGE$        ;Show correct syntax

                CALL    CMD_LINE                ;Get switches in AH
                JC      M_3
;----------------------------------------------------------------------
; If the /U switch was specified, attempt to unload a resident copy.
; If no copy is resident, report the fact. Ignore all other switches.
;----------------------------------------------------------------------
                TEST    AH,U_SW                 ;NZ = unload
                JZ      M_4

                CALL    UNLOAD                  ;Unload if possible
        ASSUME  ES:NOTHING
M_3:
                MOV     AH,9                    ;Display string
                INT     21H                     ; thru DOS
;----------------------------------------------------------------------
; Display the program title and exit.
;----------------------------------------------------------------------
M_EXIT:
                MOV     AH,9                    ;Display string
                MOV     DX,OFFSET COPYRIGHT$    ;Say who we are
                INT     21H                     ; thru DOS

                MOV     AH,4CH                  ;Terminate with error
                INT     21H                     ; thru DOS
;----------------------------------------------------------------------
; If the /E switch was specified, invoke the editor to edit the macro
; strings in the current copy. Jump there -- it never returns.
;----------------------------------------------------------------------
M_4:
                TEST    AH,E_SW                 ;NZ if Setup switch on
                JZ      M_5A

                JMP     SETUP                   ;Yes, invoke the editor
;----------------------------------------------------------------------
; No switches were specified. If not already resident, install with
; the macros in this copy. If resident, replace the resident macros.
; Jump to both these routines -- they don't return.
;----------------------------------------------------------------------
M_5A:
                CMP     WORD PTR [RES_SEG],-1   ;-1 = not resident
                JE      M_5B

                JMP     REPLACE                 ;Replace macros
M_5B:
                JMP     LOAD                    ;Load into memory

MAIN            ENDP

;======================================================================
; FIND_RES (Near)
;
; Determine if a copy of BLITZKEY is already resident by searching for
; a duplicate of the copyright notice in memory.
;----------------------------------------------------------------------
; Entry: None
; Exit :
;       DW [RES_SEG] = -1 if no resident copy
;                      code segment of resident copy, otherwise
;----------------------------------------------------------------------
; Changes: AX BX CX SI DI ES
;----------------------------------------------------------------------
FIND_RES        PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG
;----------------------------------------------------------------------
; Modify marker string to avoid false matches when searching memory.
; Initialize RES_SEG and UMB_LINK. They may have been altered if this
; is a cloned copy made when BLITZKEY was resident.
;----------------------------------------------------------------------
                NOT     WORD PTR [RES_MARKER]   ;Modify copyright
                MOV     WORD PTR [RES_SEG],-1   ;Say none resident
                MOV     BYTE PTR [UMB_LINK],-1  ;Say no UMBs
;----------------------------------------------------------------------
; If DOS 5 or later, save the current UMB state, then link them.
;----------------------------------------------------------------------
                MOV     AH,30H                  ;Get DOS version in AX
                INT     21H                     ; thru DOS

                CMP     AL,5                    ;Dos 5 or later?
                JB      FR_1

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

                MOV     [UMB_LINK],AL           ;Save it

                MOV     AX,5803H                ;Set UMB to
                MOV     BX,1                    ; linked in chain
                INT     21H                     ; thru DOS
FR_1:
;----------------------------------------------------------------------
; Get the segment address of the first MCB using DOS IVARS function.
;----------------------------------------------------------------------
                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.
; Because ES points to the MCB header and not the block itself, the
; offset is increased (DI=SI+10) to compensate.
;----------------------------------------------------------------------
                MOV     AX,DS                   ;Current seg in AX
FR_2A:
                MOV     ES,BX                   ;Point ES to MCB
        ASSUME  ES:NOTHING
                INC     BX                      ;Point BX 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     FR_2B
;----------------------------------------------------------------------
; A match was found. If it's this copy, ignore it and continue the
; search. Otherwise, save it and we're done.
;----------------------------------------------------------------------
                CMP     AX,BX                   ;Current copy?
                JE      FR_2B

                MOV     [RES_SEG],BX            ;Save resident segment
                JMP     SHORT FR_3A
;----------------------------------------------------------------------
; Not a match. Move to the next memory block. If no more, we're done.
;----------------------------------------------------------------------
FR_2B:
                ADD     BX,ES:[3]               ;Add block length

                CMP     BYTE PTR ES:[0],"Z"     ;This block the last?
                JNE     FR_2A
;----------------------------------------------------------------------
; Restore the UMB link to its previous state.
;----------------------------------------------------------------------
FR_3A:
                MOV     BL,[UMB_LINK]           ;Original link state
                CMP     BL,-1                   ;Was it recorded?
                JE      FR_3B

                SUB     BH,BH                   ;Link in BX
                MOV     AX,5803H                ;Set UMB link
                INT     21H                     ; thru DOS
FR_3B:
;----------------------------------------------------------------------
; Unmodify the copyright so we don't leave false matches in memory.
;----------------------------------------------------------------------
                NOT     WORD PTR [RES_MARKER]   ;Modify copyright

                RET

FIND_RES        ENDP

;======================================================================
; CMD_LINE (Near)
;
; Reads the command line and returns switches bit-packed in AH.
;----------------------------------------------------------------------
; Entry: None
; Exit :
;       CF = NC - successful
;            AH = bit flags
;              /U = 1
;              /E = 2
;
;       CF = CY - command tail contained improper syntax
;----------------------------------------------------------------------
; Changes: AX CX SI
;----------------------------------------------------------------------
CMD_LINE        PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG

                MOV     SI,80H                  ;Point to cmd tail len
                LODSB                           ;Get length
                CBW                             ;Convert to word (AH=0)

                OR      AL,AL                   ;Non-zero if switches
                JNZ     CMD_2A
CMD_1:
                CLC                             ;Clear carry = no error
CMD_EXIT:
                RET                             ;Return
;----------------------------------------------------------------------
; Something is on the line. Let's find out what.
;----------------------------------------------------------------------
CMD_2A:
                MOV     CX,AX                   ;Put count in CX
CMD_2B:
                JCXZ    CMD_1
CMD_2C:
                LODSB                           ;Get character
                DEC     CX                      ;Reduce count

                CMP     AL,BLANK                ;Skip blanks
                JE      CMD_2B

                CMP     AL,SLASH                ;Is the char a slash?
                JE      CMD_2D                  ;Yes, process switch
;----------------------------------------------------------------------
; A slash must be the first non-blank character encountered. Otherwise,
; exit with the carry flag set.
;----------------------------------------------------------------------
CMD_ERR:
                STC                             ;Carry on
                JMP     CMD_EXIT
;----------------------------------------------------------------------
; The switch character must immediately follow the slash. If there are
; no more characters, exit with an error.
;----------------------------------------------------------------------
CMD_2D:
                JCXZ    CMD_ERR
;----------------------------------------------------------------------
; Test for the legitimate options.
;----------------------------------------------------------------------
                LODSB                           ;Get next char
                DEC     CX                      ;Decrease count
                AND     AL,NOT 20H              ;Make switch upper case
;----------------------------------------------------------------------
; U means uninstall the resident copy.
;----------------------------------------------------------------------
                CMP     AL,"U"                  ;Uninstall switch
                JNE     CMD_3A

                OR      AH,U_SW                 ;Set bit flag
                JMP     CMD_2B
;----------------------------------------------------------------------
; E means bring up the editor.
;----------------------------------------------------------------------
CMD_3A:
                CMP     AL,"E"                  ;Edit switch
                JNE     CMD_ERR

                OR      AH,E_SW                 ;Set bit flag
                JMP     CMD_2B

CMD_LINE        ENDP

;======================================================================
; UNLOAD (Near)
;
; Attempt to remove the copy of BLITZKEY already in memory.
;----------------------------------------------------------------------
; Entry:
;       None
; Exit :
;       CF = CY - error occurred during memory release
;            NC - removed okay or not resident
;       DX = offset of message reporting result
;----------------------------------------------------------------------
; Changes: AX BX CX DX ES
;----------------------------------------------------------------------
ERR_RES$        DB      "There's No Resident Copy To Uninstall$"
ERR_VECT$       DB      "Vectors Have Been Changed. Can't Uninstall$"
UNLOAD_OK$      DB      "Uninstall Successful$"
ERR_MEM$        DB      "Error Releasing Memory -- Suggest Reboot$"

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

;----------------------------------------------------------------------
; If there is no resident copy, return with an error message.
;----------------------------------------------------------------------
                MOV     DX,OFFSET ERR_RES$      ;Assume not resident

                MOV     CX,[RES_SEG]            ;Get seg of res copy
                CMP     CX,-1                   ;-1 = not resident
                JE      U_3B

                MOV     BYTE PTR [DISABLED],-1  ;Disable expander
;----------------------------------------------------------------------
; Determine if the hooked interrupts have been changed since the
; resident copy was installed.
;----------------------------------------------------------------------
                MOV     AX,3516H                ;Get Int 16h vector
                INT     21H                     ; thru DOS
        ASSUME  ES:NOTHING

                MOV     DX,OFFSET ERR_VECT$     ;Default error message

                MOV     AX,ES                   ;Get interrupt segment
                CMP     AX,CX                   ;Same as res seg?
                JNE     U_2B

                MOV     AX,3509H                ;Get Int 9 vector
                INT     21H                     ; thru DOS
        ASSUME  ES:NOTHING

                MOV     AX,ES                   ;Get interrupt segment
                CMP     AX,CX                   ;Same as res seg?
                JNE     U_2B
;----------------------------------------------------------------------
; If we get here, the interrupts were unchanged and ES points to the
; resident segment.
;----------------------------------------------------------------------
                PUSH    DS                      ;Save used register

                MOV     AX,2509H                ;Set vector
                LDS     DX,DWORD PTR ES:[OLD9]  ;DS:DX = old vector
        ASSUME  DS:NOTHING
                INT     21H                     ; thru DOS

                MOV     AX,2516H                ;Set vector
                LDS     DX,DWORD PTR ES:[OLD16] ;DS:DX = old vector
        ASSUME  DS:NOTHING
                INT     21H                     ; thru DOS

                POP     DS                      ;Restore register
        ASSUME  DS:CSEG
;----------------------------------------------------------------------
; Release the memory block that we allocated to hold the strings.
; If an error occurs, disable the TSR and quit.
;----------------------------------------------------------------------
                MOV     DX,ES:[STR_LOC][2]      ;Get/save str seg

                MOV     AH,49H                  ;Release segment in ES
                MOV     ES,DX                   ;ES = string seg
        ASSUME  ES:NOTHING
                INT     21H                     ; thru DOS
                JC      U_2B
;----------------------------------------------------------------------
; If the resident code segment is different than the resident string
; segment, release the code block.
;----------------------------------------------------------------------
                CMP     CX,DX                   ;Cmp res seg, str seg
                JE      U_3A

                MOV     AH,49H                  ;Free memory block
                MOV     ES,CX                   ;Resident code
        ASSUME  ES:NOTHING
                INT     21H                     ; thru DOS
                JNC     U_3A
;----------------------------------------------------------------------
; Terminate with extreme prejudice -- uninstall failed.
;----------------------------------------------------------------------
U_2A:
                MOV     DX,OFFSET ERR_MEM$      ;Report memory error
U_2B:
                STC                             ;Signal failure
                JMP     SHORT U_EXIT
;----------------------------------------------------------------------
; Restore registers and exit.
;----------------------------------------------------------------------
U_3A:
                MOV     DX,OFFSET UNLOAD_OK$    ;All is okay
U_3B:
                CLC                             ;Signal success
U_EXIT:
                RET

UNLOAD          ENDP

;======================================================================
; SETUP (Near)
;
; This procedure is called to edit the macro strings in this copy. Note
; that the length of the program plus the macros cannot exceed 64k.
;----------------------------------------------------------------------
; Entry: None
; Exit : Doesn't Return
;----------------------------------------------------------------------
; Changes: n/a
;----------------------------------------------------------------------
ERR_VID$        DB      "Incompatible Video Mode$"
FERROR$         DB      CR,LF,"File error. Try Again? (Y/N)$"
OVERWRITE$      DB      CR,LF,"Overwrite existing file? (Y/N)$"
SAVE$           DB      CR,LF,"Save changes (in BLITZNEW.COM)? (Y/N)$"
WERROR$         DB      CR,LF,"Write error. Try Again? (Y/N)$"

FILENAME        DB      "BLITZNEW.COM",0

ROW_END         DB      24                      ;Defaults for
COL_END         DB      80                      ; common video
COL_MAX         DB      0                       ;Rightmost column
VPAGE           DB      0                       ;Active page

ATTR            DB      0                       ;Video attribute
 CO_ATTR        EQU     1FH                     ;Brite white/blue
 BW_ATTR        EQU     07H                     ;Reverse video

;----------------------------------------------------------------------
SETUP           PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG
;----------------------------------------------------------------------
; Expand this copy's segment to a full 64k.
;----------------------------------------------------------------------
                PUSH    CS                      ;Point ES to this seg
                POP     ES
        ASSUME  ES:CSEG

                MOV     AH,4AH                  ;Modify memory block
                MOV     BX,1000H                ;Ask for 64K
                INT     21H                     ; thru DOS
                JNC     S_2
;----------------------------------------------------------------------
; Exit this routine with an error.
;----------------------------------------------------------------------
                MOV     DX,OFFSET ERR_MEMSIZ$   ;Need more room
S_1A:
                MOV     AH,9                    ;Display string
                INT     21H                     ; thru DOS
S_1B:
                MOV     AH,9                    ;Display string
                MOV     DX,OFFSET COPYRIGHT$    ;Say who we are
                INT     21H                     ; thru DOS

                MOV     AH,4CH                  ;Terminate program
                INT     21H                     ; thru DOS
;----------------------------------------------------------------------
; Relocate the stack pointer to the end of the resized segment.
;----------------------------------------------------------------------
S_2:
                MOV     SP,0FFFEH               ;Move stack to end of seg
;----------------------------------------------------------------------
; BLITZKEY requires that the video display be in a text mode to edit.
;----------------------------------------------------------------------
                CALL    VIDEO_SETUP             ;Examine video hardware
                MOV     DX,OFFSET ERR_VID$      ;Assume an error
                JC      S_1A
;----------------------------------------------------------------------
; Draw the edit window.
;----------------------------------------------------------------------
                MOV     AL,[COL_END]            ;Right edge of screen
                SUB     AL,2                    ;(1 based) in one char
                MOV     [COL_MAX],AL            ;Is rightmost column

                CALL    CLR_BOX                 ;Draw the window
;----------------------------------------------------------------------
; Invoke the string editor. Returns when F7 is pressed.
;----------------------------------------------------------------------
                CALL    EDIT                    ;String editor

                MOV     CX,[COM_PTR]            ;Get program length
                SUB     CX,OFFSET STRINGS       ; minus start of strings
                MOV     [STR_LEN],CX            ; is string length
;----------------------------------------------------------------------
; Ask if changes should be written out to BLITZNEW.COM. If not, end.
;----------------------------------------------------------------------
S_3:
                MOV     DX,OFFSET SAVE$         ;Clone the changes?
                CALL    GETRESPONSE             ;Yes or No
                JNC     S_1B
;----------------------------------------------------------------------
; Try to open the file to see if it exists.
;----------------------------------------------------------------------
                MOV     AX,3D02H                ;Open file for r/w
                MOV     DX,OFFSET FILENAME      ; This name
                INT     21H                     ; thru DOS
                JC      S_5                     ;Jump if not found

                MOV     BX,AX                   ;Move file's handle

                MOV     DX,OFFSET OVERWRITE$    ;Should we overwrite?
                CALL    GETRESPONSE
                JC      S_6                     ;Yes
;----------------------------------------------------------------------
; Close the file handle in BX, and ask the question again. This gives
; the user the chance to save the file using some other utility.
;----------------------------------------------------------------------
S_4:
                MOV     AH,3EH                  ;Close file handle
                INT     21H                     ; thru DOS
                JMP     S_3                     ;Ask again
;----------------------------------------------------------------------
; File does not exist. Attempt to open as new.
;----------------------------------------------------------------------
S_5:
                MOV     AH,3CH                  ;Create file fn
                SUB     CX,CX                   ;For writing
                MOV     DX,OFFSET FILENAME      ;This is name
                INT     21H                     ; thru DOS
                MOV     BX,AX                   ;Save handle in BX
                JNC     S_6
;----------------------------------------------------------------------
; If a file error occurs, give the user a change to correct it.
;----------------------------------------------------------------------
                MOV     DX,OFFSET FERROR$       ;Error opening file
                CALL    GETRESPONSE             ;Try again?
                JC      S_5
                JMP     S_3
;----------------------------------------------------------------------
; A valid file handle is in BX. Write away.
;----------------------------------------------------------------------
S_6:
                MOV     AH,40H                  ;Write to file fn
                MOV     CX,[COM_PTR]            ;Length of file
                MOV     DX,100H                 ;Start here
                SUB     CX,DX                   ;Subtract PSP length
                INT     21H                     ; thru DOS
                JC      S_7

                CMP     AX,CX                   ;EQ = All bytes written
                JE      S_8
;----------------------------------------------------------------------
; An error was encountered on the write.
;----------------------------------------------------------------------
S_7:
                MOV     DX,OFFSET WERROR$
                CALL    GETRESPONSE             ;Try again?
                JC      S_6
                JMP     S_4
;----------------------------------------------------------------------
; File was written okay. Close and exit.
;----------------------------------------------------------------------
S_8:
                MOV     AH,3EH                  ;Close handle in BX
                INT     21H                     ; thru DOS
                JMP     S_1B

SETUP           ENDP

;======================================================================
; VIDEO_SETUP (Near)
;
; Determine all the paramters and info we need to handle the display.
;----------------------------------------------------------------------
; Entry: None
; Exit :
;       CF = NC - video mode is okay
;            CY - incompatible mode
;----------------------------------------------------------------------
; Changes: AX BX DX
;----------------------------------------------------------------------
VIDEO_SETUP     PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG

                MOV     AH,0FH                  ;Get video mode
                INT     10H                     ; Thru BIOS

                MOV     [ATTR],CO_ATTR          ;Assume color

                CMP     AL,3                    ;CGA video modes okay
                JBE     VID_1

                MOV     [ATTR],BW_ATTR          ;Force B/W

                CMP     AL,7                    ;MDA text mode okay
                JE      VID_1

                STC                             ;Else, an error
                JMP     SHORT VID_EXIT
;----------------------------------------------------------------------
; Save some video parameters.
;----------------------------------------------------------------------
VID_1:
                MOV     [COL_END],AH            ;Save cols
                MOV     [VPAGE],BH              ;Save current page
;----------------------------------------------------------------------
; Determine if an EGA/VGA adapter is installed.
;----------------------------------------------------------------------
                MOV     AH,12H                  ;EGA alternate select
                MOV     BL,10H                  ;Return EGA info
                INT     10H                     ; thru BIOS

                CMP     BL,10H                  ;If BL unchanged
                MOV     DL,24                   ;Set default rows
                JE      VID_2                   ; there's no EGA/VGA
;----------------------------------------------------------------------
; Find the row count.
;----------------------------------------------------------------------
                PUSH    ES                      ;Changed by call
                MOV     AX,1130H                ;EGA info call
                SUB     BH,BH                   ;Dummy argument
                INT     10H                     ; thru BIOS
        ASSUME  ES:NOTHING
                POP     ES
        ASSUME  ES:NOTHING
;----------------------------------------------------------------------
;
;----------------------------------------------------------------------
VID_2:
                MOV     [ROW_END],DL            ;Save rows
                CLC
VID_EXIT:
                RET

VIDEO_SETUP     ENDP

;======================================================================
; CLR_BOX (Near)
;
; Clear the screen, the draw a window on the screen.
;----------------------------------------------------------------------
; Entry: None
; Exit : None
;----------------------------------------------------------------------
; Changes: AX BX CX DX SI
;----------------------------------------------------------------------
INSET$          DB      0B5H,"BlitzKey 1.00",0C6H,0
INSET_LEN       EQU     $-INSET$

HELP$           DB      "STRING: ",27,32,26," INS DEL ",24,32,25
                DB      "  MACRO: PgUp PgDn  F7 = Save",0
HELP_LEN        EQU     $-HELP$

ALT$            DB      "HOTKEY = ALT+",0
TITLE$          DB      "TITLE:",0
MACRO$          DB      "MACRO:",0

BOX_CHARS       DB      201,205,187
                DB      186, 32,186
                DB      199,196,182
                DB      200,205,188

NROW            EQU     9

;----------------------------------------------------------------------
CLR_BOX         PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG
;----------------------------------------------------------------------
; Clear the entire screen and set the colors.
;----------------------------------------------------------------------
                MOV     AX,0700H                ;Scroll window fn
                MOV     BH,[ATTR]               ;Clear to this color
                SUB     CX,CX                   ;Starting row & col
                MOV     DH,[ROW_END]            ;Ending row
                MOV     DL,[COL_END]            ;Ending column
                DEC     DL                      ; (0-based)
                INT     10H                     ; thru BIOS

                MOV     BH,[VPAGE]              ;Get active page
                MOV     SI,OFFSET BOX_CHARS     ;Draw the edit window
                MOV     DX,CX                   ;Cursor from last call
                MOV     CX,NROW                 ;Number rows to draw
;----------------------------------------------------------------------
; Construct the window on the screen.
;----------------------------------------------------------------------
CB_1A:
                PUSH    CX                      ;Save row counter

                MOV     AH,2                    ;Position cursor
                SUB     DL,DL                   ;To column 0
                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     CL,[COL_END]            ;Width of box
                SUB     CH,CH
                SUB     CX,2                    ; minus 2 sides
                INT     10H                     ; thru BIOS

                MOV     AH,2                    ;Position cursor
                SUB     DL,DL
                ADD     DL,[COL_END]
                DEC     DL                      ;Col = righthand edge
                INT     10H                     ; thru BIOS

                LODSB                           ;Get rightmost char
                MOV     AH,0AH                  ;Write repeated char
                MOV     CX,1                    ; 1 copy
                INT     10H                     ; thru BIOS

                INC     DH                      ;Next row
                POP     CX                      ;Restore counter

                CMP     CL,NROW                 ;Examine row we wrote
                JE      CB_1C                   ;If first row

                CMP     CL,2                    ;or next to last
                JNE     CB_1B                   ;Don't adjust count
                ADD     SI,3
CB_1B:
                TEST    CL,1                    ;If row is even
                JZ      CB_1C                   ;Don't adjust count
                SUB     SI,6
CB_1C:
                LOOP    CB_1A
;----------------------------------------------------------------------
; Fill in the title, prompt, and help lines.
;----------------------------------------------------------------------
                SUB     AH,AH                   ;Top row
                MOV     AL,[COL_MAX]            ;Rightmost column
                SUB     AL,(INSET_LEN+5)        ;Backup
                MOV     [CUR_POS],AX            ; to here
                MOV     SI,OFFSET INSET$        ;Program name
                CALL    CB_2A                   ;Write to screen

                MOV     AH,7
                MOV     AL,(79-HELP_LEN)/2
                MOV     [CUR_POS],AX
                MOV     SI,OFFSET HELP$         ;Instructions
                CALL    CB_2A                   ;Write to screen

                MOV     WORD PTR [CUR_POS],0101H
                MOV     SI,OFFSET ALT$          ;Macro key combo
                CALL    CB_2A

                MOV     WORD PTR [CUR_POS],0301H
                MOV     SI,OFFSET TITLE$        ;Macro title
                CALL    CB_2A                   ;Write to screen

                MOV     WORD PTR [CUR_POS],0501H
                MOV     SI,OFFSET MACRO$        ;Macro text
CB_2A:
                CALL    CUR_SET                 ;Position cursor
CB_2B:
                MOV     BH,[VPAGE]              ;Use active page

                LODSB                           ;Get a char
                OR      AL,AL                   ;If zero
                JZ      CB_2C                   ; quit

                MOV     AH,0EH                  ;Else, write TTY
                INT     10H                     ; Thru BIOS
                JMP     CB_2B                   ;Continue
CB_2C:
                RET

CLR_BOX         ENDP

;======================================================================
; CUR_SET (Near)
;
; Position the cursor to the stored values.
;----------------------------------------------------------------------
; Entry: None
; Exit : None
;----------------------------------------------------------------------
; Changes: BX DX
;----------------------------------------------------------------------
CUR_SET         PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG

                PUSH    AX                      ;Save used register

                MOV     AH,2                    ;Position cursor fn
                MOV     BH,[VPAGE]              ; current page
                MOV     DX,[CUR_POS]            ; new cursor position
                INT     10H                     ; Thru BIOS

                POP     AX                      ;Restore register
                RET

CUR_SET         ENDP

;======================================================================
; EDIT (Near)
;
; The EDIT procedure handles all the editing. It keeps track of the
; current macro strings and displays them on the screen as they change.
;----------------------------------------------------------------------
; Entry: None
; Exit : None
;----------------------------------------------------------------------
; Changes: AX BX CX
;----------------------------------------------------------------------
; The PTR offset points to the character that appears at the left side
; of the window
;----------------------------------------------------------------------
PTR_ARRAY       LABEL   WORD                    ;Indicates the starting
 NAM_PTR        DW      0                       ; offset of the current
 STR_PTR        DW      0                       ; macro name and string

ACTIVE          DW      0                       ;Index for array

CUR_POS         LABEL   WORD
 CUR_COL        DB      0                       ;Current cursor
 CUR_ROW        DB      0                       ; position

MACRO_PTR       DB      0                       ;Pointer to string set
INS_STATE       DB      0                       ;0=INS FF=TYPEOVER

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

                SUB     BP,BP                   ;Create zero word
                MOV     BYTE PTR [MACRO_PTR],0  ;Choose first string
;----------------------------------------------------------------------
; Display the hotkey for this macro.
;----------------------------------------------------------------------
E_1:
                MOV     WORD PTR [CUR_POS],010EH ;Move past prompt
                CALL    CUR_SET

                MOV     AH,0AH                  ;Write character fn
                MOV     AL,[MACRO_PTR]          ;Number of macro
                ADD     AL,"A"                  ;Convert to letter
                MOV     CX,1                    ;Write 1 copy
                INT     10H                     ; thru BIOS
;----------------------------------------------------------------------
; Initialize the pointers to point to the first macro set.
;----------------------------------------------------------------------
                MOV     BL,[MACRO_PTR]          ;String # to look for
                ADD     BL,BL                   ;They come in pairs

                MOV     SI,OFFSET STRINGS       ;DS:SI -> strings
                CALL    GET_POINTER             ;Load pointer
                MOV     [NAM_PTR],SI            ;Save offset

                INC     BL                      ;Next string
                MOV     SI,OFFSET STRINGS       ;DS:SI -> strings
                CALL    GET_POINTER             ;Load pointer
                MOV     [STR_PTR],SI            ;Save offset
;----------------------------------------------------------------------
; Display the selected strings on the screen as read from memory.
;----------------------------------------------------------------------
                MOV     [ACTIVE],BP             ;Change active index
                MOV     BYTE PTR [CUR_COL],7    ;Leftmost column
                MOV     BYTE PTR [CUR_ROW],3    ;Save new row
        CALL    DISPLAYX        ;Show the string
                MOV     BX,2                    ;Index for Macro
E_2A:
                MOV     [ACTIVE],BX             ;Change active index
                MOV     BYTE PTR [CUR_COL],7    ;Leftmost column
                MOV     DH,3                    ;Row for name
                OR      BX,BX                   ;If BX=0, we're done
                JZ      E_2B

                ADD     DH,BL                   ;Else row for string
E_2B:
                MOV     [CUR_ROW],DH            ;Save new row
        CALL    DISPLAYX        ;Show the string
;----------------------------------------------------------------------
; Get a key from the keyboard and act on it.
;----------------------------------------------------------------------
E_3:
                SUB     AH,AH                   ;Fetch the key
                INT     16H                     ; Thru BIOS

                OR      AL,AL                   ;0=extended=command
                JZ      E_7

                CMP     AX,BS                   ;Actual BS key?
                JNE     E_5A

                OR      AH,AH                   ;If zero = char
                JZ      E_5A
;----------------------------------------------------------------------
; The backspace key is the only key that requires special handling.
; Treat BS as a CURSOR-LEFT/DELETE combination.
;----------------------------------------------------------------------
                CALL    CURSOR_LEFT             ;Move cursor left
                JC      E_3
E_4:
                CALL    STRING_DEL              ;Delete char at cursor
                JC      E_3

        CALL    DISPLAYX        ;Display the results
;----------------------------------------------------------------------
; If the char was deleted from the TITLE string, the macro string moved
; also. Adjust the pointer.
;----------------------------------------------------------------------
                CMP     [ACTIVE],BP             ;If TITLE is active
                JNE     E_3

                SUB     WORD PTR [PTR_ARRAY][2],2 ;Back up MACRO ptr
                JMP     E_3
;----------------------------------------------------------------------
; Normal chars are placed on the screen and in the string.
;----------------------------------------------------------------------
E_5A:
                CMP     BYTE PTR [INS_STATE],0  ;If insert state
                JE      E_5B
;----------------------------------------------------------------------
; If at end of string, typeover works just like insert.
; Fall through to the cursor-right routine.
;----------------------------------------------------------------------
                CALL    LOCATE_SI               ;If current char not 0
                CMP     [SI],BP                 ; just overwrite
                JNZ     E_5C
E_5B:
                CALL    STRING_INS              ;Create hole at cursor

                CMP     [ACTIVE],BP             ;If inserting TITLE...
                JNE     E_5C

                ADD     WORD PTR [PTR_ARRAY][2],2 ;...advance MACRO
E_5C:
                CALL    LOCATE_SI               ;Point to cursor location
                MOV     [SI],AX                 ; and pop in char

        CALL    DISPLAYX        ;Show changes
;----------------------------------------------------------------------
; ->  Move the cursor to the right one space.
;----------------------------------------------------------------------
E_6:
                CALL    CURSOR_RIGHT            ;Move cursor along
                JMP     E_3
;----------------------------------------------------------------------
; Key is an extended key.  Must be a command.
;----------------------------------------------------------------------
E_7:
                CMP     AH,F7KEY                ;F7 is the exit key
                JNE     E_8

                MOV     BYTE PTR [CUR_COL],0    ;Reposition cursor
                MOV     BYTE PTR [CUR_ROW],NROW ; for message
                CALL    CUR_SET
;----------------------------------------------------------------------
; Exit the edit procedure.
;----------------------------------------------------------------------
                RET                             ;The only way out
;----------------------------------------------------------------------
; All remaining key dispatch is performed from here.
;----------------------------------------------------------------------
E_8:
                MOV     BL,[MACRO_PTR]          ;Number of macro set
;----------------------------------------------------------------------
; Delete kills the char at the cursor.
;----------------------------------------------------------------------
                CMP     AH,DEL                  ;Kill char at cursor
                JE      E_4
;----------------------------------------------------------------------
; PgUp and PgDn move between macro sets.
;----------------------------------------------------------------------
                CMP     AH,PGUP                 ;Check for PgUp
                JE      E_9A                    ; else check next

                CMP     AH,PGDN                 ;Move to next macro
                JE      E_9C
;----------------------------------------------------------------------
; The function of the arrow keys depend on the active string.
;----------------------------------------------------------------------
                MOV     BX,[ACTIVE]             ;Get active index

                CMP     AH,RARROW               ;Move right 1 char
                JE      E_6

                CMP     AH,LARROW               ;Move left 1 char
                JE      E_10A

                CMP     AH,UARROW               ;Move to NAME field
                JE      E_11

                CMP     AH,DARROW               ;Move to MACRO field
                JE      E_12
;----------------------------------------------------------------------
; INS toggles the insert state.
;----------------------------------------------------------------------
        CMP AH,INSK         ;Use Insert mode
                JE      E_13
;----------------------------------------------------------------------
; HOME and END perform rapid cursor movement.
;----------------------------------------------------------------------
                CMP     AH,ENDKEY               ;Move to end of string
                JE      E_14

                CMP     AH,HOME                 ;Move to start of string
                JE      E_15

                JMP     E_3                     ;Didn't recongnize it
;----------------------------------------------------------------------
; PgUp key:  Move to the previous macro.
;----------------------------------------------------------------------
E_9A:
                DEC     BL                      ;Back up active pointer
                JNS     E_9B

                MOV     BL,25                   ;If past end, reset
E_9B:
                MOV     [MACRO_PTR],BL          ;Update pointer
                JMP     E_1                     ;Start over
;----------------------------------------------------------------------
; PgDn key:  Move to the next macro.
;----------------------------------------------------------------------
E_9C:
                INC     BL                      ;Go forward
                CMP     BL,25                   ;If past end
                JBE     E_9B

                SUB     BL,BL                   ;Reset
                JMP     E_9B
;----------------------------------------------------------------------
; <-  Move the cursor to the left one space. If this fails, we just
; ignore it.
;----------------------------------------------------------------------
E_10A:
                CALL    CURSOR_LEFT             ;Move cursor left
E_10B:
                JMP     E_3
;----------------------------------------------------------------------
; ^  Move to the NAME field.
;----------------------------------------------------------------------
E_11:
                OR      BX,BX                   ;Skip if already there
                JZ      E_10B

                SUB     BX,BX                   ;Else, switch
                JMP     E_2A
;----------------------------------------------------------------------
; v  Move to the STRING field.
;----------------------------------------------------------------------
E_12:
                OR      BX,BX                   ;Skip if already there
                JNZ     E_10B

                INC     BX                      ;Move index to
                INC     BX                      ; second pointer
                JMP     E_2A
;----------------------------------------------------------------------
; Toggle the insert/typeover state.
;----------------------------------------------------------------------
E_13:
                NOT     BYTE PTR [INS_STATE]    ;Toggle the flag
                JMP     E_3
;----------------------------------------------------------------------
; Move to end of string by repeatedly calling cursor right.
;----------------------------------------------------------------------
E_14:
                CALL    CURSOR_RIGHT            ;Move to the right
                JNC     E_14                    ; as long as successful
                JMP     E_3
;----------------------------------------------------------------------
; Move to start of string by repeatedly calling cursor left.
;----------------------------------------------------------------------
E_15:
                CALL    CURSOR_LEFT             ;Move to the left
                JNC     E_15                    ; as long as successful
                JMP     E_3

EDIT            ENDP

;======================================================================
; DISPLAYX (Near)
;
; Write the active string to the screen from the current cursor
; position forward. Called only when a char is typed or the window is
; pushed.
;----------------------------------------------------------------------
; Entry: None
; Exit : None
;----------------------------------------------------------------------
; Changes: AX BX CX SI
;----------------------------------------------------------------------
DISPLAYX    PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG

                CALL    CUR_SET                 ;Position the cursor
                CALL    LOCATE_SI               ;Point SI to string
                MOV     CH,[CUR_COL]            ;From current cursor
                MOV     CL,[COL_MAX]            ; to rightmost column
;----------------------------------------------------------------------
; Display each character until the end of the visible window.
;----------------------------------------------------------------------
D_0:
                LODSW                           ;Get character
                OR      AX,AX                   ;0=end of string
                JNZ     D_1
;----------------------------------------------------------------------
; If we've reached the end of the string, just display blanks until the
; window is filled.
;----------------------------------------------------------------------
                DEC     SI                      ;Back up to the zero
                DEC     SI
                MOV     AL,BLANK                ;Display a blank
;----------------------------------------------------------------------
; Display the char in AL. Use Fn 0Ah to print control characters.
;----------------------------------------------------------------------
D_1:
                CALL    CUR_SET                 ;Position the cursor
                PUSH    CX                      ;Save register

                MOV     AH,0AH                  ;Write Repeated Char
                MOV     BH,[VPAGE]              ;Active page
                MOV     CX,1                    ;# copies to write
                INT     10H                     ; thru BIOS

                POP     CX                      ;Restore register

                INC     BYTE PTR [CUR_COL]      ;Change position

                CMP     CL,[CUR_COL]            ;Is col <= end?
                JAE     D_0
;----------------------------------------------------------------------
; Past the end of the window - done with display.
;----------------------------------------------------------------------
                MOV     [CUR_COL],CH            ;Return to old spot
                CALL    CUR_SET                 ; do it

                RET

DISPLAYX    ENDP

;======================================================================
; LOCATE_SI (Near)
;
; Point SI to the same char in the string that is currently above the
; cursor on the screen.
;----------------------------------------------------------------------
; Entry: None
; Exit:
;       SI = offset of char currently above the cursor
;----------------------------------------------------------------------
; Changes: BX CX SI
;----------------------------------------------------------------------
LOCATE_SI       PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG
;----------------------------------------------------------------------
; Point SI to the offset of the first visible character.
;----------------------------------------------------------------------
                MOV     BX,[ACTIVE]             ;Get active index
                MOV     SI,[PTR_ARRAY][BX]      ;Get start of string
;----------------------------------------------------------------------
; Adjust SI forward based on the current cursor position.
;----------------------------------------------------------------------
                MOV     CL,[CUR_COL]            ;Current column
                SUB     CL,7                    ; - leftmost
                SUB     CH,CH                   ;Make into word
                ADD     CX,CX                   ;Double offset
                ADD     SI,CX                   ;Add to pointer

                RET

LOCATE_SI       ENDP

;======================================================================
; CURSOR_RIGHT (Near, nested)
;
; Move the cursor right 1 char.
;----------------------------------------------------------------------
; Entry:
;       BX = String index into pointer array
; Exit:
;       CF = NC - cursor was moved
;            CY - cursor could not be moved
;----------------------------------------------------------------------
; Changes: CX SI
;----------------------------------------------------------------------
CURSOR_RIGHT    PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG
;----------------------------------------------------------------------
; If already on last char, return failure.
;----------------------------------------------------------------------
                CALL    LOCATE_SI               ;Get pointers
                CMP     WORD PTR [SI],0         ;0=last char of string
                JNE     CR_1
CR_A:
                STC                             ;Signal failure
                JMP     SHORT CR_EXIT
;----------------------------------------------------------------------
; Common exit.
;----------------------------------------------------------------------
CR_0:
                CLC                             ;Signal success
CR_EXIT:
                RET
;----------------------------------------------------------------------
; Move the cursor within the visible window.
;----------------------------------------------------------------------
CR_1:
                MOV     CL,[CUR_COL]            ;Get current column
                CMP     CL,[COL_MAX]            ;At screen edge?
                JE      CR_3

                INC     CL                      ;Move to next col
CR_2:
                MOV     [CUR_COL],CL            ;Save column
                CALL    CUR_SET                 ;Move cursor
                JMP     CR_0
;----------------------------------------------------------------------
; This cursor movement is pushing the visible window.
;----------------------------------------------------------------------
CR_3:
                ADD     WORD PTR [PTR_ARRAY][BX],2 ;Move the start
                PUSH    WORD PTR [CUR_POS]      ;Save current cursor

                MOV     BYTE PTR [CUR_COL],7    ;Redisplay from left side
                CALL    CUR_SET                 ;Set cursor
        CALL    DISPLAYX        ;Draw string

                POP     WORD PTR [CUR_POS]      ;Reset old cursor
                CALL    CUR_SET
                JMP     CR_0

;======================================================================
; CURSOR_LEFT (Near, nested)
;
; Move the cursor left 1 char.
;----------------------------------------------------------------------
; Entry:
;       BX = String index into pointer array
; Exit:
;       CF = NC - cursor was moved
;            CY - cursor could not be moved
;----------------------------------------------------------------------
; Changes: CX SI
;----------------------------------------------------------------------
CURSOR_LEFT     PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG

;----------------------------------------------------------------------
; If not in first column, simply move cursor within window.
;----------------------------------------------------------------------
                MOV     CL,[CUR_COL]            ;Get cursor position
                CMP     CL,7                    ;At 1st column?
                JE      CL_1

                DEC     CL                      ;Back up cursor
                JMP     CR_2
;----------------------------------------------------------------------
; Push the window.
;----------------------------------------------------------------------
CL_1:
                MOV     SI,[PTR_ARRAY][BX]      ;Start of window

                DEC     SI                      ;Back one char
                DEC     SI
                CMP     WORD PTR [SI],0         ;Stop if past start
                JE      CR_A

                MOV     [PTR_ARRAY][BX],SI      ;Update the pointer
        CALL    DISPLAYX        ;Display the string
                JMP     CR_0

CURSOR_LEFT     ENDP
CURSOR_RIGHT    ENDP

;======================================================================
; STRING_DEL (Near)
;
; Delete the char at the cursor and close up the string.
;----------------------------------------------------------------------
; Entry: None
; Exit :
;       CF = NC - char deleted successfully
;            CY - char could not be deleted
;----------------------------------------------------------------------
; Changes: CX SI DI
;----------------------------------------------------------------------
STRING_DEL      PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG

                CALL    LOCATE_SI               ;Point to current char
                CMP     WORD PTR [SI],0         ;Can't backup too far
                JNZ     SD_1

                STC                             ;Return failure
SD_EXIT:
                RET
;----------------------------------------------------------------------
; Delete the char and adjust the COM file length.
;----------------------------------------------------------------------
SD_1:
                MOV     CX,[COM_PTR]            ;End of strings offset
                SUB     CX,SI                   ;# bytes to move is 2
                DEC     CX                      ; less than length
                DEC     CX

                MOV     DI,SI                   ;Dest is DI
                INC     SI                      ;Src is previous
                INC     SI                      ; word

                REP     MOVSB                   ;Move the strings

                SUB     WORD PTR [COM_PTR],2    ;File gets shorter
                CLC                             ;Say succcess
                JMP     SD_EXIT

STRING_DEL      ENDP

;======================================================================
; STRING_INS (Near)
;
; Create a hole in the string by moving everything to the right.
;----------------------------------------------------------------------
; Entry: None
; Exit : None
;----------------------------------------------------------------------
; Changes: CX SI DI
;----------------------------------------------------------------------
STRING_INS      PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG

                CALL    LOCATE_SI               ;SI = current word

                MOV     CX,[COM_PTR]            ;End of strings offset
                MOV     DI,CX                   ; also target for move
                SUB     CX,SI                   ;Bytes to move

                MOV     SI,DI                   ;Copy to src register
                DEC     SI                      ;Copy from prev word
                DEC     SI

                STD                             ;Move backwards
                REP     MOVSB                   ; whole string
                CLD                             ;Strings forward again

                ADD     WORD PTR [COM_PTR],2    ;File is longer

                RET

STRING_INS      ENDP

;======================================================================
; GETRESPONSE (Near)
;
; Accept only a Y or N answer from the console.
;----------------------------------------------------------------------
; Entry:
;       DX = offset of $-terminated prompt to display
; Exit:
;       CF = CY if answer was yes
;            NC if answer was no
;----------------------------------------------------------------------
; CHANGES: AX
;----------------------------------------------------------------------
GETRESPONSE     PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG

                MOV     AH,9                    ;Display string fn
                INT     21H                     ; thru DOS
GETR_0:
                SUB     AH,AH                   ;Wait for key
                INT     16H                     ; thru BIOS

                AND     AL,NOT 20H              ;Capitalize
                CMP     AL,"N"                  ;Was it N?
                JNE     GETR_1
;----------------------------------------------------------------------
; Note that if the comparison was equal, CF is off.
;----------------------------------------------------------------------
GETR_EXIT:
                RET                             ; just end
GETR_1:
                CMP     AL,"Y"                  ;If not YES,
                JNE     GETR_0                  ; try again

                STC                             ;Carry on
                JMP     GETR_EXIT

GETRESPONSE     ENDP

;======================================================================
; REPLACE (Near)
;
; The program has been loaded previously, and is already resident.
; Just replace the old strings with the new strings.
;----------------------------------------------------------------------
; Entry: None
; Exit : Doesn't Return
;----------------------------------------------------------------------
; Changes: n/a
;----------------------------------------------------------------------
BADREPLACE$     DB      CR,LF,"BlitzKey Failed. Suggest Reboot.",CR,LF,"$"
NEWSEGLEN       DW      0

REPLACE         PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG
;----------------------------------------------------------------------
; Get the PSP of the resident program. Save in BP for easy access.
;----------------------------------------------------------------------
                MOV     BX,[RES_SEG]            ;Get resident PSP
                MOV     BP,BX                   ;Save in BP
                MOV     ES,BX                   ; and load into ES
        ASSUME  ES:NOTHING
;----------------------------------------------------------------------
; Make the PSP of the resident segment (in BX) the active PSP for
; subsequent memory management calls.
;----------------------------------------------------------------------
                MOV     AH,50H                  ;Set active PSP
                INT     21H                     ; thru DOS
;----------------------------------------------------------------------
; Turn off any current macro expansion and prevent more from starting.
;----------------------------------------------------------------------
                MOV     BYTE PTR ES:[EXPANDING],0 ;Expander off
                MOV     BYTE PTR ES:[DISABLED],-1 ;Disable macros
;----------------------------------------------------------------------
; Compare the resident string segment (as recorded in the pointer
; located in the resident copy) to the resident code segment.
;----------------------------------------------------------------------
                MOV     AX,ES:[STR_LOC][2]      ;Get string segment
                CMP     AX,BX                   ;Same as old prog seg?
                JE      R_1
;----------------------------------------------------------------------
; If the segments don't match, the strings are in a separate block, so
; we can simply release the entire string block.
;----------------------------------------------------------------------
                MOV     ES,AX                   ;Point ES to segment
        ASSUME  ES:NOTHING

                MOV     AH,49H                  ;Free allocated memory
                INT     21H                     ; thru DOS
                JNC     R_2
                JMP     SHORT R_4
;----------------------------------------------------------------------
; The strings are still attached to the original BLITZKEY.COM file.
; (This occurs only on the first load.) Shrink the old code segment
; to hold just the program code. The current PSP must be set to the
; owner of this block (the resident copy) before making this call.
;----------------------------------------------------------------------
R_1:
                MOV     AH,4AH                  ;Modify block size
                MOV     BX,(OFFSET CUTOFF - CSEG + 15) SHR 4
                INT     21H                     ; thru DOS
                JC      R_4
;----------------------------------------------------------------------
; Try to locate a block in lower memory that is large enough to contain
; the strings. We want the allocated block must belong to the resident
; copy. It must be the active PSP when this call is made.
;----------------------------------------------------------------------
R_2:
                CALL    FIND_LOW                ;Look for memory block
        ASSUME  ES:NOTHING
                JC      R_3                     ;Jump if not found
;----------------------------------------------------------------------
; Room was found. The new segment was returned in AX. Copy the strings
; from this copy of the program to the new block. MOVE_STRINGS
; updates the pointers in the resident copy.
;----------------------------------------------------------------------
                MOV     ES,BP                   ;Point to res copy
        ASSUME  ES:NOTHING

                CALL    MOVE_STRINGS            ;Copy strings to block
        ASSUME  ES:NOTHING
;----------------------------------------------------------------------
; Before terminating, we must set the active PSP back to this copy of
; the program.
;----------------------------------------------------------------------
                MOV     AH,50H                  ;Set active PSP
                MOV     BX,CS                   ; to this program
                INT     21H                     ; thru DOS
;----------------------------------------------------------------------
; Enable macro expansion and terminate.
;----------------------------------------------------------------------
                MOV     ES,BP                   ;Point to res copy
        ASSUME  ES:NOTHING
                MOV     BYTE PTR ES:[DISABLED],0 ;Enable macros
R_EXIT:
                MOV     AH,9                    ;Display string
                MOV     DX,OFFSET COPYRIGHT$    ;Say who we are
                INT     21H                     ; thru DOS

                MOV     AH,4CH                  ;All done! Terminate.
                INT     21H                     ; thru DOS
;----------------------------------------------------------------------
; There is no room in low memory, but we still want to relocate these
; string to the lowest possible address. We currently own all memory.
; Shrink this PSP block to hold just the program, strings, and stack.
;----------------------------------------------------------------------
        ASSUME  ES:NOTHING
R_3:
                MOV     AH,50H                  ;Set active PSP
                MOV     BX,CS                   ; back to us
                INT     21H                     ; thru DOS

                MOV     AH,4AH                  ;Modify block size
                MOV     BX,[STACK_TOP]          ; to hold prog+stack
                ADD     BX,15                   ;Round up
                MOV     CL,4
                SHR     BX,CL                   ;Convert to paras
                MOV     [NEWSEGLEN],BX          ;Save this size

                PUSH    CS                      ;Put seg to modify (CS)
                POP     ES                      ; into ES
        ASSUME  ES:NOTHING

                INT     21H                     ; thru DOS
                JNC     R_5
;----------------------------------------------------------------------
; A memory error occurred. Display a message and exit.
;----------------------------------------------------------------------
R_4:
                MOV     DX,OFFSET BADREPLACE$   ;Indicate an error
                MOV     AH,9                    ;Display string
                INT     21H                     ; thru DOS
                JMP     R_EXIT
;----------------------------------------------------------------------
; Allocate all memory above us as a single block. If there's not
; enough to hold a new copy of this program, terminate.
;----------------------------------------------------------------------
R_5:
                MOV     AH,48H                  ;Allocate memory
                MOV     BX,0FFFFH               ;Ask for 640K
                INT     21H                     ; thru DOS

                CMP     BX,[NEWSEGLEN]          ;Enough mem available?
                JB      R_4

                MOV     AH,48H                  ;Allocate BX paras
                INT     21H                     ; thru DOS
                JC      R_4
;----------------------------------------------------------------------
; The segment of the new block is returned in AX. Duplicate the program
; at the new address. Copy from DS:SI to ES:DI.
;----------------------------------------------------------------------
                SUB     SI,SI                   ;SI = 0
                MOV     DI,SI                   ;DI = 0

                MOV     ES,AX                   ;New block segment
        ASSUME  ES:NOTHING
                MOV     CX,[STACK_TOP]          ;Bytes to move
                REP     MOVSB                   ;Copy to new address
;----------------------------------------------------------------------
; Now, hop up to our new home by using a far return to change segments.
;----------------------------------------------------------------------
                PUSH    AX                      ;Put new CS on stack
                MOV     DX,OFFSET TARGET        ;And address of the
                PUSH    DX                      ; next instruction
                DB      RETFAR                  ;Jump to new segment
;----------------------------------------------------------------------
; Now we're at AX:TARGET, in the new copy of the program. Note that
; although the segment is not really CSEG, it seems that way to the
; assembler for the purpose of calculating offsets.
; CS = new CSEG
; DS = old CSEG
; ES = nothing
; SS = old CSEG
; Move the stack to the new copy.
;----------------------------------------------------------------------
        ASSUME  CS:CSEG
TARGET:
                CLI                             ;Disable interrupts
                MOV     SS,AX                   ;Change segment
        ASSUME  SS:CSEG
                MOV     SP,[STACK_TOP]          ; and offset
                STI                             ;Enable interrupts
;----------------------------------------------------------------------
; Release the memory held by the old copy of the program. Then point
; DS to this segment.
;----------------------------------------------------------------------
                PUSH    DS                      ;Put old PSP segment
                POP     ES                      ; into  ES
        ASSUME  ES:NOTHING

                MOV     DS,AX                   ;Point DS to this seg
        ASSUME  DS:CSEG

                MOV     AH,49H                  ;Free block in ES
                INT     21H                     ; thru DOS
                JC      R_4
;----------------------------------------------------------------------
; Now allocate a block for the strings. The block must belong to the
; resident copy, so make it the active process.
;
; The block we released was big enough to hold the entire program. The
; allocation call must now succeed. The new segment is returned in AX.
;----------------------------------------------------------------------
                MOV     AH,50H                  ;Set active PSP
                MOV     BX,BP                   ; to resident copy
                INT     21H                     ; thru DOS

                CALL    FIND_LOW                ;Allocate block
        ASSUME  ES:NOTHING

                MOV     ES,BP                   ;Point ES to res PSP
        ASSUME  ES:NOTHING
;----------------------------------------------------------------------
; Copy the strings from this copy to the new block. MOVE_STRINGS
; updates the pointers in the resident copy.
;----------------------------------------------------------------------
                CALL    MOVE_STRINGS            ;Copy strings to block
        ASSUME  ES:NOTHING
;----------------------------------------------------------------------
; Release the current PSP segment.
;----------------------------------------------------------------------
                PUSH    CS                      ;Point ES to the
                POP     ES                      ; current segment
        ASSUME  ES:NOTHING

                MOV     AH,49H                  ;Release block
                INT     21H                     ; thru DOS
                JC      R_4
;----------------------------------------------------------------------
; Re-enable macro expansion.
;----------------------------------------------------------------------
                MOV     ES,BP                   ;Point to res copy
        ASSUME  ES:NOTHING
                MOV     BYTE PTR ES:[DISABLED],0 ;Enable macros
;----------------------------------------------------------------------
; Now terminate by using the TSR call with the already resident PSP.
; If we don't, DOS tries to access this program's original PSP to close
; the file handles and crashes.
;----------------------------------------------------------------------
                MOV     AH,31H                  ;Keep TSR seg
                MOV     BX,(OFFSET CUTOFF-CSEG+15) SHR 4 ;code length
                INT     21H                     ; thru DOS

REPLACE         ENDP

;======================================================================
; LOAD (Near)
;
; This procedure will cause the load copy of the program to become
; resident. The new copy will try to locate the strings as low in
; memory as possible. Hook the interrupt vectors, load the strings,
; and TSR.
;----------------------------------------------------------------------
; Entry: None
; Exit : Doesn't Return
;----------------------------------------------------------------------
; Changes: n/a
;----------------------------------------------------------------------
LOAD            PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG
;----------------------------------------------------------------------
; Display our copyright notice.
;----------------------------------------------------------------------
                MOV     AH,9                    ;Display string
                MOV     DX,OFFSET COPYRIGHT$    ;Say who we are
                INT     21H                     ; thru DOS
;----------------------------------------------------------------------
; Hook into the interrupt chains for Int 9 and Int 16h.
;----------------------------------------------------------------------
                MOV     AX,3509H                ;Get Int 9 vector
                INT     21H                     ; thru DOS
        ASSUME  ES:NOTHING

                MOV     WORD PTR [OLD9][0],BX   ;Save in resident
                MOV     WORD PTR [OLD9][2],ES   ; portion

                MOV     AX,2509H                ;Set vector for 9
                MOV     DX,OFFSET INT_9         ;Point it here
                INT     21H                     ; thru DOS

                MOV     AX,3516H                ;Get Int 16h vector
                INT     21H                     ; thru DOS
        ASSUME  ES:NOTHING

                MOV     WORD PTR [OLD16][0],BX  ;Save in resident
                MOV     WORD PTR [OLD16][2],ES  ; portion

                MOV     AX,2516H                ;Set vector for 16h
                MOV     DX,OFFSET INT_16        ;Point it here
                INT     21H                     ; thru DOS

                PUSH    CS                      ;Point ES back to
                POP     ES                      ; this seg
        ASSUME  ES:CSEG
;----------------------------------------------------------------------
; As loaded, BLITZKEY owns all memory from its PSP to the end of
; conventional memory. If UMBs are not linked, the only memory an
; allocation call will find will be below us. Try to find a low memory
; block to contain the strings. If success, AX contains segment of
; allocated block.  If no room is found, discard excess code and
; relocate strings downward.
;----------------------------------------------------------------------
                CALL    FIND_LOW                ;Look for lower block
                JNC     L_1                     ;No Carry if found
;----------------------------------------------------------------------
; No memory was found. Relocate the strings downward in the segment
; until they appear just after the last resident procedure.
;----------------------------------------------------------------------
                MOV     SI,OFFSET STRINGS       ;Source of strings
                MOV     DI,OFFSET CUTOFF        ;Destination
                MOV     CX,[STR_LEN]            ;Number bytes to move

                MOV     STR_LOC[2],ES           ;New segment of strings
                MOV     STR_LOC[0],DI           ;New offset

                MOV     DX,CX                   ;Length of strings
                ADD     DX,DI                   ; plus program length
                ADD     DX,15                   ;Round up
                PUSH    CX                      ;(save count for REP)
                MOV     CL,4
                SHR     DX,CL                   ;Convert to paras
                POP     CX                      ;(restore count)
                JMP     SHRIVEL
;----------------------------------------------------------------------
; A chunk of memory of suitable size was found below this program at
; segment in AX. Relocate the strings to the new area.  DS:SI to AX:DI
; Then TSR,leaving only the macro expander resident in this segment.
;----------------------------------------------------------------------
L_1:
                CALL    MOVE_STRINGS            ;Move the strings
        ASSUME  ES:NOTHING
                MOV     DX,(OFFSET CUTOFF -  CSEG + 15) SHR 4
;----------------------------------------------------------------------
; This copy is going to become resident. Modify RES_MARKER.
;----------------------------------------------------------------------
L_2:
                NOT     WORD PTR [RES_MARKER]   ;Modify for matching

                MOV     AH,31H                  ;Keep process resident
                INT     21H                     ; thru DOS

LOAD            ENDP

;======================================================================
; FIND_LOW (Near)
;
; Look for a piece of memory large enough to hold DS:STR_LEN bytes.
; Will find low memory in standard systems or use high memory if
; it was linked when program was started.
;----------------------------------------------------------------------
; Entry: None
; Exit :
;       CF = NC - Memory block found
;            AX = segment of block
;
;       CF = CY - Memory block not found
;----------------------------------------------------------------------
; Changes: AX BX CX
;----------------------------------------------------------------------
FIND_LOW        PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG

                MOV     AH,48H                  ;Allocate memory
                MOV     BX,[STR_LEN]            ;Change length in bytes
                ADD     BX,15
                MOV     CL,4
                SHR     BX,CL                   ; to paras
                INT     21H                     ; thru DOS

                RET

FIND_LOW        ENDP

;======================================================================
; MOVE_STRINGS (Near)
;
; Copies the string block from the current copy (located at
; DS:[STRINGS]) to AX:0. The string pointer and block length is updated
; in the resident copy.
;----------------------------------------------------------------------
; Entry:
;       DS = segment of current program copy
;       ES = segment of resident program copy
;       AX = destination segment for string copy
; Exit:
;       None
;----------------------------------------------------------------------
; Changes: CX SI DI ES
;----------------------------------------------------------------------
MOVE_STRINGS    PROC    NEAR
        ASSUME  CS:CSEG, DS:CSEG, ES:NOTHING, SS:CSEG

                CLD                             ;String moves forward
;----------------------------------------------------------------------
; Save the string length of the current strings in the resident copy.
;----------------------------------------------------------------------
                MOV     CX,[STR_LEN]            ;Bytes to move
                MOV     ES:[STR_LEN],CX         ;Update resident copy
;----------------------------------------------------------------------
; Copy the strings into the indicated block.
;----------------------------------------------------------------------
                SUB     DI,DI                   ;Copy to offset 0
                MOV     ES:[STR_LOC][0],DI      ;New offset
                MOV     ES:[STR_LOC][2],AX      ;New segment

                MOV     ES,AX                   ;Destination is ES:DI
        ASSUME  ES:NOTHING

                MOV     SI,OFFSET STRINGS       ;Source is DS:SI
                REP     MOVSB                   ;Move 'em

                RET

MOVE_STRINGS    ENDP

;======================================================================
; The strings are stored here, after the program code. The macro titles
; are stored in ASCIIZ form. The macros are stored as words.
; During execution, the strings can be anywhere in memory.
;----------------------------------------------------------------------
                DW      0               ;Prevents backing up too far
                                        ;Used when editing ONLY
STRINGS         DW      26 DUP(0,0)     ;Strings start empty
                DW      0               ;End of string block
STRING_END      EQU     $

CSEG            ENDS
                END     ENTPT

[ RETURN TO DIRECTORY ]