cseg    segment para public 'code'
org     100h
keymod  proc far

keyaddr equ 9h*4        ; keyboard interrupt address
vidaddr equ 10h*4       ; video interrupt address
usraddr equ 61h*4       ; user interrupt address
whozat  equ 1235h       ; signature value

; Memory-resident program to intercept keyboard interrupts.
; Once loaded, this program will remain resident until DOS is reloaded.
; The program can be re-run, to change the original options.
; This program is used to control the state of the following keys:
;
;       Caps Lock  : U+ (force on), U- (force off), or U (normal)
;       Num Lock   : N+ (force on), N- (force off), or N (normal)
;       Alt Key    : A+ (force on), A- (force off), or A (normal)
;       Ctrl Key   : C+ (force on), C- (force off), or C (normal)
;       Left Shift : L+ (force on), L- (force off), or L (normal)
;       Right Shift: R+ (force on), R- (force off), or R (normal)
;       Boot       : B+ or B (on - normal) or B- (off - disabled)
;       Ctrl-Break : X+ or X (on - normal) or X- (off - disabled)
;       Suspend    : S+ (enable pause), S- or S (normal - no effect)
;       Reset all keys to normal: *
;
; For example, 'KEYMOD U+ B- X-' would force caps lock on, disable booting via
; Alt-Ctrl-Del, and disable Ctrl-Break.

        assume cs:cseg,ds:cseg,ss:nothing,es:nothing
        jmp p100                ; start-up code

keyint  dd 0                    ; keyboard interrupt vector
vidint  dd 0                    ; video interrupt vector
signature dw whozat             ; program's signature

                                ; keyboard mod table
                                ; 0 = no change, 1 = force off, 2 = force on
modtbl  db 0ffh                 ; insert - not used
        db 0                    ; caps lock
        db 0                    ; num lock
        db 0ffh                 ; suspend - used below
        db 0                    ; alt
        db 0                    ; ctrl
        db 0                    ; left shift
        db 0                    ; right shift
noboot  db 0                    ; disable boot if 1
nobreak db 0                    ; disable ctrl-break if 1
suspend db 0                    ; enable suspend key if 2
tstack  db 16 dup ('stack   ')  ; temporary stack
estack  db 0                    ; end of temp stack
holdss  dw 0                    ; original ss
holdsp  dw 0                    ; original sp

p000:   sti                     ; interrupt (resident) code starts here
        push ax                 ; save registers
        push cx
        push es
        push di
        pushf

        mov ax,40h
        mov es,ax
        mov di,17h              ; point to keyboard flag
        mov ah,es:[di]          ; keyboard state - flag in ah

        mov cx,8
        mov di,offset modtbl    ; point to mod table
p010:   mov al,cs:[di]          ; get mod value
        cmp al,1                ; turn key off?
        jz p020                 ; yes
        cmp al,2                ; turn key on?
        jz p015                 ; yes
        jmp p030                ; do nothing with this one

p015:   mov al,1                ; turn key on
        rol al,cl               ; rotate left
        ror al,1                ; back 1
        or ah,al                ; switch bits on in ah
        jmp p030

p020:   mov al,1                ; turn key off
        rol al,cl
        ror al,1
        xor al,0ffh             ; flip the bits
        and ah,al               ; switch bits off in ah

p030:   inc di
        loop p010               ; continue

        mov al,cs:[noboot]      ; is boot disabled?
        cmp al,1
        jnz p040                ; no
        test ah,08h             ; alt pressed?
        jz p040                 ; no
        test ah,04h             ; ctrl pressed?
        jz p040                 ; no
        and ah,0fbh             ; tried alt-ctrl-del, turn off ctrl!

p040:   test ah,04h             ; is ctrl pressed?
        jz p050                 ; no
        mov al,cs:[nobreak]     ; is ctrl-break disabled?
        cmp al,1
        jnz p050                ; no
        in al,60h               ; get next char from kybd
        cmp al,46               ; is it a 'c'?
        jz p045                 ; yes
        cmp al,70               ; is it a break?
        jz p045                 ; yes
        jmp p050
p045:   and ah,0fbh             ; turn off ctrl!

p050:   mov di,17h
        mov es:[di],ah
        popf
        pop di
        pop es
        pop cx
        pop ax
        jmp cs:[keyint]

