;
                 page 66,132
;
;KYLOCK.ASM for the IBM PC/XT & PCJR - 1987 by Ocean
;
;This is part 2 of a 2 part utilities set consisting of -----------------------
;SCRNOFF3.ASM & KYLOCK.ASM. -----------------------THEY MUST BE USED TOGETHER!
;                                                       -----------------------
;
kb_data             equ 60h                     ;keyboard data port
kb_ctrl             equ 61h                     ;keyboard control port
k_key               equ 37                      ;scan code of k key
o_key               equ 24                      ;scan code of o key
del_key             equ 83                      ;scan code of Del key
ins_key             equ 82                      ;scan code of Ins key
alt_key             equ 8                       ;shift code for Alt key
ctrl_key            equ 4                       ;shift code for Ctrl key
eoi                 equ 20h                     ;end-of-interrupt signal
int_ctrl_port       equ 20h                     ;8259 interrupt controller port
;
code             segment para public 'code'
                 assume cs:code
                 org 0
seg_org             equ $
;
bios_data        segment at 40h                 ;bios data area
                 org 063h
addr_6845        dw ?                           ;define location that holds
                                                ;word address of active display
                 org 065h
crt_mode_set     db ?                           ;define location that holds
                                                ;byte loaded into pc active
                                                ;display port or pcjr's vga
bios_data        ends
;
                 org 0100h
begin:           jmp initialize                 ;goto initialization routine
;
adapter          db 0                           ;0 = EGA, 1 = CGA or MDA, and
                                                ;2 = PCJR
pcjr_mode        db ?                           ;current setting of PCJR video
pc_mode          db ?                           ;current setting of 6845 mode
                                                ;control register of the pc
kylock_status    db 0                           ;interrupt routine status flag
char_count       dw ?                           ;length of password
hotky_val        db 00h,44h,2fh,2eh,2dh         ;hotkey make scan code table -
                 db 20h,1eh,16h,11h             ;filler,F10,v,c,x,d,a,u, and w
msr_address      dw ?                           ;address of mode select register
pcjr_signature   db 0fdh                        ;PCJR BIOS signature
ibm_signature    db 'IBM'                       ;EGA BIOS signature
errmsg           db 13,10,'KYLOCK ALREADY LOADED!',13,10,'$'
;
old_int_9h       label dword                    ;holding area for INT 9h vector
int_9h_ptr       dw 2 dup (?)
old_int_16h      label dword                    ;holding area for INT 16h vector
int_16h_ptr      dw 2 dup (?)
old_int_5h       label dword                    ;holding area, INT 5h vector
int_5h_ptr       dw 2 dup (?)
;
;------------------------------------------------------------------------------
;All calls to INT 5 will henceforth be routed thru here.  On the PCJR, the
;Print Screen Function is processed by INT 48h (This interrupt translates the
;PCJR 62 key keyboard scan codes to be compatible with the 83 key keyboard of
;the PC and then INT 9 occurs.) which activates INT 5 before INT 9 is called.
;To insure that a copy of the screen cannot be printed out on the PCJR while
;the keyboard is supposedly locked, therefore, requires that the bios print
;screen service be intercepted and nulled while KYLOCK is active.
;------------------------------------------------------------------------------
;
new_int_5h       proc near
                 test kylock_status,01          ;kylock routine active?
                 jnz print_not                  ;yes, block PRTSCR function
                 jmp old_int_5h                 ;no, goto normal INT 5h handler
print_not:       iret                           ;exit, nulling INT 5h
new_int_5h       endp
;
;------------------------------------------------------------------------------
;All calls to INT 16h will henceforth be routed thru here.  If ah = 255 on
;entry, bh is set to 255 to signal the caller that this routine was indeed
;invoked.
;------------------------------------------------------------------------------
new_int_16h      proc near
                 cmp ah,255                     ;ah = 255?
                 jne newint1                    ;no, then jump
                 mov bh,ah                      ;yes, set bh to 255 and exit
                 iret
