COMMENT ~
 PROGRAM: DRVLIGHT.ASM
  AUTHOR: Denis Boyles
 RELEASE: Public Domain (Nov 30, 1996 third draft)

 COMPILE: Arrowsoft Assembler (MASM v3.0)
      OS: MS-DOS

 PURPOSE: creates a drive activity light out of your scroll-lock LED

 CHANGES: 1) removed INT 16 call and programmed LEDS directly (faster?)
          2) changes the LED on read/write calls only
          3) added self check/removal code
~

;--------------------------------------
;define some constants for us
;--------------------------------------

ID      EQU 0DBD0h                     ;program ID

CR      EQU 00Dh                       ;carriage return
LF      EQU 00Ah                       ;line feed

DSKWR   EQU 002h                       ;BIOS disk read? function
DSKRD   EQU 003h                       ;BIOS disk write? function

KBDAT   EQU 060h                       ;keyboard data port
KBSTAT  EQU 064h                       ;keyboard status port

;--------------------------------------
;define our default segments
;--------------------------------------

CODE SEGMENT WORD
    ASSUME CS:CODE,DS:CODE,ES:CODE,SS:CODE
    ORG 0100h

;--------------------------------------
;skip TSR code to install code
;--------------------------------------

MAIN:
    jmp     short START

;--------------------------------------
;TSR resident data
;--------------------------------------

OldDsk      dw ?                       ;and OldDsk-2 for old disk vector
OldPlx      dd ?                       ;for old INT 2Fh vector

;--------------------------------------
;NewDsk - our new BIOS INT 13h handler
;--------------------------------------

NewDsk proc far
    cmp     AH,DSKRD                   ;is a disk read being requested?
    jz      DoIt                       ;YES - then toggle the SL-LED

    cmp     AH,DSKWR                   ;NO - is a disk write requested?
    jnz     ExDsk                      ;NO - then never mind the change

DoIt:
    call    SetLED                     ;toggle the SL-LED on/off
    pushf                              ;call the original BIOS handler
    call    dword ptr CS:[OldDsk-2]
    call    SetLED                     ;toggle the SL-LED back on/off
    ret     2                          ;exit INT and save the disk flags?!

ExDsk:
    jmp     dword ptr CS:[OldDsk-2]    ;straight to BIOS on other functions
NewDsk endp

;--------------------------------------
;NewPlx - new multiplex TSR handler
;         for TSR detection
;--------------------------------------

NewPlx proc far
    cmp     AX,ID                      ;did we call ourselves?
    jnz     ExPlx                      ;NO exit INT to continue

    push    CS                         ;YES we did, save so our CS on the
    pop     ES                         ;stack to bring back as ES
    stc                                ;set carry flag to confirm ourselves
    ret     2                          ;return back and keep out flag state

ExPlx:
    jmp     dword ptr CS:[OldPlx]      ;not ourselves, jump to previous INT
NewPlx endp

;--------------------------------------
;SetLED - toggles the Scroll-Lock LED
;--------------------------------------

SetLED proc
    push    AX                         ;save used registers on the stack
    push    DS

    mov     AX,040h                    ;setup data segment to BIOS data area
    mov     DS,AX

    xor     AX,AX                      ;zero out AX, AH=0,AL=0

    xor     byte ptr DS:[017h],010h    ;toggle SL bit within key status byte
    mov     AL,DS:[017h]               ;AL = changed keyboard status byte

    test    AL,020h                    ;was Num-Lock on before?
    jz      M0                         ;NO then check next lock key

    or      AH,002h                    ;YES set bit for keyboard port

M0:
    test    AL,040h                    ;was Caps-Lock on before?
    jz      M1                         ;NO then check next lock key

    or      AH,004h                    ;YES add it's bit for keyboard port

M1:
    test    AL,010h                    ;did we turn on Scroll-Lock?
    jz      M2                         ;NO then send bits to keyboard port

    or      AH,001h                    ;YES add it's bit for keyboard port