p060:   sti                     ; resident code for video interrupt
        push ax                 ; save registers
        push di
        push es

        mov al,cs:[suspend]     ; is suspend on?
        cmp al,2
        jnz p070                ; no
        mov di,40h
        mov es,di
        mov di,17h              ; point to keyboard flag
        mov al,es:[di]          ; get keyboard flag
        test al,10h             ; check suspend
        jz p070                 ; it's off
        cmp ah,7                ; scroll down?
        jz p065                 ; yes
        cmp ah,6                ; scroll up?
        jnz p070                ; no

p065:
        cli                     ; no interrupts
        mov ax,ss               ; save original stack
        mov cs:[holdss],ax
        mov cs:[holdsp],sp
        mov ax,cs               ; point to temp stack
        mov ss,ax               ; make ss=cs
        mov sp,offset estack
        sti                     ; allow interrupts

        mov ah,0                ; wait for a key to be pressed
        int 16h

        cli                     ; no interrupts
        mov ax,cs:[holdss]      ; restore original stack
        mov ss,ax
        mov sp,cs:[holdsp]
        sti                     ; allow interrupts

        mov di,17h
        mov al,es:[di]          ; keyboard state - flag in al
        and al,0efh             ; turn suspend off
        mov es:[di],al          ; re-set state

p070:   pop es
        pop di
        pop ax
        jmp cs:[vidint]         ; jump to video interrupt


p100:                           ; transient code starts here
        call p120               ; get current keyboard vector
        call p140               ; decode command line
        call p160               ; turn suspend off
        call p180               ; display key settings
        mov al,firstime
        cmp al,1                ; first time?
        jz p105                 ; yes - leave resident
        int 20h                 ; no - vanish!

p105:   mov dx,offset p100      ; last byte of resident portion
        int 27h                 ; terminate

p120    proc near               ; get current keyboard buffer
        mov dx,offset copyr
        call p200               ; display copyright
        mov di,usraddr+2        ; point to user interrupt
        mov ax,0
        mov es,ax
        mov bx,es:[di]          ; get current contents
        mov es,bx               ; make it the segment
        mov di,offset signature
        mov cx,es:[di]          ; get signature
        cmp cx,whozat           ; is it this program?
        jz p125                 ; yes - don't install it

        mov ax,0
        mov es,ax
        mov di,keyaddr          ; point to keyboard interrupt
        mov ax,es:[di]          ; get current ip
        mov bx,es:[di+2]        ; get current cs
        mov si,offset keyint    ; save address
        mov [si],ax
        mov [si+2],bx

        mov di,vidaddr          ; point to video interrupt
        mov ax,es:[di]          ; get current ip
        mov bx,es:[di+2]        ; get current cs
        mov si,offset vidint    ; save address
        mov [si],ax
        mov [si+2],bx

        mov al,1                ; first time through
        mov firstime,al         ; set first time flag
        mov dx,offset install
        call p200
        mov ax,0
        mov es,ax
        mov bx,ds
        cli                     ; no interrupts

        mov di,keyaddr
        mov ax,offset p000
        mov es:[di],ax          ; change keyboard interrupt
        mov es:[di+2],bx

        mov di,vidaddr
        mov ax,offset p060
        mov es:[di],ax          ; change video interrupt
        mov es:[di+2],bx

        mov di,usraddr          ; set user interrupt - segment only
        mov ax,offset signature
        mov es:[di],ax
        mov es:[di+2],bx

        sti                     ; allow interrupts again

        push cs
        pop bx                  ; get current cs

p125:   mov si,offset realcs
        mov [si],bx             ; save real cs
        ret
p120    endp

p140    proc near               ; decode command line
        mov si,80h              ; point to command line
        mov ch,0
        mov cl,[si]             ; get length
        jcxz p159               ; length is zero
        push cx                 ; save length
        inc si                  ; point to first char
        push si                 ; save start addr
p142:   mov al,[si]             ; get character
        cmp al,'a'              ; lower case?
        jb p144                 ; no
        cmp al,'z'              ; lower case?
        ja p144                 ; no
        sub al,'a'-'A'          ; yes - make it upper case
        mov [si],al             ; put it back on the line