newint1:         jmp old_int_16h                ;goto normal 16h handler
new_int_16h      endp
;
;------------------------------------------------------------------------------
;Execution comes here every time an interrupt 9 is generated from the keyboard.
;------------------------------------------------------------------------------
;
kylock           proc near
                 sti                            ;restore interrupts
                 push ax                        ;save registers
                 push bx
                 push cx
                 push dx
                 push si
                 push di
                 push ds
                 push es
                 in al,kb_data                  ;read keypress (or break)
                 cmp kylock_status,0            ;kylock routine active?
                 je kylock0                     ;no, then check for Alt-O/Alt-K
;
;The kylock routine is already in progress.  Check for a press of one of the
;POPUP DESKSET "hot-keys" and block it out if it just occured.  (This routine
; may be omitted if this program is not used with POPUP DESKSET.)
;
                 and al,7fh                     ;strip the break bit
                 mov dl,al                      ;load keypress make scan code
                 mov cx,8                       ;number of hotkeys to check
                 lea bx,hotky_val               ;address of table of hotkeys
next1:           mov ax,cx                      ;sequential pointer to hotkey
                 xlat hotky_val                 ;load value of hotkey to al
                 cmp dl,al                      ;keypress match this "hotkey"?
                 jne loop                       ;no, how about next one?
                 mov ah,2                       ;yes, get shift status
                 int 16h                        ;
                 cmp al,alt_key                 ;alt pressed?
                 jne exit1                      ;no, goto old int 9 routine
                 call kb_reset                  ;alt-"hotkey" pressed; reset kb
                 jmp unlck1                     ;exit
loop:            loop next1                     ;get next value from table
                 mov al,dl                      ;no match - continue
;
;The kylock routine is already in progress.  Check for a press of Ctrl-Alt-Del
;or Ctrl-Alt-Ins (On the PCJR, this combination invokes diagnostic routines
;from which a Ctrl-Alt-Del can reset the machine.) and block it out if it just
;occurred.
;
                 cmp al,ins_key                 ;was Ins Key pressed?
                 je sh_stat                     ;get shift status
                 cmp al,del_key                 ;was the Del key pressed?
                 jne exit1                      ;no, goto old INT 9 routine
sh_stat:         mov ah,2                       ;Int 16h - get shift status
                 int 16h                        ;get status of shift keys
                 and al,alt_key+ctrl_key        ;ctrl-alt pressed?
                 cmp al,alt_key+ctrl_key
                 jne exit1                      ;no, goto old int 9 routine
                 call kb_reset                  ;ctrl-alt-del press - reset kb
                 jmp unlck1                     ;exit
;                
;The kylock routine is not already active.  If Alt-O was pressed, toggle timer
;on/off, then block it out.
;
kylock0:         cmp al,o_key                   ;o key pressed?
                 jne strp_brk                   ;no, jump
                 mov ah,2                       ;yes, get shift status
                 int 16h
                 test al,alt_key                ;Alt pressed when o key press?
                 jnz toggle                     ;Alt-o key press --- toggle
                 jmp strp_brk                   ;Alt-o key not pressed --- jump
;
;Int 16h is called here with ah set to 158.  Int 16h is a chained interrupt
;hooked into by SCRNOFF3.  When it is SCRNOFF3's turn in the chain, it tests
;the value of the register AH.  If it has been set to 158, then it toggles its
;timer on/off.
;
toggle:          mov ah,158                     ;Place 158 into AH
                 int 16h                        ;Call INT with AH set to 158
                 call kb_reset                  ;reset kybrd, throw away alt-o
                 jmp unlck1                     ;exit, skip normal kybrd route