M2:
    mov     AL,0EDh                    ;tell keyboard we want to set LEDs
    out     KBDAT,AL                   ;send function to keyboard

M3:
    in      AL,KBSTAT                  ;read keyboard status
    test    AL,002h                    ;is the keyboard ready?
    jnz     M3                         ;NO so loop until it is
    mov     AL,AH                      ;transfer combined LED bits to AL
    out     KBDAT,AL                   ;send LED bits to keyboard to set

M4:
    in      AL,KBSTAT                  ;read keyboard status
    test    AL,002h                    ;is the keyboard ready?
    jnz     M4                         ;NO so loop until it is

    pop     DS                         ;restored saved registers from stack
    pop     AX
    ret                                ;return to caller
SetLED endp

;--------------------------------------
;program starts here for setup
;--------------------------------------

START:
    mov     AX,ID                      ;check if our program is already
    int     02Fh                       ;resident by calling INT 2Fh with ID

    jc      RemoveTSR                  ;carry flag set? YES remove ourselves

    mov     AX,DS:[02Ch]               ;get environment segment of program
    mov     ES,AX                      ;from PSP and store in ES
    mov     AH,049h                    ;DOS free memory block
    int     021h                       ;call DOS to free the environment

    mov     AX,03513h                  ;DOS get vector for INT 13h
    int     021h                       ;call DOS to get the vector

    mov     [OldDsk-2],BX              ;store offset of vector in memory
    mov     [OldDsk],ES                ;store segment of vector in memory

    mov     AX,0352Fh                  ;DOS get vector for INT 2Fh
    int     021h                       ;call DOS to get vector

    mov     word ptr [OldPlx],BX       ;store offset of vector in memory
    mov     word ptr [OldPlx+2],ES     ;store segment of vector in memory

    mov     AX,02513h                  ;DOS set vector for new INT 13h
    mov     DX,offset NewDsk           ;DS:DX -> new interrupt handler
    int     021h                       ;call DOS to set new handler vector

    mov     AX,0252Fh                  ;DOS set vector for new INT 2fh
    mov     DX,offset NewPlx           ;DS:DX -> new interrupt handler
    int     021h                       ;call DOS to set new handler vector

    mov     AH,009h                    ;DOS print an ASCIID string
    mov     DX,offset msg0             ;DS:DX -> string to print
    int     021h                       ;call DOS to print title message

    mov     DX,offset START            ;everything upto START is kept
    mov     CL,004h                    ;in memory, so convert bytes to
    shr     DX,CL                      ;paragraphs. 1p = 16 bytes
    inc     DX                         ;plus one more to even it out

    mov     AX,03100h                  ;DOS TSR program with code (0)
    int     021h                       ;call DOS to leave resident

RemoveTSR:                             ;On ID check ES = program's CS
    push    DS                         ;save DS register to stack

    lds     DX,dword ptr ES:[OldDsk-2] ;DS:DX -> old vector for INT 1Ch
    mov     AX,02513h                  ;DOS set vector for old INT 1Ch
    int     021h                       ;call DOS to restore INT 1Ch vector

    lds     DX,ES:[OldPlx]             ;DS:DX -> old vector for INT 2Fh
    mov     AX,0252Fh                  ;DOS set vector for old INT 2Fh
    int     021h                       ;call DOS to restore INT 2Fh vector

    pop     DS                         ;restore our saved DS register

    mov     AH,049h                    ;DOS free memory block @ ES
    int     021h                       ;call DOS to free our TSR memory

    mov     AH,009h                    ;DOS print an ASCIID string
    mov     DX,offset msg1             ;DS:DX -> string to print
    int     021h                       ;call DOS to print our removed message

    mov     AX,04C00h                  ;DOS terminate program with code (0)
    int     021h                       ;call DOS to end our program

msg0        db 'DRVLIGHT - The Scroll-Lock disk drive activity LED'
            db CR,LF,'installed!',CR,LF,'$'
msg1        db 'DRVLIGHT removed!',CR,LF,'$'

CODE ENDS
END MAIN