p144:   inc si                  ; point to next
        loop p142               ; all done?

        mov ax,realcs
        mov es,ax
        pop bx                  ; get start addr
        pop cx                  ; get command line length

p146:   mov al,[bx]             ; get character
        cmp al,' '              ; is it a space?
        jz p155                 ; yes
        cmp al,'*'              ; is it an asterisk?
        jz p146a                ; yes
        cmp al,'8'              ; is it an 8 (lower-case asterisk)?
        jz p146a                ; yes
        jmp p146b

p146a:  mov di,offset modtbl    ; clear all key modifications
        mov cx,11
        mov al,0
        rep stosb               ; clear mod table
        jmp p159                ; return

p146b:  mov si,offset tokens    ; search token list
        mov dl,0                ; number of items to compare
p147:   mov dh,[si]             ; get token
        cmp dh,al               ; same?
        jz p148                 ; yes
        inc si                  ; point to next token
        inc dl
        cmp dl,11               ; done?
        jnz p147                ; no
        jmp p155                ; no hit

p148:                           ; got a valid character
        mov dh,0
        mov di,offset modtbl
        add di,dx               ; got offset into table
        mov es:[di],dh          ; clear current setting
        mov dh,[bx+1]           ; get next character
        cmp dh,'+'              ; is it a plus?
        jnz p149                ; no
        mov dh,2                ; set it on
        jmp p150
p149:   cmp dh,'-'              ; is it a minus?
        jnz p155                ; no
        mov dh,1                ; set it off
p150:   mov es:[di],dh          ; change mod table

p155:   inc bx                  ; point to next char
        loop p146               ; done?

p159:   ret
p140    endp

p160    proc near               ; turn suspend off
        push ax
        push es
        push di
        mov ax,40h
        mov es,ax
        mov di,17h              ; point to keyboard flag
        mov al,es:[di]          ; keyboard state - flag in al
        and al,0efh             ; turn suspend off
        mov es:[di],al          ; re-set state
        pop di
        pop es
        pop ax
        ret
p160    endp

p180    proc near               ; display status of keys
        mov ax,realcs           ; get real code segment
        mov es,ax               ; get code seg in es
        mov cx,11               ; items to display
        mov di,offset modtbl    ; point to mod table
        mov dx,offset dummy1    ; point to first message

p181:
        mov si,dx
        mov al,[si]             ; get first char of message
        cmp al,255              ; no show?
        jz p185                 ; yes
        call p200               ; print key name(s)
        push dx
        mov al,es:[di]
        cmp al,0
        jb p182                 ; out of range
        cmp al,2
        jbe p183
p182:   mov al,2
p183:   push cx
        mov cl,4
        mov ah,0
        rol al,cl               ; convert number to message offset
        pop cx
        mov dx,offset statemsg
        add dx,ax
        call p200               ; print status message
        pop dx                  ; get part 1 address
p185:   add dx,18               ; point to next one
        inc di                  ; point to next status byte
        loop p181               ; go back for more...
        ret
p180    endp

p200    proc near               ; display message
        push ax
        mov ah,9
        int 21h
        pop ax
        ret
p200    endp

dummy1  db 255,'                $'      ; key names
capsmsg db 'Caps Lock    (U):$'
numlmsg db 'Num Lock     (N):$'
dummy2  db 255,'                $'
altkmsg db 'Alt key      (A):$'
ctrlmsg db 'Ctrl key     (C):$'
lshkmsg db 'Left shift   (L):$'
rshkmsg db 'Right shift  (R):$'
bootmsg db 'Alt-Ctrl-Del (B):$'
cbrkmsg db 'Ctrl-Break   (X):$'
suspmsg db 'Suspend      (S):$'

statemsg db ' Not affected',10,13,'$'   ; status messages
         db ' Off         ',10,13,'$'
         db ' On          ',10,13,'$'

copyr   db 'KEYMOD - Copyright 1983 Data Base Decisions',10,13,10,13,'$'
install db 'Installing keyboard modifier',10,13,10,13,'$'
realcs  dw 0                    ; cs of first copy of this program
firstime db 0                   ; 0 if repeat, 1 if first time (original)
tokens  db 255,'UN',255,'ACLRBXS'; commands

keymod  endp
cseg    ends
end     keymod