strp_brk:        and al,7fh                     ;strip the break bit
;
;The kylock routine is not already active.  See if Alt-K was pressed.
;
                 cmp al,k_key                   ;is it the k key?
                 jne exit                       ;no, goto old int 9 routine
                 mov ah,2                       ;int 16h - get shift status
                 int 16h
                 test al,alt_key                ;is the alt key pressed?
                 jne kylock2                    ;yes, goto kylock routine
;
;Exit routine restores registers and, if screen is blanked, enables the video
;& resets count before exiting thru normal keyboard interrupt handler.
;
exit:            pop es                         ;pop registers off stack
                 pop ds
                 pop di
                 pop si
                 pop dx
                 pop cx
                 pop bx
                 pop ax
;
;Call user interrupt 4dh (created by scrnoff3.com) to unblank screen if the
;timer within scrnoff3.com is 0.
;
                 int 4dh                        ;user interrupt 4dh
                 jmp old_int_9h                 ;exit to normal interrupt
                                                ;handler
;
;Exit1 routine restores registers before exiting thru normal keyboard interrupt
;handler and bypasses video enable and count reset routines since kylock is in
;progress.
;
exit1:           pop es                         ;pop registers off stack
                 pop ds
                 pop di
                 pop si
                 pop dx
                 pop cx
                 pop bx
                 pop ax
                 jmp old_int_9h                 ;exit to normal interrupt
                                                ;handler
;
;Trigger keys are pressed.  Reset the keyboard, send an EOI signal to the 8259,
;and execute our kylock routine.
;
kylock2:         call kb_reset                  ;reset keyboard and issue EOI
                 mov kylock_status,1            ;set status byte
                 push cs                        ;set ds and es to the code
                                                ;segment
                 pop ds
                 push cs
                 pop es
                 assume ds:code
;
;Begin kylock routine by disabling video and getting a password from the user.
;
                 call video_disable             ;disable video display
                 xor di,di                      ;point DI to start of PSP
                 call get_password              ;get the password
                 or cx,cx                       ;any characters input?
                 je unlock                      ;no, exit this interrupt
                 cmp al,27                      ;was input terminated with ESC
                 je unlock                      ;yes, then exit
                 mov char_count,cx              ;save length of password
;
;The password is now entered and the keyboard 'locked', awaiting input of a 
;matching password.  Accept input from the keyboard but continue to loop until
;the password is correctly re-entered.
;
kylock3:         mov di,80h                     ;point DI to upper storage
                 call get_password              ;get password
                 cmp cx,char_count              ;same number of characters?
                 jne kylock3                    ;no, then paswords don't match
                 cmp al,27                      ;was input ended with ESC key?
                 je kylock3                     ;yes, start input loop over
                 xor si,si                      ;point SI to first password
                 mov di,80h                     ;point DI to second password
                 cld                            ;clear DF for string operations
                 repe cmpsw                     ;compare the two passwords
                 jne kylock3                    ;not equal, continue looping
;
;Exit kylock routine by clearing the status flag, enabling the video display,
;and executing an IRET instruction.
;
unlock:          mov kylock_status,0            ;reset status byte
                 push ax                        ;save ax & dx
                 push dx
                 call video_enable              ;re-enable video display
                 pop dx                         ;restore dx & ax
                 pop ax
unlck1:          pop es                         ;restore register values
                 pop ds
                 pop di
                 pop si
                 pop dx
                 pop cx
                 pop bx
                 pop ax
                 iret                           ;exit kylock routine
kylock           endp
;
;------------------------------------------------------------------------------
;VIDEO ENABLE and VIDEO DISABLE enable and disable the VIDEO output to the
;screen of the PCJR
;------------------------------------------------------------------------------
;
video_disable    proc near                      ;disable the video display
                 cmp adapter,0                  ;is video adapter an EGA?
                 je ega_disable                 ;yes, then jump
                 test adapter,2                 ;is computer a PCJR?
                 jnz jr_disable                 ;yes, then jump
