;; ; @(#) demo_tsr.asm - Demo TSR program. ; (c) 1996 Ivan Maidanski http://ivmai.chat.ru ; Freeware program source. All rights reserved. ;; ; Language: ASM8086 (for IBM PC/AT, MS-DOS v2.0+, COM file) ; Tested with: Turbo Assembler v3.1, Turbo Link v5.1 ; Last modified: 1996-08-13 16:55:00 GMT+04:00 ;; NAME Demo_TSR ; tasm /m2 demo_tsr.asm ; tlink /t demo_tsr.obj ; Comment: when installed, it displays 'Hello, world!' on Ctrl-Alt-R. .MODEL SMALL .CODE .8086 Null LABEL NEAR ; PSP segment ORG 2Ch PSP@EnvSeg DW ? ; environment segment ORG 80h PSP@CmdLine DB 80h DUP(?) ; program command line ; RAM-resident part variables ORG 6Ch TSR@DTA LABEL BYTE ; Program start point ORG 100h Start LABEL NEAR cmp sp,OFFSET StackTop jae Start@CRef sub bp,bp ; abort program on no memory push es push bp retf Start@CRef: jmp Main ; RAM-resident part local stack EVEN TSRStackTop LABEL WORD ; RAM-resident part code ASSUME ds:NOTHING,es:NOTHING Int08hHandler PROC FAR ; Comment: Timer interrupt handler ; Expects: - ; Returns: - ; Uses: - pushf DB 9Ah ; call far Int08hHandler@Old DD ? call CheckActivity jc Int08hHandler@Ret call ActivateTSR Int08hHandler@Ret: iret Int08hHandler ENDP Int09hKeyHandler PROC FAR ; Comment: Keyboard interrupt handler (hot key checking only) ; Expects: - ; Returns: - ; Uses: - push ax in al,60h ; get key scan code sti call CheckHotKey cli je Int09hKeyHandler@Pressed pop ax jmp short Int09hHandler@Near ; pass control to the original handler Int09hKeyHandler@Pressed: in al,61h ; reset keyboard controller or al,80h out 61h,al and al,7Fh out 61h,al mov al,20h ; reset interrupt controller out 20h,al sti pop ax mov byte ptr cs:[CheckActivity@GateHKey],0 ; open hot key gate ORG $-1h nop iret Int09hKeyHandler ENDP Int09hHandler PROC FAR ; Comment: Keyboard interrupt handler (no hot key checking) ; Expects: - ; Returns: - ; Uses: - Int09hHandler@Near: ; near label for Int09hKeyHandler mov byte ptr cs:[CheckActivity@Gate09h],0 ; close Int 09h gate ORG $-1h retn pushf DB 9Ah ; call far Int09hHandler@Old DD ? mov byte ptr cs:[CheckActivity@Gate09h],0 ; open Int 09h gate ORG $-1h nop iret Int09hHandler ENDP Int10hHandler PROC FAR ; Comment: BIOS Video service handler ; Expects: - (registers passed to int 10h) ; Returns: - (registers returned by int 10h) ; Uses: - mov byte ptr cs:[CheckActivity@Gate10h],0 ; close int 10h gate ORG $-1h retn pushf DB 9Ah ; call far Int10hHandler@Old DD ? mov byte ptr cs:[CheckActivity@Gate10h],0 ; open int 10h gate ORG $-1h nop sti ; simulate IRET without popping flags ret 2h Int10hHandler ENDP Int13hHandler PROC FAR ; Comment: BIOS disk I/O service handler ; Expects: - (registers passed to int 13h) ; Returns: - (registers returned by int 13h) ; Uses: - mov byte ptr cs:[CheckActivity@Gate13h],0 ; close int 13h gate ORG $-1h retn pushf DB 9Ah ; call far Int13hHandler@Old DD ? mov byte ptr cs:[CheckActivity@Gate13h],0 ; open int 13h gate ORG $-1h nop sti ; simulate IRET without popping flags ret 2h Int13hHandler ENDP Int15hHandler PROC FAR ; Comment: Miscellaneous system services handler (if needed) ; Expects: - (registers passed int 15h) ; Returns: CF=0 if Hot key pressed (or registers returned by int 15h) ; Uses: - sti cmp ah,4Fh jne Int15hHandler@Skip push ax call CheckHotKey pop ax je Int15hHandler@Process Int15hHandler@Skip: stc ; signal BIOS to process the key if int 15h 4Fh not intercepted cli DB 0EAh ; jmp far Int15hHandler@Old DD ? Int15hHandler@Process: mov byte ptr cs:[CheckActivity@GateHKey],0 ; open hot key gate ORG $-1h nop clc ; signal BIOS to ignore the key ret 2h ; simulate IRET without popping flags Int15hHandler ENDP Int1BhHandler PROC FAR ; Comment: CTRL+BREAK handler ; Expects: - ; Returns: - ; Uses: - iret ; disable CTRL+BREAK processing Int1BhHandler@Old DD ? Int1BhHandler ENDP Int23hHandler PROC FAR ; Comment: CTRL+C handler ; Expects: - ; Returns: - ; Uses: - iret ; disable CTRL+C processing Int23hHandler@Old DD ? Int23hHandler ENDP Int24hHandler PROC FAR ; Comment: Critical error handler ; Expects: ah,al,di,bp:si ; Returns: al - action code (ignore,retry,abort,fail) ; Uses: al mov al,0 ; - ignore (for MS DOS 2.x) ORG $-1h Int24hHandler@ACode DB 3h ; - fail (for MS DOS 3.0 or later) iret Int24hHandler@Old DD ? Int24hHandler ENDP Int28hHandler PROC FAR ; Comment: DOS idle handler ; Expects: - ; Returns: - ; Uses: - pushf DB 9Ah ; call far Int28hHandler@Old DD ? mov byte ptr cs:[CheckActivity@IdleFlag],1h call CheckActivity jc Int28hHandler@Ret call ActivateTSR Int28hHandler@Ret: iret Int28hHandler ENDP Int2FhHandler PROC FAR ; Comment: Multiplex interrupt handler ; Expects: ah - ID number, al=0 ; Returns: ah - ID number, al=1h, es:[di] - ID string ; Uses: - (al,di,es) cmp ah,0 ORG $-1h Int2FhHandler@IDNumber DB ? je Int2FhHandler@Check push ax mov ax,word ptr cs:[Int2FhHandler@Old] ; check for null entry pointer or ax,word ptr cs:[Int2FhHandler@Old+2h] pop ax jz Int2FhHandler@Ret DB 0EAh ; jmp far Int2FhHandler@Old DD ? Int2FhHandler@Check: cmp al,0 jne Int2FhHandler@Ret mov di,cs mov es,di inc ax ; indicate that ID number taken mov di,OFFSET Int2FhHandler@IDStr Int2FhHandler@Ret: iret Int2FhHandler@IDStr DB 'TSR demo v2.0 (c) 1996',0 Int2FhHandler@IDStrLen= $-Int2FhHandler@IDStr Int2FhHandler ENDP CheckHotKey PROC NEAR ; Comment: Check if hot key pressed ; Expects: al - scan code ; Returns: ZF - key pressed ; Uses: ax cmp al,0 ORG $-1h CheckHotKey@Code DB 0 ; for none jne CheckHotKey@Ret push ds sub ax,ax mov ds,ax mov al,ds:[CheckHotKey@FlagsOfs] ; get keyboard flags pop ds test al,3h ; Shift key? jz CheckHotKey@NoShift or al,3h ; set both of the Shift keys pressed CheckHotKey@NoShift: and al,0Eh ; only Shift,Ctrl and Alt keys cmp al,0 ORG $-1h CheckHotKey@Status DB ? CheckHotKey@Ret: ret CheckHotKey@FlagsOfs= 417h ; (segment=0) CheckHotKey ENDP CheckActivity PROC NEAR ; Comment: Check TSR activate and busy flags, InDOS flag, ; BIOS busy flags and hardware status ; Expects: CheckActivity@IdleFlag - is set if called from idle handler ; Returns: CF - System busy (or TSR active flag is on) ; Uses: - cli ; prevent this procedure from being reentered while executing stc CheckActivity@GateHKey DB ? ; TSR hot key pressed flag ORG $-1h retn ; hot key gates closed CheckActivity@GateAct DB ? ; TSR active flag ORG $-1h nop ; TSR is passive now CheckActivity@Gate09h DB ? ORG $-1h nop ; keyboard interrupt gates opened CheckActivity@Gate10h DB ? ORG $-1h nop ; video service gates opened CheckActivity@Gate13h DB ? ORG $-1h nop ; disk I/O service gates opened push ax push ds mov ax,0 ; load DS with DOS segment ORG $-2h CheckActivity@DOSSeg DW ? mov ds,ax mov ah,0 mov al,ds:[0] ; 0 if no critical error ORG $-2h CheckActivity@CritErrOfs DW ? cmp ah,0 ORG $-1h CheckActivity@IdleFlag DB 0 ; called not by idle handler mov cs:[CheckActivity@IdleFlag],ah ; reset idle flag jne CheckActivity@SkipInDOS or al,ds:[0] ; 0 if DOS not busy ORG $-2h CheckActivity@InDOSOfs DW ? CheckActivity@SkipInDOS: pop ds cmp ah,al ; set CF if DOS busy jc CheckActivity@Ret mov al,0Bh ; al= 0CW3 for interrupt controller out 20h,al ; request 8259A in-service register jmp short $+2h ; wait a few cycles in al,20h ; return hardware interrupts being serviced cmp ah,al ; set CF if controller busy CheckActivity@Ret: pop ax ret CheckActivity ENDP ActivateTSR PROC NEAR ; Comment: set up environment of the TSR main part ; Expects: - ; Returns: - ; Uses: - mov byte ptr cs:[CheckActivity@GateAct],0 ; close TSR active gates ORG $-1h retn mov cs:[TSRStackTop-4h],ss mov cs:[TSRStackTop-2h],sp cli ; set up a new stack push cs pop ss mov sp,OFFSET TSRStackTop-4h sti ; enable interrupts push ax ; preserve all registers push bx push cx push dx push bp push si push di push ds push es stc ; store old and set up new error trapping handlers call SetErrTrap stc ; store old and set up new PSP and DTA regions call SetPSPDTA call TSRMain clc ; restore old PSP and DTA regions call SetPSPDTA clc ; restore old error trapping handlers call SetErrTrap pop es ; restore all registers pop ds pop di pop si pop bp pop dx pop cx pop bx pop ax cli ; restore old stack pop ss mov sp,cs:[TSRStackTop-2h] mov byte ptr cs:[CheckActivity@GateHKey],0 ; close hot key gate ORG $-1h retn mov byte ptr cs:[CheckActivity@GateAct],0 ; open TSR active gates ORG $-1h nop sti ret ActivateTSR ENDP SetErrTrap PROC NEAR ; Comment: set up new or restore old trapping handlers ; (ints 1Bh,23h,24h) and CTRL+BREAK checking ; Expects: CF=1 - set up new handlers ; Returns: - ; Uses: ax,bx,dx,ds,es jc SetErrTrap@SetUp mov ax,2524h ; restore old int 24h handler addr lds dx,cs:[Int24hHandler@Old] int 21h mov ax,2523h ; restore old int 23h handler addr lds dx,cs:[Int23hHandler@Old] int 21h mov ax,251Bh ; restore old int 1Bh handler addr lds dx,cs:[Int1BhHandler@Old] int 21h mov ax,3301h ; restore CTRL+BREAK check flag mov dl,0 ORG $-1h SetErrTrap@OldFlag DB ? int 21h jmp short SetErrTrap@Ret SetErrTrap@SetUp: push cs pop ds ; set ds=cs mov ax,3300h ; save old CTRL+BREAK check flag int 21h mov ds:[SetErrTrap@OldFlag],dl mov ax,3301h ; set CTRL+BREAK check flag off sub dl,dl int 21h mov ax,351Bh ; save old int 1Bh handler addr int 21h mov word ptr ds:[Int1BhHandler@Old],bx mov word ptr ds:[Int1BhHandler@Old+2h],es mov ax,3523h ; save old int 23h handler addr int 21h mov word ptr ds:[Int23hHandler@Old],bx mov word ptr ds:[Int23hHandler@Old+2h],es mov ax,3524h ; save old int 24h handler addr int 21h mov word ptr ds:[Int24hHandler@Old],bx mov word ptr ds:[Int24hHandler@Old+2h],es mov ax,251Bh ; set up new int 1Bh handler addr mov dx,OFFSET Int1BhHandler ; (ds=cs) int 21h mov ax,2523h ; set up new int 23h handler addr mov dx,OFFSET Int23hHandler ; (ds=cs) int 21h mov ax,2524h ; set up new int 24h handler addr mov dx,OFFSET Int24hHandler ; (ds=cs) int 21h SetErrTrap@Ret: ret SetErrTrap ENDP SetPSPDTA PROC NEAR ; Comment: set up new or restore old PSP and DTA regions ; Expects: CF=1 - set up new regions ; Returns: - ; Uses: ax,bx,dx,ds,es push cs pop ds ; set ds=cs jc SetPSPDTA@SetUp mov ah,1Ah ; restore old DTA addr lds dx,ds:[SetPSPDTA@OldDTAAddr] int 21h mov ah,50h ; restore old PSP segment mov bx,0 ORG $-2h SetPSPDTA@OldPSPSeg DW ? int 21h jmp short SetPSPDTA@Ret SetPSPDTA@OldDTAAddr DD ? SetPSPDTA@SetUp: mov ah,51h ; save current PSP segment (PID) int 21h mov ds:[SetPSPDTA@OldPSPSeg],bx mov ah,50h ; set new PSP segment (PID) mov bx,cs int 21h mov ah,2Fh ; save current DTA address int 21h mov word ptr ds:[SetPSPDTA@OldDTAAddr],bx mov word ptr ds:[SetPSPDTA@OldDTAAddr+2h],es mov ah,1Ah ; set new DTA offset (ds=cs) mov dx,OFFSET TSR@DTA int 21h SetPSPDTA@Ret: ret SetPSPDTA ENDP TSRMain PROC NEAR ; Comment: Main part of TSR (demo) ; Expects: - ; Returns: - ; Uses: ax,bx,cx,dx,bp,si,di (all) mov ah,0Fh ; get current video mode int 10h cmp al,7h ; MONO mode je TSRMain@Init cmp al,4h ; 0 .. 3 - TEXT modes jae TSRMain@Ret TSRMain@Init: mov bl,ah mov ah,3h ; save old cursor position int 10h push dx cld ; prepare for saving screen area and writing message push cs ; set ds=cs pop ds mov si,OFFSET TSRMain@HelloMsg mov dh,0Ch ; compute start cursor position mov dl,bl sub dl,10h shr dl,1h sub cx,cx mov bl,3Eh ; set video attribute - yellow/cyan TSRMain@Update: inc dl ; move to next position mov ah,2h int 10h mov ah,8h ; read character int 10h push ax lodsb test al,al jz TSRMain@Wait inc cx push cx mov cx,1h mov ah,9h ; write character int 10h pop cx jmp short TSRMain@Update TSRMain@Wait: sub ah,ah ; wait for keystroke int 16h pop ax TSRMain@Restore: dec dl ; move to previous position mov ah,2h int 10h pop ax mov bl,ah push cx mov cx,1h mov ah,9h ; write saved character int 10h pop cx loop TSRMain@Restore pop dx ; restore old cursor position mov ah,2h int 10h TSRMain@Ret: ret TSRMain@HelloMsg DB ' Hello, world! ',0 TSRMain ENDP TSRLength= $-Null ; Non-resident part code ASSUME ds:@CODE,es:NOTHING Main PROC NEAR ; Comment: - ; Expects: ds=es=cs ; Returns: - (not applicable) ; Uses: - (all) mov sp,OFFSET StackTop mov ah,4Ah ; shrink memory block mov bx,(ProgramLength+0Fh)/10h ; size of program in paragraphs int 21h jnc Main@NoShrinkErr call MemAllocErr Main@NoShrinkErr: call TestDOSVer call DisableBreak mov ah,9h ; print info mov dx,OFFSET Main@MsgInfo int 21h push ds pop es ; es=ds mov si,OFFSET Main@Options ; get all program options call AnalyzeCMDLine test al,18h ; show help information? jz Main@NoHelp mov ah,40h ; write to STDOUT sub bx,bx mov dx,OFFSET Main@HelpPage mov cx,Main@HelpPageLen int 21h jmp Main@Exit Main@NoHelp: test al,1h ; store di if -K present jnz Main@OptK mov di,OFFSET Main@KeySpec Main@OptK: push ax ; preserve options presence byte and argument offset push di call CheckIDNumber pop si pop dx mov al,dl and ah,ah ; TSR installed? jnz Main@Already test al,2h ; -U ? jnz Main@ErrNot test al,4h ; -I ? jnz Main@Inst test al,1h ; -K ? jz Main@ExitCRef Main@ErrNot: mov ah,9h ; print error message mov dx,OFFSET Main@MsgNot int 21h mov ax,4C01h int 21h Main@Inst: call TransKeyCode mov [CheckHotKey@Code],al mov [CheckHotKey@Status],ah call GetIDNumber mov [Int2FhHandler@IDNumber],ah call GetDOSFlags mov [CheckActivity@DOSSeg],es mov [CheckActivity@CritErrOfs],bx mov [CheckActivity@InDOSOfs],dx call FreeEnvSeg call StoreOldInts call SetNewInts mov ah,9h ; print OK message mov dx,OFFSET Main@MsgOK int 21h mov ax,3100h ; terminate but stay resident mov dx,(TSRLength+0Fh)/10h int 21h Main@Already: test al,2h ; -U ? jz Main@ChangeKey call CheckInts call RestoreInts mov ah,49h int 21h jnc Main@NoFreeErr call MemAllocErr Main@NoFreeErr: mov ah,9h ; print OK message mov dx,OFFSET Main@MsgUn int 21h Main@ExitCRef: ; cross reference on exit jmp short Main@Exit Main@ChangeKey: test al,1h ; -K ? jz Main@Status call TransKeyCode ; change hot key mov es:[CheckHotKey@Code],al mov es:[CheckHotKey@Status],ah mov ah,9h ; print Change Key message mov dx,OFFSET Main@MsgChKey int 21h Main@Status: mov ah,9h ; print Status info mov dx,OFFSET Main@MsgStatus int 21h Main@Exit: mov ax,4C00h int 21h Main@Options DB '?HIUK',0 Main@KeySpec DB 'CAR',0 ; Default: Ctrl+Alt+R Main@MsgInfo DB 'Demo TSR v2.0',0Dh,0Ah DB ' Type DEMO_TSR /? for help.',0Dh,0Ah,'$' Main@MsgOK DB 'TSR installed successfully.',0Dh,0Ah,'$' Main@MsgUn DB 'TSR removed from memory successfully.',0Dh,0Ah,'$' Main@MsgChKey DB 'Hot key changed. $' Main@MsgStatus DB 'TSR installed.',0Dh,0Ah,'$' Main@MsgNot DB 'Error: TSR not installed.',0Dh,0Ah,'$' Main@HelpPage DB 0Dh,0Ah DB 'DEMO_TSR [-H] [-I] [-U] [-K[key]]',0Dh,0Ah DB 0Dh,0Ah,'Options:',0Dh,0Ah DB ' -H ', ' Display this help page',0Dh,0Ah DB ' -I ', ' Install program (as a TSR)',0Dh,0Ah DB ' -U ', ' Uninstall program',0Dh,0Ah DB ' -K[key] ', ' Set hot key (see below)',0Dh,0Ah DB 0Dh,0Ah DB 'Default hot key: Ctrl+Alt+R',0Dh,0Ah DB 'Hot Key specification: [S][C][A]key',0Dh,0Ah DB ' Where: S - Shift, C - Ctrl, A - Alt',0Dh,0Ah DB ' Valid keys:',0Dh,0Ah DB ' ',' _(for space bar),A..Z,0..9,~+\;.',0Dh,0Ah DB ' ',' F1..F12,K0..K9,K+,K-,K*,K. (keypad)',0Dh,0Ah DB ' ',' X0xx (hexadecimal key definition)',0Dh,0Ah DB 0Dh,0Ah Main@HelpPageLen= $-Main@HelpPage Main ENDP TestDOSVer PROC NEAR ; Comment: Tests for MS-DOS 2.0 or later ; Expects: ds=cs=PSP segment ; Returns: - (may not be applicable) ; Uses: ax,bx,cx mov ax,3000h ; get DOS version (al=0) int 21h cmp al,2h jae TestDOSVer@Ret mov ah,9h mov dx,OFFSET TestDOSVer@Msg int 21h int 20h ; CS must be the same as the PSP TestDOSVer@Msg DB 'Requires MS-DOS 2.0 or later.',0Dh,0Ah,'$' TestDOSVer@Ret: ret TestDOSVer ENDP DisableBreak PROC NEAR ; Comment: disable aborting program by int 23h,24h handlers ; Expects: ds=cs ; Returns: - ; Uses: ax,bx,cx,dx mov ax,2523h ; set up empty int 23h handler ; CTRL+C mov dx,OFFSET DisableBreak@CtrlC ; (ds=cs) int 21h inc ax ; set up new int 24h handler ; Critical error mov dx,OFFSET DisableBreak@CritErr ; (ds=cs) int 21h mov ah,30h ; set "ignore" action code for MS-DOS 2.x int 21h cmp al,3h jae DisableBreak@Ret mov [Int24hHandler@ACode],0 DisableBreak@Ret: ret DisableBreak@CritErr: mov al,0 ; - ignore DisableBreak@CtrlC: iret DisableBreak ENDP COMMENT # TestLoadHigh PROC NEAR ; Comment: Tests for program being loaded into UMBs ; Expects: ds=cs ; Returns: - (may not be applicable) ; Uses: ax mov ax,cs cmp ax,0A000h jb TestLoadHigh@Ret mov ah,9h mov dx,OFFSET TestLoadHigh@Msg int 21h mov ax,4C03h ; abort loaded high program int 21h TestLoadHigh@Msg DB 'Do not use LOADHIGH or LH commands' DB ' to load this program.',0Dh,0Ah,'$' TestLoadHigh@Ret: ret TestLoadHigh ENDP # FreeEnvSeg PROC NEAR ; Comment: Frees program environment segment ; Expects: ds=cs ; Returns: - (may not be applicable) ; Uses: ax,bx,es mov ah,51h ; get PSP segment int 21h mov es,bx sub ax,ax xchg ax,es:[PSP@EnvSeg] and ax,ax ; check for presence of PSP jz FreeEnvSeg@Ret mov es,ax mov ah,49h int 21h jnc FreeEnvSeg@Ret call MemAllocErr FreeEnvSeg@Ret: ret FreeEnvSeg ENDP COMMENT # AllocMemory PROC NEAR ; Comment: allocate necessary memory preffering upper memory blocks ; Expects: ds=cs, dx - memory size (in paragraphs), ; CF - Use UMBs if possible ; Returns: ax - segment of the allocated memory, if ax>=0A000h then ; memory allocated into UMBs (or not applicable if not enough memory) ; Uses: ax,bx,cx,dx,si rcl si,1h ; preserve CF mov ah,30h ; query DOS version int 21h cmp al,3h ; test for versions 2.x jb AllocMemory@Simple rcr si,1h ; check "use UMBs" flag jnc AllocMemory@Alloc cmp al,5h ; test for version 5.0 or later jb AllocMemory@CMC mov ax,5802h ; query upper memory link state int 21h mov cl,al ; preserve link state mov ax,5803h ; link upper memory and check for errors mov bx,1h int 21h AllocMemory@CMC: cmc AllocMemory@Alloc: rcl ch,1h ; preserve "use UMBs" flag mov ax,5800h ; query memory allocation strategy int 21h mov si,ax ; preserve strategy mov bx,1h ; use "best fit low" strategy (by default) test ch,1h ; check "use UMBs" flag jz AllocMemory@NoUMBs or bl,80h ; add "use UMBs first" strategy AllocMemory@NoUMBs: mov ax,5801h ; set new memory allocation strategy int 21h jc AllocMemory@Skip mov ah,48h ; allocate conventional/upper memory block mov bx,dx int 21h AllocMemory@Skip: mov dx,ax ; preserve segment or error code rcl ch,1h ; preserve error flag mov ax,5801h ; restore old strategy mov bx,si int 21h test ch,2h ; check "use UMBs" flag jz AllocMemory@Check mov ax,5803h ; restore old upper memory link state sub bx,bx mov bl,cl int 21h AllocMemory@Check: mov ax,dx rcr ch,1h ; check error flag jnc AllocMemory@Ret AllocMemory@Err: call MemAllocErr AllocMemory@Simple: ; for MS DOS 2.x mov ah,48h ; allocate memory block mov bx,dx int 21h jc AllocMemory@Err AllocMemory@Ret: ret AllocMemory ENDP # MemAllocErr PROC NEAR ; Comment: Print memory error message and abort program ; Expects: ds=cs, ax - error code (7h,8h or 9h) ; Returns: - (not applicable) ; Uses: ax,dx mov dx,OFFSET MemAllocErr@MsgBad cmp al,9h je MemAllocErr@Print mov dx,OFFSET MemAllocErr@MsgNoM cmp al,8h je MemAllocErr@Print mov dx,OFFSET MemAllocErr@MsgMCB MemAllocErr@Print: mov ah,9h ; print message int 21h mov ax,4C04h ; abort program int 21h MemAllocErr@MsgMCB DB 'Error: Memory Control Block damaged.',0Dh,0Ah,'$' MemAllocErr@MsgNoM DB 'Not enough memory to allocate data.',0Dh,0Ah,'$' MemAllocErr@MsgBad DB 'Cannot free memory block.',0Dh,0Ah,'$' MemAllocErr ENDP AnalyzeCMDLine PROC NEAR ; Comment: get options presence word ; Expects: ds:[si] - options letters string (in upper case), es - PSP seg ; Returns: ax - options presence word, es:[di] - last option argument ; Uses: ax,bx,si,di cld sub bx,bx ; initialize flags lodsb AnalyzeCMDLine@Loop: mov ah,al call GetOption rcl bx,1h ; store presence flag lodsb and al,al ; end of string? jnz AnalyzeCMDLine@Loop mov ax,bx ret AnalyzeCMDLine ENDP GetOption PROC NEAR ; Comment: get option (/X or -X, case insensitive) ; Expects: ah - letter for which to scan (in upper case), es - PSP seg ; Returns: CF - "found" flag, es:[di] - found option argument ; Uses: al,di mov di,OFFSET PSP@CmdLine mov al,es:[di] ; get command line length and al,al ; clear "found" flag (CF=0) jz GetOption@Ret ; if none, return inc di GetOption@Loop: mov al,es:[di] inc di cmp al,' ' ; whitespace or end of command line? je GetOption@Loop cmc ; clear "found" flag if (al<' ') jnc GetOption@Ret cmp al,'/' je GetOption@Analyze cmp al,'-' jne GetOption@Skip GetOption@Analyze: mov al,es:[di] inc di cmp al,' ' ; whitespace or end of command line? je GetOption@Loop cmc ; clear "found" flag if (al<' ') jnc GetOption@Ret cmp al,'a' ; check for lower case jb GetOption@Upper cmp al,'z' ja GetOption@Upper sub al,'a'-'A' ; convert lower to upper case symbol GetOption@Upper: cmp al,ah stc ; set "found" flag je GetOption@Ret GetOption@Skip: mov al,es:[di] inc di cmp al,' ' ; whitespace or end of command line? je GetOption@Loop cmc ; clear "found" flag if (al<' ') jc GetOption@Skip GetOption@Ret: ret GetOption ENDP TransKeyCode PROC NEAR ; Comment: translate specification string into hot key scan-code ; and keyboard status bytes (and store them into TSR) ; Expects: ds=cs, si - string offset ; Returns: al - scan-code (0 for none), ah - keyboard status byte ; (or may not be applicable) ; Uses: ax,bx,si cld lodsb cmp al,' ' ; check for empty specification mov bl,al ; preserve symbol mov ax,0 ; al=0 for none, ah - keyboard flags jbe TransKeyCode@RetCRef xchg al,bl ; bl=0 TransKeyCode@Loop: cmp al,7Fh ; check upper boundary jae TransKeyCode@ErrCRef cmp al,'a' ; check for lower case jb TransKeyCode@Upper cmp al,'z' ja TransKeyCode@Upper sub al,'a'-'A' ; convert lower to upper case symbol TransKeyCode@Upper: and bl,bl ; is it the first symbol? jz TransKeyCode@Cont cmp bl,'X' ; hexadecimal key code definition? je TransKeyCode@Hex cmp bl,'K' ; is it a symbol on keypad? je TransKeyCode@Keypad cmp bl,'F' ; is it a function key? je TransKeyCode@Func mov bh,8h cmp bl,'A' ; Alt key? je TransKeyCode@Status shr bh,1h cmp bl,'C' ; Ctrl key? je TransKeyCode@Status shr bh,1h cmp bl,'S' ; Shift key? clc ; for the cross reference jne TransKeyCode@ErrCRef TransKeyCode@Status: test ah,bh ; CF=0 jnz TransKeyCode@ErrCRef ; this keyboard flag has already been set or ah,bh TransKeyCode@Cont: mov bl,al ; preserve current symbol lodsb cmp al,' ' ; end of specification? (use '_' to define space bar!) ja TransKeyCode@Loop mov al,bl cmp al,'{' ; skip 'a'..'z' letters in the table jb TransKeyCode@NoShift sub al,'{'-'a' TransKeyCode@NoShift: sub al,' ' ; shift ASCII code mov bx,OFFSET TransKeyCode@Table ; convert ASCII to key scan-code xlatb TransKeyCode@RetCRef: ; cross reference on return jmp TransKeyCode@Ret TransKeyCode@Hex: sub bl,bl TransKeyCode@NextHex: cmp al,'0' ; check lower boundary jb TransKeyCode@Err cmp al,'9' ; check upper decimal digit boundary jbe TransKeyCode@Digit and al,NOT ('a'-'A') ; convert lower to upper case symbol cmp al,'A' ; check lower hexadecimal digit boundary jb TransKeyCode@Err cmp al,'F' ; check upper boundary ja TransKeyCode@Err sub al,'A'-'9'-1h TransKeyCode@Digit: sub al,'0' ; convert ASCII symbol to hexadecimal value or bl,al ; add digit lodsb cmp al,' ' ; check for the end of specification xchg al,bl jbe TransKeyCode@RetCRef cmp al,10h ; high digit? TransKeyCode@ErrCRef: ; cross reference on error (CF=0) jae TransKeyCode@Err shl al,1h ; multiplay by 10h shl al,1h shl al,1h shl al,1h xchg al,bl ; preserve value jmp short TransKeyCode@NextHex TransKeyCode@Keypad: cmp al,'9' ; check upper boundary ja TransKeyCode@Err sub al,'*' ; shift ASCII code and check lower boundary jb TransKeyCode@Err mov bx,OFFSET TransKeyCode@KpTable xlatb and al,al jz TransKeyCode@Err jmp short TransKeyCode@Last ; al!=3Bh TransKeyCode@Func: cmp al,'1' ; check lower boundary jb TransKeyCode@Err cmp al,'9' ; check upper boundary ja TransKeyCode@Err add al,3Bh-'1' ; convert ASCII to functional key scan-code TransKeyCode@Last: mov bl,al ; preserve scan-code lodsb cmp al,' ' ; check for the end of specification xchg al,bl jbe TransKeyCode@RetCRef cmp al,3Bh ; check for the specification of the F10,F11 or F12 keys jne TransKeyCode@Err mov al,44h ; F10 key? cmp bl,'0' je TransKeyCode@Last jb TransKeyCode@Err mov al,58h ; F12 key? cmp bl,'2' je TransKeyCode@Last dec ax ; dec al (because al>0), CF remain unchanged jb TransKeyCode@Last ; F11 key? TransKeyCode@Err: mov dx,OFFSET TransKeyCode@Msg mov ah,9h ; print error message int 21h mov ax,4C05h ; abort program int 21h TransKeyCode@Msg DB 'Error: Invalid hot key specification.',0Dh,0Ah,'$' TransKeyCode@Table DB 0,2h,28h,4h,5h,6h,8h,28h ; _!"#$%&' DB 0Ah,0Bh,9h,0Dh,33h,0Ch,34h,35h ; ()*+,-./ DB 0Bh,2h,3h,4h,5h,6h,7h,8h ; 01234567 DB 9h,0Ah,27h,27h,33h,0Dh,34h,35h ; 89:;<=>? DB 3h,1Eh,30h,2Eh,20h,12h,21h,22h ; @ABCDEFG DB 23h,17h,24h,25h,26h,32h,31h,18h ; HIJKLMNO DB 19h,10h,13h,1Fh,14h,16h,2Fh,11h ; PQRSTUVW DB 2Dh,15h,2Ch,1Ah,2Bh,1Bh,7h,39h ; XYZ[\]^ DB 29h,1Ah,2Bh,1Bh,29h ; `{|}~ TransKeyCode@KpTable DB 37h,4Eh,0,4Ah,53h,0,52h,4Fh ; *+,-./01 DB 50h,51h,4Bh,4Ch,4Dh,47h,48h,49h ; 23456789 TransKeyCode@Ret: ret TransKeyCode ENDP CheckIDNumber PROC NEAR ; Comment: Check whether TSR already installed ; Expects: ds=cs ; Returns: ah - ID number (or ah=0 if TSR not installed), ; es - TSR segment (if ah>0) ; Uses: ax,bx,cx,dx,si,di,es mov ax,352Fh ; check whether any int 2Fh handlers installed int 21h mov ax,es or ax,bx jz CheckIDNumber@Ret ; if int 2Fh entry is null (in DOS 2.x) mov bx,0C001h CheckIDNumber@Loop: sub ax,ax mov es,ax ; es=0 mov ah,bh ; ah - checked ID number, al=0 push bx ; preserve bx int 2Fh pop bx cmp ax,bx ; check whether al=1h jne CheckIDNumber@Cont mov ax,es and ax,ax ; check whether es=0 jz CheckIDNumber@Cont cld mov si,OFFSET Int2FhHandler@IDStr mov cx,Int2FhHandler@IDStrLen repe cmpsb ; compare ID strings (DS:SI and ES:DI) je CheckIDNumber@Ret CheckIDNumber@Cont: inc bh jnz CheckIDNumber@Loop CheckIDNumber@Ret: mov ax,bx ; ah - ID number ret CheckIDNumber ENDP GetIDNumber PROC NEAR ; Comment: Get free ID number ; Expects: ds=cs ; Returns: ah - ID number (or not applicable if all ID numbers reserved) ; Uses: ax,bx,cx,dx,si,di,es mov ax,352Fh ; check whether any int 2Fh handlers installed int 21h mov ax,es or ax,bx mov ax,0C000h ; ID=0C0h is free if ZF=1 jz GetIDNumber@Ret ; if int 2Fh entry is null (in DOS 2.x) mov bx,ax GetIDNumber@Loop: mov ax,bx ; ah - checked ID number, al=0 push bx ; preserve bx int 2Fh pop bx cmp ax,bx ; check whether ID number reserved je GetIDNumber@Ret inc bh jnz GetIDNumber@Loop mov dx,OFFSET GetIDNumber@Msg mov ah,9h int 21h mov ax,4C03h ; abort program int 21h GetIDNumber@Msg DB 'Error: No ID numbers available.',0Dh,0Ah,'$' GetIDNumber@Ret: ret GetIDNumber ENDP GetDOSFlags PROC NEAR ; Comment: get (and store into TSR) InDOS and ; Critical Error flags addresses ; Expects: ds=cs ; Returns: es:[bx] - CritErr flag, es:[dx] - InDOS flag ; (or may not be applicable if cannot find the addresses) ; Uses: ax,bx,cx,dx,di,es mov ah,34h ; get InDOS flag addr int 21h mov dx,bx mov ah,30h ; get DOS version int 21h cmp al,0Ah ; test for OS/2 system jae GetDOSFlags@Scan xchg al,ah cmp ax,30Ah ; test for MS-DOS version 3.10 jb GetDOSFlags@Scan mov bx,dx ; if MS-DOS 3.10 or later and not OS/2 compatibility mode dec bx ; then CritErrOfs=InDOSOfs-1 jmp short GetDOSFlags@Ret GetDOSFlags@Scan: sub di,di ; prepare to scan through the DOS segment mov cx,di ; to find an "int 28h" instruction in a specific context dec cx ; cx - maximum bytes to scan cld GetDOSFlags@Search: mov ax,0 ; load opcode for "int 28h" ORG $-2h int 28h GetDOSFlags@Loop: repne scasb ; scan for the first byte of the opcode je GetDOSFlags@Check mov dx,OFFSET GetDOSFlags@Msg mov ah,9h ; print error message int 21h mov ax,4C03h ; abort program int 21h GetDOSFlags@Msg DB 'Error: Cannot find MS-DOS critical' DB ' error flag.',0Dh,0Ah,'$' GetDOSFlags@Check: cmp ah,es:[di] ; check the second byte to match jne GetDOSFlags@Loop mov ax,0 ; load main opcode for "cmp byte ptr ss:[CritErrFlag],0" ORG $-2h cmp byte ptr ds:[0],0 ; short form (no SEG prefix) ORG $-3h ; skip address and immediate parts cmp ax,es:[di-8h] ; addr of "cmp byte ptr [CritErrFlag],0" mov bx,es:[di-6h] ; addr of CritErr flag je GetDOSFlags@Ret ; check for the first context mov ax,0 ; load main opcode for "test byte ptr ?s:[CritErrFlag],0FFh" ORG $-2h test byte ptr ds:[0],0FFh ; short form (no SEG prefix) ORG $-3h ; skip address and immediate parts cmp ax,es:[di-0Dh] ; addr of "test byte ptr [CritErrFlag],0FFh" mov bx,es:[di-0Bh] ; addr of CritErr flag jne GetDOSFlags@Search ; check for the second context GetDOSFlags@Ret: ret GetDOSFlags ENDP CheckKbdFunc PROC NEAR ; Comment: check for int 15h func 4Fh ; (keyboard intercept) being supported ; Expects: - ; Returns: CF - keyboard intercept supported ; Uses: ax,bx,es mov ah,0C0h ; get system configuration (if possible) int 15h sti ; Compaq ROM may leave interrupts disabled cmc jnc CheckKbdFunc@Ret ; not successful and ah,ah ; CF=0 jnz CheckKbdFunc@Ret ; the services are not supported mov al,es:[bx+5h] ; test features byte test al,10h ; CF=0 jz CheckKbdFunc@Ret stc ; intercept is implemented CheckKbdFunc@Ret: ret CheckKbdFunc ENDP StoreOldInts PROC NEAR ; Comment: store old handlers for ints 8h,9h,10h,13h,15h,28h,2Fh ; Expects: ds=cs ; Returns: - ; Uses: ax,bx,es mov ax,352Fh ; store old int 2Fh handler addr int 21h mov word ptr [Int2FhHandler@Old],bx mov word ptr [Int2FhHandler@Old+2h],es mov ax,3510h ; store old int 10h handler addr int 21h mov word ptr [Int10hHandler@Old],bx mov word ptr [Int10hHandler@Old+2h],es mov ax,3513h ; store old int 13h handler addr int 21h mov word ptr [Int13hHandler@Old],bx mov word ptr [Int13hHandler@Old+2h],es mov ax,3508h ; store old int 8h handler addr int 21h mov word ptr [Int08hHandler@Old],bx mov word ptr [Int08hHandler@Old+2h],es mov ax,3528h ; store old int 28h handler addr int 21h mov word ptr [Int28hHandler@Old],bx mov word ptr [Int28hHandler@Old+2h],es mov ax,3509h ; store old int 9h handler addr int 21h mov word ptr [Int09hHandler@Old],bx mov word ptr [Int09hHandler@Old+2h],es mov ax,3515h ; store old int 15h handler addr int 21h mov word ptr [Int15hHandler@Old],bx mov word ptr [Int15hHandler@Old+2h],es ret StoreOldInts ENDP SetNewInts PROC NEAR ; Comment: set up new handlers for ints 8h,9h,10h,13h,15h,28h,2Fh ; Expects: ds=cs ; Returns: - ; Uses: ax,bx,dx,es call CheckKbdFunc mov ax,2509h ; set up new int 9h handler mov dx,OFFSET Int09hKeyHandler jnc SetNewInts@NoMsc mov dx,OFFSET Int09hHandler int 21h mov ax,2515h ; set up new int 15h handler mov dx,OFFSET Int15hHandler SetNewInts@NoMsc: int 21h mov ax,252Fh ; set up new int 2Fh handler mov dx,OFFSET Int2FhHandler int 21h mov ax,2510h ; set up new int 10h handler mov dx,OFFSET Int10hHandler int 21h mov ax,2513h ; set up new int 13h handler mov dx,OFFSET Int13hHandler int 21h mov ax,2508h ; set up new int 8h handler mov dx,OFFSET Int08hHandler int 21h mov ax,2528h ; set up new int 28h handler mov dx,OFFSET Int28hHandler int 21h ret SetNewInts ENDP CheckInts PROC NEAR ; Comment: check current handlers for ints 8h,9h,10h,13h,15h,28h,2Fh ; Expects: ds=cs, es - TSR segment ; Returns: - (may not be applicable) ; Uses: ax,bx,cx,dx mov dx,es ; preserve ES mov ax,352Fh ; check current int 2Fh handler int 21h mov ax,es cmp ax,dx jne CheckInts@Err cmp bx,OFFSET Int2FhHandler jne CheckInts@Err mov ax,3510h ; check current int 10h handler int 21h mov ax,es cmp ax,dx jne CheckInts@Err cmp bx,OFFSET Int10hHandler jne CheckInts@Err mov ax,3513h ; check current int 13h handler int 21h mov ax,es cmp ax,dx jne CheckInts@Err cmp bx,OFFSET Int13hHandler jne CheckInts@Err mov ax,3508h ; check current int 8h handler int 21h mov ax,es cmp ax,dx jne CheckInts@Err cmp bx,OFFSET Int08hHandler jne CheckInts@Err mov ax,3528h ; check current int 28h handler int 21h mov ax,es cmp ax,dx jne CheckInts@Err cmp bx,OFFSET Int28hHandler jne CheckInts@Err call CheckKbdFunc mov cx,OFFSET Int09hKeyHandler jnc CheckInts@NoMsc mov ax,3515h ; check current int 15h handler (if needed) int 21h mov ax,es cmp ax,dx jne CheckInts@Err cmp bx,OFFSET Int15hHandler jne CheckInts@Err mov cx,OFFSET Int09hHandler CheckInts@NoMsc: mov ax,3509h ; check current int 9h handler int 21h mov ax,es cmp ax,dx jne CheckInts@Err cmp bx,cx je CheckInts@Ret CheckInts@Err: mov dx,OFFSET CheckInts@Msg mov ah,9h ; print error message int 21h mov ax,4C02h ; abort program int 21h CheckInts@Msg DB 'TSR cannot be uninstalled now.',0Dh,0Ah,'$' CheckInts@Ret: mov es,dx ret CheckInts ENDP RestoreInts PROC NEAR ; Comment: restore old handlers for ints 8h,9h,10h,13h,15h,28h,2Fh ; Expects: es - TSR segment ; Returns: - ; Uses: ax,bx,dx push es ; preserve ES call CheckKbdFunc pop es push ds ; preserve DS mov ax,2515h ; restore old int 15h handler (if needed) jnc RestoreInts@NoMsc lds dx,es:[Int15hHandler@Old] int 21h RestoreInts@NoMsc: mov ax,2509h ; restore old int 9h handler lds dx,es:[Int09hHandler@Old] int 21h mov ax,252Fh ; restore old int 2Fh handler lds dx,es:[Int2FhHandler@Old] int 21h mov ax,2510h ; restore old int 10h handler lds dx,es:[Int10hHandler@Old] int 21h mov ax,2513h ; restore old int 13h handler lds dx,es:[Int13hHandler@Old] int 21h mov ax,2508h ; restore old int 8h handler lds dx,es:[Int08hHandler@Old] int 21h mov ax,2528h ; restore old int 28h handler lds dx,es:[Int28hHandler@Old] int 21h pop ds ret RestoreInts ENDP ; Program stack EVEN DB 100h DUP(?) StackTop LABEL BYTE ; End of program ProgramLength= $-Null END Start