XNUMCAPS - a 48-byte TSR

By Bücherwurm

Do you really need the capslock and numlock keys? No? I don't like them at all, actually! So I decided to write a tiny resident Dos program (TSR) that resets the status of capslock and numlock to "off" each time a key is pressed. I found that this was quite easy. A handler for the keyboard hardware interrupt which clears the capslock and numlock status bits in the BIOS data area at 40:17h (bit 5 = numlock status, bit 6 = capslock status) and then jumps to the original interrupt routine works just fine on my computer - for Dos and also for Windows 3.1! Obviously the windows keyboard handler still calls the Dos handler even though this handler is no longer needed if you don't use any Dos programs. I know nothing about the behaviour of Windows 95 in this regard.

So I decided to write a small resident program. Small? I disposed of the Dos environment segment. The program needed still about 300 bytes of memory when it was resident. Not too bad - but not very good; the interrupt handler was only 20 bytes long! The 256-byte program segment prefix was made resident, too, when I used the normal Dos functions (Interrupt 27h or function AH=31h of Interrupt 21h). Therefore I allocated a 32-byte memory block, then copied the keyboard interrupt service routine to this block. I set the interrupt 9 vector to this block. Then I patched the memory control block (MCB) of my tiny TSR. An MCB is a 16-byte structure that immediately precedes a Dos memory block. The word at offset 1 of this block is the segment address of the "owner" of the memory block. So I wrote the segment address of the resident memory block to this word - and had a separate resident program.

That's not at all a new technique. MCBs - Microsoft calls them "arena headers" - have been officially documented at least since the release of Dos 5. I patched MCBs several times before (e.g. in order to start a resident program from a basic interpreter). This is not a nice method. But it is very successful. I reduced the size of the resident code to just 32 bytes (plus the obligatory 16-byte MCB). The price is that you cannot use my program CHKTSR to unload the resident program. Actually the program has neither installation check nor unload procedure so far. Let's say I am just too lazy for that. There are several ways to handle that. Some examples: either you forget about my program. Or you change "jc mem_error" to "jmp mem_error" and reassemble the program (btw I used Borland's TASM) and link it to a .COM file. Now you can use CHKTSR on the result but you still waste a couple of hundred bytes of your RAM. Or you send a postcard with your request to me or may be even a disk with some freeware/public domain Dos software that runs on a 486 and needs neither more than standard VGA graphics (no VESA progs, please!) nor a soundcard. Or you just wait and see and hope that someone else is more active than you are. Oh well, I don't really care about this. It's totally up to you! Anyway, finally, let's have a look at the code:

; This prog is intended to switch off CapsLock and NumLock.
; The little assembly-language TSR was declared freeware
; by Robert Flogaus-Faust (Bcherwurm) on May 1, 1998.
; You may do with it what you like. But please don't sue the
; author if it doesn't work, crashes your computer, kills your
; pets or whatever unfavourable consequences it may have for you
; or someone else. You got the source code. So please read the
; code and remove the bugs before you assemble and run the program.
; If you change this program and pass it on to other people then
; you must make sure that the original version of the source
; code is included!

ORG 100h
jmp short transient
int9proc:  ;This is the resident handler for INT 9
push ds    ;Save all registers that are used
push ax
xor ax,ax
mov ds,ax  ;0 => DS
and byte ptr ds:[417h],(0ffh-60h)  ;Clear CapsLock and Numlock
                                   ;status bits at 0:417h (i.e. 40:17h)
pop ax
pop ds
db 0eah                            ;far jump to the far pointer in oldint9h
oldint9 dw 0,0ffffh                ;filled in by the program before use
                                   ;(or your computer will reboot!!)

mov ax,3000h
int 21h                            ;get dos version
cmp al,2                           ;DOS 2+ ???
jae dos_version_ok                 ;yes - continue
int 20h                            ;this should terminate the program
cmp sp,offset end_of_program+100h  ;is there enough stack?
ja enough_stack
mov dx,offset error_text
mov ah,9
int 21h                            ;show an error message
mov ax,4c01h
int 21h                            ;terminate with exit code 1
mov dx,offset copyright
mov ah,9
int 21h                            ;display the copyright
mov ax,2523h
mov byte ptr ds:[100h],0cfh        ;write an IRET to a now unused byte
mov dx,100h
int 21h                            ;and set the vector of Interrupt 23h
                                   ;(Dos-Ctrl-C-Interrupt) to it
mov ax,3509h
int 21h                            ;get vector of hardware keyboard
                                   ;interrupt 9...
mov word ptr ds:[oldint9],bx       ;...and save it...
mov word ptr ds:[oldint9+2],es     ;...to oldint9
mov ax,ds:[2ch]                    ;segment of Dos environment block
or ax,ax                           ;is it zero?
jz no_environment
mov es,ax
mov ah,49h                         ;if it is not...
int 21h                            ;...then free this memory block...
mov word ptr ds:[2ch],0            ;...and set the addy to 0
mov bx,(offset transient)-(offset int9proc)  ;the size required
                                        ;for the resident program (Bytes)
mov si,bx
add bx,15                               ;add 15 bytes because Dos can only
                                        ;allocate paragraphs
                                        ;(blocks of 16 bytes)
mov cl,4
shr bx,cl                               ;number of paragraphs required
push bx
mov ah,48h
int 21h                                 ;allocate memory for TSR
jc mem_error                            ;if that is not available then
                                        ;just make it resident the
                                        ;"normal" way!

mov es,ax                               ;the segment address
                                        ;of the TSR block

; *** This block is only necessary because MEM            ***
; *** sometimes did not get the name of the TSR right.    ***
; *** Therefore the TSR block was filled with zero bytes. ***
xor ax,ax
mov di,ax
pop cx       ;number of allocated paragraphs
shl cx,1
shl cx,1
shl cx,1     ;number of words (i.e. bytes*2)
rep stosw    ;fill the TSR block with zero

xor di,di
mov cx,si    ;Length of the TSR routine
mov si,offset int9proc
rep movsb    ;write the TSR routine to the TSR block
push es
pop ds
mov ax,ds
dec ax
mov es,ax     ;Address of MCB of the TSR block
mov es:[1],ds ;"owner" of the TSR block=the TSR block itself

mov ax,2509h
xor dx,dx
int 21h       ;set INT 9 vector to our resident program block

mov ax,cs
dec ax
mov ds,ax     ;the segment of the MCB
mov si,5
mov di,si
mov cx,11
rep movsb     ;copy last 11 bytes (including the name of the TSR for Dos 4+)
              ;from the transient code segment MCB to the MCB of the TSR
mov ax,4c00h
int 21h       ;and terminate the transient program with Exit code 0

error_text db 7,'XNUMCAPS aborted!'
             db ' Insufficient memory!',13,10,'$'
mov ax,2509h
mov dx,offset int9proc
int 21h                 ;set interrupt 9 vector to handler in TSR
mov dx,offset transient ;number of bytes of TSR
int 27h                 ;make program resident
copyright db 'XNUMCAPS - freeware TSR to switch off '
          db 'CapsLock and NumLock permanently.',13,10,13,10
          db '(C) Robert Flogaus-Faust, 1998',13,10,13,10
          db 'This program must not be distributed'
          db ' without the original source code!',13,10,13,10
          db       '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!'
          db 13,10,'!USE IS ENTIRELY AT YOUR OWN RISK!',13,10
          db       '!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!',13,10,13,10,'$'

END start_of_program