;
;Disable video of PC with CGA or MDA adapter.
;
                 push ds                        ;save ds
                 mov ax,bios_data               ;set es to bios data area
                 mov ds,ax
                 assume ds:bios_data
                 mov al,crt_mode_set            ;get current value (dynamic) of
                                                ;6845 mode control register
                 mov pc_mode,al                 ;save it
                 mov ax,addr_6845               ;get active display address
                 add ax,4                       ;add 4 to get MSR address
                 mov msr_address,ax             ;save address
                 mov al,0ah                     ;out 6845 index register...
                 mov dx,03b4h                   ;points to 6845 data reg. 10
                 out dx,al
                 mov al,2bh                     ;value to turn off cursor
                 mov dx,03b5h                   ;6845 data registers port
                 out dx,al                      ;disable cursor
                 mov dx,msr_address             ;6845 mode control register
                 mov al,pc_mode                 ;get current value of 6845 mode
                 and al,37h                     ;strip enable bit
                 out dx,al                      ;disable video display
                 pop ds                         ;restore ds
                 ret
;
;Disable video of PC with an EGA card.
;
ega_disable:     xor al,al                      ;zero al (clear bit 5)
                 call set_ega                   ;disable EGA
                 ret
;
;Disable video of PCJR.
;
jr_disable:      push ds                        ;save ds register
                 mov ax,bios_data               ;set es to bios data area
                 mov ds,ax                      ;                              
                 assume ds:bios_data            ;                              
                 mov al,crt_mode_set            ;get current value (dynamic) of
                                                ;PCJR VGA mode control register
                 mov pcjr_mode,al               ;store current value, VGA mode
                 mov dx,03dah                   ;PCJR VGA I/O port
                 in  al,dx                      ;addr/data f/f to proper state
                 mov al,02h                     ;VGA border color register
                 out dx,al                      ;set VGA to border color cont.
                 mov al,00h                     ;color black
                 out dx,al                      ;set border color black
                 mov al,00                      ;VGA mode control 1 register
                 out dx,al                      ;set VGA to mode control 1
                 mov al,pcjr_mode               ;get current value of VGA mode
                 and al,0f7h                    ;toggle enable/disable bit
                 out dx,al                      ;disable video display
                 pop ds                         ;restore ds register
                 ret
video_disable    endp
;
video_enable     proc near                      ;enable the video display
                 cmp adapter,0                  ;is video adapter an EGA?
                 je ega_enable                  ;yes,then jump
                 test adapter,2                 ;is the computer a PCJR?
                 jnz jr_enable                  ;yes, then jump
;
;Enable video of PC with CGA or MDA adapter card.
;
                 mov al,0ah                     ;out to 6845 index register...
                 mov dx,03b4h                   ;points to 6845 data reg. 10
                 out dx,al
                 mov al,0bh                     ;value to turn cursor on
                 mov dx,03b5h                   ;6845 data registers port
                 out dx,al                      ;enable cursor
                 mov dx,msr_address             ;6845 mode control register
                 mov al,pc_mode                 ;get stored value 6845 mode
                 out dx,al                      ;enable pc display
                 ret
;
;Enable video of PC with an EGA adapter card.
;
ega_enable:      mov al,20h                     ;set bit 5 of al
                 call set_ega                   ;enable EGA video
                 ret
;
;Enable video of PCJR.
;
jr_enable:       mov dx,03dah                   ;set PCJR VGA address
                 in al,dx                       ;addr/data f/f to proper state
                 mov al,00h                     ;VGA mode control 1 register
                 out dx,al                      ;set VGA to mode control 1
                 mov al,pcjr_mode               ;get stored value VGA mode
                 out dx,al                      ;enable PCJR video display ...
                                                ;border remains black
                 ret
video_enable     endp
;
;-----------------------------------------------------------------------------
;SET_EGA is called by VIDEO_ENABLE and VIDEO_DISABLE routines to selectively
;set or clear bit 5 of the EGA Attribute Address Register.
;Entry:  AL - value to OUT to Attribute Address Register
;-----------------------------------------------------------------------------
;
set_ega          proc near
                 push ax                        ;save AL
                 mov dx,3bah                    ;reset monochrome flip-flop...
                 in al,dx                       ;for write to Address Register
                 mov dx,3dah                    ;reset color flip-flop
                 in al,dx
                 mov dx,3c0h                    ;set DX to Attr Addr Register
                 pop ax                         ;retrieve entry value of AL
                 out dx,al                      ;write value to register
                 ret
set_ega          endp
;
;------------------------------------------------------------------------------
;KB RESET subroutine resets the keyboard and issues an EOI to the 8259 PIC.
;------------------------------------------------------------------------------
;
kb_reset         proc near
                 test adapter,2                 ;Is this computer a PCJR?
                 jnz jr_kbd                     ;yes,then jump
;
;Reset standard PC keyboard.
;
std_kbd:         in al,kb_ctrl                  ;get current control port value
                 mov ah,al                      ;save it
                 or al,80h                      ;set the keyboard clear bit
                 out kb_ctrl,al                 ;send reset value to port
                 mov al,ah                      ;get the original value
                 out kb_ctrl,al                 ;enable the keyboard
                 jmp kbd1                       ;jump to disable interrupts
                                                ;and send EOI signal to 8259
;
;Reset PCJR keyboard.
;
jr_kbd:          in al,0a0h                     ;yes, then read NMI mask
                                                ;register to clear PCJR
                                                ;keyboard NMI latch
kbd1:            cli                            ;disable interrupts
                 mov al,eoi                     ;get eoi value
                 out int_ctrl_port,al           ;send eoi signal to the 8259
                 sti                            ;enable interrupts
                 ret                            ;return to caller
kb_reset         endp
;
;------------------------------------------------------------------------------
;GET PASSWORD subroutine reads up to 50 characters entered from the keyboard
;and stores them in the designated buffer.  Backspace key is active as an
;editing key.
;Entry:  ES:DI - buffer address        | EXIT:  CX - character count
;------------------------------------------------------------------------------
;
get_password     proc near
                 cld                            ;clear DF for string operations
                 xor cx,cx                      ;zero CX - initial char count
getpas1:         mov ah,0                       ;INT 16h funct. - get keypress
                 int 16h                        ;get character from keyboard
                 cmp al,13                      ;ENTER key?
                 je done                        ;yes, then exit
                 cmp al,27                      ;ESC key?
                 je done                        ;yes, then exit
                 cmp al,8                       ;BACKSPACE key?
                 je backspace                   ;yes,goto backspace routine
                 cmp cx,40h                     ;is the buffer full?
                 je buffer_full                 ;yes, then don't accept entry
                 inc cx                         ;char entered - increment count
                 stosw                          ;deposit character in buffer
                 jmp getpas1                    ;return for more input
backspace:       or cx,cx                       ;any characters to delete?
                 je getpas1                     ;no,then goto input loop
                 sub di,2                       ;decrement buffer pointer
                 dec cx                         ;decrement character count
                 jmp getpas1                    ;return to input loop
buffer_full:     mov ah,14                      ;INT 10h function - Write TTY
                 mov al,7                       ;ASCII code for beep
                 int 10h                        ;sound the beep
                 jmp getpas1                    ;and return for more input
done:            ret                            ;return to calling routine
get_password     endp
;
;------------------------------------------------------------------------------
;INITIALIZE routine checks to see if KYLOCK has already been loaded.  If so,
;execution aborts with an error message.  If it hasn't, then the value of 
;ADAPTER is set according to the type of display adapter present in the system
;and the vectors in low memory pointing to the INT 9h and 16h routines are set
;to point to our own newly installed code.
;------------------------------------------------------------------------------
;
initialize       proc near
;
;See if KYLOCK has been previously loaded by calling INT 16h with AH set to
;255 and BH set to 0.  If BH comes back unchanged, then KYLOCK is NOT
;currently resident in memory; if BH = 255, then KYLOCK has been loaded.
;
                 mov ah,255                     ;set AH and BH
                 xor bh,bh
                 int 16h                        ;call interrupt routine
                 or bh,bh                       ;is BH = 0?
                 je init1                       ;yes, then continue
                 lea dx,errmsg                  ;no, print error message & exit
                 mov ah,09h
                 int 21h
                 ret
;
;Check the computer's ID to see if its a PC or PCJR.
;
init1:           mov ax,0f000h                  ;es to BIOS segment holding
                 mov es,ax                      ;computer ID value
                 mov al,byte ptr es:[0fffeh]    ;offset address and load to al
                 sub al,0fch                    ;
                 or al,al                       ;al = 0?
                 jz pc                          ;AT, try the pc routine...
                 dec al                         ;al = 1?
                 jz pcjr                        ;yes, then computer is a PCJR
                 dec al                         ;al = 2?
                 jz pc                          ;XT, try the pc routine...
                 dec al                         ;al = 3?
                 jz pc                          ;PC
                 jnz pc                         ;unidentified computer, try PC
                 ret
;
;The computer is a PCJR.
;
pcjr:            mov adapter,2                  ;set adapter to PCJR
                 jmp init2                      ;jump to install program
;
;The computer is a PC.  Check for the presence of an Enhanced Graphics Adapter
;by looking for an 'IBM'signature in the EGA Bios area.
;
pc:              mov ax,0c000h                  ;set ES to EGA BIOS segment
                 mov es,ax
                 mov di,1eh                     ;starting address of signature
                 lea si,ibm_signature           ;point si to 'IBM' text
                 mov cx,3                       ;three characters to check
                 cld                            ;clear df
                 repe cmpsb                     ;compare the three bytes
                 je init2                       ;signature found - EGA present
;
;The computer is a PC, but the display adapter is not an EGA.  It must be
;either a CGA or an MDA.
;
                 mov adapter,1                  ;set ADAPTER for CGA or MDA
;
;Save the current interrupt 16h vector and replace it with our own.
;
init2:           mov ah,35h                     ;DOS function - get vector
                 mov al,16h                     ;interrupt 16h
                 int 21h                        ;get the vector
                 mov int_16h_ptr,bx             ;save offset of vector
                 mov int_16h_ptr[2],es          ;save segment of vector
                 mov ah,25h                     ;DOS function - set vector
                 mov al,16h                     ;interrupt 16h
                 lea dx,new_int_16h             ;pointer to new routine
                 int 21h                        ;set vector
;
;Now save the old interrupt 9 vector, replace it with the new one, and exit.
;
                 mov ah,35h                     ;DOS function - get vector
                 mov al,9h                      ;interrupt 9
                 int 21h                        ;get vector
                 mov int_9h_ptr,bx              ;save offset
                 mov int_9h_ptr[2],es           ;save segment
                 mov ah,25h                     ;DOS function - set vector
                 mov al,9h                      ;interrupt 9
                 lea dx,kylock                  ;point to our freeze routine
                 int 21h                        ;set vector
;
;Save the current interrupt 5h vector and replace it with a new routine.
;
                 mov ah,35h                     ;DOS function - get vector
                 mov al,5h                      ;interrupt 5h
                 int 21h                        ;get the vector
                 mov int_5h_ptr,bx              ;save offset of vector
                 mov int_5h_ptr[2],es           ;save segment of vector
                 mov ah,25h                     ;DOS function - set vector
                 mov al,5h                      ;interrupt 5h
                 lea dx,new_int_5h              ;pointer to new routine
                 int 21h                        ;set vector
                 mov dx,(offset initialize - seg_org + 15) shr 4     ;...
                                                ;prepare DX for exit
                 mov ah,31h                     ;terminate-but-stay-resident
                 int 21h                        ;dos function call
initialize       endp
;
code             ends
                 end begin
                            
