comment | This program reads a file of keystrokes into a buffer and uses them to replace keyboard input until the buffer is empty. Written for MASM 5.1 (should compile with earlier versions) Save as AUTOKEY.ASM Compile: MASM AUTOKEY; LINK AUTOKEY; (ignore stack segment warning) EXE2BIN AUTOKEY.EXE AUTOKEY.COM | CR equ 0dh ;Definitions for LF equ 0ah ; the program's use TAB equ 09h code_seg segment assume cs:code_seg, ds:code_seg, es:code_seg org 100h ;.COM file format start: jmp begin ;--------- ; Data used by the ; memory-resident portion of ; the program. ;--------- kbd_head equ 1ah kbd_tail equ 1ch kbd_start equ 80h kbd_end equ 82h int8_gate db ? old_08 dd ? old_09 dd ? save_ss dw ? save_sp dw ? dw 50h dup (?) our_stack equ $-2 buf_ptr dw offset keybuf buf_count dw 0 BUF_SIZE equ 500 ;Change to suit your needs keybuf db BUF_SIZE dup (?) ;--------- ; Keyboard Interrupt 09 hex ; While keys are being sent ; to the buffer from Autokey, ; disable the physical keyboard ; by simply ignoring all keystrokes ;--------- new_int09 proc far test cs:buf_count,-1 ;Are we sending keystrokes? jz orig_int9 ;No -- go push ax ;Else save AX in al,60h ;Read the keystroke in al,61h ;Get Port B value or al,80h ;Set high bit out 61h,al ;Send it out and al,7fh ;Clear high bit out 61h,al ;We said we received it mov al,20h ;Signal end of out 20h,al ; hardware interrupt pop ax ;Recover original AX iret ;Key is swallowed orig_int9: jmp dword ptr cs:[old_09] ;Use original code for Int 9 new_int09 endp ;--------- ; Timer Interrupt ; This is where we stuff the ; keyboard buffer ;--------- new_int08 proc far pushf ;Simulate an interrupt call call dword ptr cs:[old_08] ;Hand off to previous code test cs:buf_count,-1 ;Anything to stuff jz new8_out ;No -- leave immediately test cs:int8_gate,-1 ;Okay to execute? jz stuff_keys new8_out: iret ;Return to interrupted program stuff_keys: ;------ ; Set the gate flag. ; switch stacks, and save ; necessary registers. ;------ mov cs:int8_gate,-1 ;Don't allow recursive calls push ax ;Save caller's AX mov cs:[save_ss],ss ;Save caller's stack mov cs:[save_sp],sp cli ;Interrupts off to switch stack mov ax,cs ;Get our segment mov ss,ax ; in SS mov sp,offset our_stack ;Move to our stack and sp,0fffeh ;Make sure it's even sti ;Interrupts on now push ds ;Save caller's segments push es mov ds,ax ;Point to our segment push di ;Now save registers we need push si push bx mov ax,40h ;Segment for BIOS buffer mov es,ax ;ES ==> BIOS segment mov di,es:[kbd_head] ;Get head of buffer mov bx,es:[kbd_tail] ; and the tail cmp di,bx ;Is buffer empty? jne end_stuff ;No -- forget it mov si,[buf_ptr] ;DS:SI ==> head of our buffer lodsw ;Get next word mov [buf_ptr],si ;Save new pointer dec [buf_count] ;Count this keystroke or ax,ax ;Is keystroke null? jz end_stuff ;Yes -- don't stuff it stosw ;Else put it in buffer inc bx ;Move tail pointer inc bx cmp bx,es:[kbd_end] ;Past the end? jl dont_wrap ;No -- go mov bx,es:[kbd_start] ;Get start of key buffer dont_wrap: mov es:[kbd_tail],bx ;Save new value end_stuff: pop bx ;Clear our stack pop si pop di pop es pop ds cli ;Interrupts off to mov ss,cs:[save_ss] ; switch stacks mov sp,cs:[save_sp] pop ax ;All done mov cs:int8_gate,0 ;Let next call in jmp new8_out ;And leave new_int08 endp res_code equ $ - new_int08 res_size equ $-start ;--------- ; Installation code ; and file parsing. ; Everything from here to the end ; is discarded when the TSR is installed ;--------- ;--------- ; Transient Data ;--------- temp_buf db BUF_SIZE * 2 dup (0) hello db CR,"AUTOKEY Automatic Keyboard Entry",CR,LF,"$" bad_ver db "This program requires DOS 2.0 or later",CR,LF,"$" no_file db "Usage: AUTOKEY keyfile",CR,LF,"$" no_open db "Cannot open keystroke file",CR,LF,"$" bad_read db "Error reading keystroke file",CR,LF,"$" too_large db "Keystroke file is too larger",CR,LF,"$" installed db "Program and keystrokes installed in memory",CR,LF,"$" reloaded db "Keystroke buffer reloaded",CR,LF,"$" first_byte db ? ;For 1st of 2-byte code code_table dw "cA",1e01h ;Control keys dw "cB",3002h dw "cC",2e03h dw "cD",2004h dw "cE",1205h dw "cF",2106h dw "cG",2207h dw "cH",2308h dw "cI",1709h dw "cJ",240ah dw "cK",250bh dw "cL",260ch dw "cM",320dh dw "cN",310eh dw "cO",180fh dw "cP",1910h dw "cQ",1011h dw "cR",1312h dw "cS",1f13h dw "cT",1414h dw "cU",1615h dw "cV",2f16h dw "cW",1117h dw "cX",2d18h dw "cY",1519h dw "cZ",2c1ah dw "aA",1e00h ;Alt keys dw "aB",3000h dw "aC",2e00h dw "aD",2000h dw "aE",1200h dw "aF",2100h dw "aG",2200h dw "aH",2300h dw "aI",1700h dw "aJ",2400h dw "aK",2500h dw "aL",2600h dw "aM",3200h dw "aN",3100h dw "aO",1800h dw "aP",1900h dw "aQ",1000h dw "aR",1300h dw "aS",1f00h dw "aT",1400h dw "aU",1600h dw "aV",2f00h dw "aW",1100h dw "aX",2d00h dw "aY",1500h dw "aZ",2c00h dw "F1",3b00h ;Function keys dw "F2",3c00h dw "F3",3d00h dw "F4",3e00h dw "F5",3f00h dw "F6",4000h dw "F7",4100h dw "F8",4200h dw "F9",4300h dw "Ft",4400h dw "Fe",8500h dw "Fv",8600h dw "s1",5400h ;Shift Function keys dw "s2",5500h dw "s3",5600h dw "s4",5700h dw "s5",5800h dw "s6",5900h dw "s7",5a00h dw "s8",5b00h dw "s9",5c00h dw "st",5d00h dw "se",8700h dw "sv",8800h dw "c1",5e00h ;Control Function keys dw "c2",5f00h dw "c3",6000h dw "c4",6100h dw "c5",6200h dw "c6",6300h dw "c7",6400h dw "c8",6500h dw "c9",6600h dw "ct",6700h dw "ce",8900h dw "cv",8a00h dw "a1",6800h ;Alt function keys dw "a2",6900h dw "a3",6a00h dw "a4",6b00h dw "a5",6c00h dw "a6",6d00h dw "a7",6e00h dw "a8",6f00h dw "a9",7000h dw "at",7100h dw "ae",8b00h dw "av",8c00h dw "a!",7800h ;Alt-numbers dw "a@",7900h dw "a#",7a00h dw "a$",7b00h dw "a%",7c00h dw "a^",7d00h dw "a&",7e00h dw "a*",7f00h dw "a(",8000h dw "a)",8100h dw "t1",9800h ;Tandy 1000 F11 & F12 dw "t2",9900h dw "!1",0a200h ; (shifted) dw "!2",0a300h dw "^1",0ac00h ; (control) dw "^2",0ad00h dw "~1",0b600h ; (alt) dw "~2",0b700h dw "HM",4700h ;Home dw "UA",4800h ;Up-arrow dw "PU",4900h ;Page Up dw "LA",4b00h ;Left Arrow dw "RA",4d00h ;Right Arrow dw "EN",4f00h ;End dw "DA",5000h ;Down Arrow dw "PD",5100h ;Page down dw "IN",5200h ;Insert dw "DL",5300h ;Delete dw "CR",1c0dh ;Return/Enter dw "Tb",0f09h ;Tab dw "BK",0e08h ;Backspace dw "sb",0f00h ;Shift-Tab dw "ES",011bh ;Escape dw "P+",4e2bh ;Pad Plus dw "P-",4a2dh ;Pad Minus dw "ch",7700h ;Ctrl-home dw "cu",8400h ;Ctrl-up dw "cl",7300h ;Ctrl-left dw "cr",7400h ;Ctrl-right dw "ce",7500h ;Ctrl-end dw "cd",7600h ;Ctrl-down dw "NL",0000h ;Null code_len equ $-code_table ;Length of the table begin: lea dx,hello ;DS:DX ==> opening message mov ah,9 ;Print the message int 21h mov ah,30h ;Get DOS version int 21h cmp al,2 ;At least version 2? jae version_ok ;Yes -- go lea dx,bad_ver ;Else report error & leave jmp error version_ok: mov ax,3508h ;Get location of Int 08h int 21h mov word ptr [old_08],bx ;Save the address mov word ptr [old_08+2],es ; in ES:BX mov ax,3509h ;Get location of Int 09h int 21h mov word ptr [old_09],bx ;Save the address mov word ptr [old_09+2],es ; in ES:BX lea di,new_int09 ;Get code start address in DI mov si,di ;Copy to SI mov cx,res_code ;Bytes to compare repz cmpsb ;Are we already installed? jz read_file ;Yes -- go push ds ;Else move ES pop es ; to this segment read_file: mov es:buf_count,0 ;Turn off any output lea dx,no_file ;Ready for possible error mov si,80h ;DS:SI ==> command tail in PSP lodsb ;Get length of tail or al,al ;Anything there? jnz read_1 ;Yes -- go jmp error ;Else report error & leave read_1: lodsb ;Get a character cmp al,' ' ;Is it a space? je read_1 ;Yes -- skip it cmp al,TAB ;Is it a tab? je read_1 ;Yes -- skip it mov di,si ;Save this position dec di ;DI ==> beginning of file name read_2: lodsb ;Get next character cmp al,' ' ;At end of file name? jg read_2 ;No -- loop back dec si ;Yes -- move back to terminator mov byte ptr [si],0 ;Change terminator to NULL mov ax,di ;Get offset of beginning sub ax,si ;Check name length jnz read_3 ;Go if name exists jmp error ;Else report error & leave read_3: mov dx,di ;DS:DX ==> beginning of name mov ax,3d00h ;Open file for reading int 21h ;Try to open file jnc read_4 ;Go if file was opened lea dx,no_open ;Else report failure jmp error ; and leave read_4: mov bx,ax ;File handle in BX lea dx,temp_buf ;DS:DX ==> receiving buffer mov cx,BUF_SIZE * 2 ;Maximum bytes to read mov ah,3fh ;Read from file int 21h jnc read_5 ;No error -- continue lea dx,bad_read ;Else report error jmp error ; and leave read_5: push ax ;Save number of bytes read mov ah,3eh ;Close the file int 21h pop bx ;BX = number of bytes read cmp bx,cx ;Did we get the whole file? jl read_6 ;Yes -- go lea dx,too_large ;Else report error jmp error ; and leave read_6: add bx,offset temp_buf ;BX ==> end of input stream mov byte ptr [bx],0 ;Terminate buffer lea si,temp_buf ;DS:SI ==> temporary buffer lea di,keybuf ;ES:DI ==> key buffer mov cx,0 ;CX will count bytes mov dl,0 ;DL = in-quotes flag read_7: lodsb ;Get byte from input buffer cmp al,0 ;End of buffer? jz read_9 ;Yes -- go cmp al," " ;Less than a space? jl read_7 ;Yes -- ignore it call translate ;Else translate to scan code jz read_8 ;Go if nothing to save stosw ;Else put in key buffer inc cx ;Count the key inc cx ; twice read_8: cmp cx,BUF_SIZE * 2 ;Buffer full? jl read_7 ;No -- get some more lea dx,too_large ;Else report error jmp error ; and leave read_9: shr cx,1 ;Divide byte count by 2 lea ax,keybuf ;Get start of buffer mov es:[buf_ptr],ax ;Save start address mov es:[buf_count],cx ; and keystroke count mov ax,es ;Get buffer segment mov bx,ds ;And local segment cmp ax,bx ;Are they the same? jne exit ;No -- buffer already installed lea dx,installed ;Report success mov ah,09h ;Print a string int 21h mov ax,40h ;BIOS data segment mov es,ax ;Use ES to reach BIOS data mov ax,es:[kbd_start] ;Is value initialized? or ax,ax ;Test the value jnz kbd_ok ;Go if already set mov ax,1eh ;Else set standard keyboard mov es:[kbd_start],ax ; buffer in BIOS area mov es:[kbd_head],ax ;And empty keyboard mov es:[kbd_tail],ax mov ax,3eh ;Normal keyboard end mov es:[kbd_end],ax ;Store that address, also kbd_ok: lea dx,new_int08 ;DS:DX ==> new Int 08 routine mov ax,2508h ;Hook it into Int8 vector int 21h lea dx,new_int09 ;DS:DX ==> new Int 09 routine mov ax,2509h ;Hook it into Int9 vector int 21h mov ax,res_size ;Get size to keep add ax,10fh ;Add PSP and round up mov cl,4 ;Convert to paragraphs shr ax,cl ;AX = paragraphs to keep mov dx,ax ;Size in DX mov ax,3100h ;Keep resident program int 21h ;Back to DOS exit: lea dx,reloaded ;Report using old buffer mov ah,9 ;Print the string int 21h mov al,0 ;Return errorlevel = 0 exit1: mov ah,4ch ;Exit to DOS int 21h error: mov ah,9 ;Print error message int 21h mov al,0ffh ;Return errorlevel = 255 jmp exit1 ;Back to DOS ;--------- ;Translate from the ASCII/human ; format into the appropriate ; ASCII/scancode format for Int 16h ;Receives character from input in AL, ;Returns scan code in AX, ZeroFlag = NZ to save code ; or AX = 0, ZeroFlag = Z to not save the code ;--------- translate proc near or dl,dl ;In quotes now? jz no_quote ;No -- go cmp al,dl ;Ending quotes? jz end_quote ;Yes -- go cbw ;Else save character in AL or ax,ax ;Reset Z-flag ret end_quote: mov dl,0 ;Turn off quotes xor ax,ax ;Clear AX & set Z-flag ret no_quote: cmp al,'"' ;Beginning quote? jz start_quote ;Yes -- go cmp al,"'" ;Beginning single quote? jz start_quote ;Yes -- go test first_byte,-1 ;2nd byte of 2-byte command jnz byte_2 ;Yes -- go cmp al," " ;White space? jbe end_quote ;Yes -- skip it mov first_byte,al ;Else begin 2-byte command xor ax,ax ;Set AX = 0 & Z-flag ret start_quote: mov dl,al ;Save this quote mark xor ax,ax ;AX = 0 & Z-flag set ret byte_2: ;2nd byte of 2-byte command mov ah,first_byte ;AX = 2-byte code mov first_byte,0 ;Erase 1st byte push cx ;Save our registers push di push es push ds ;Bring ES to this segment pop es lea di,code_table ;ES:DI ==> table of codes mov cx,code_len ;Get length of table to search shr cx,1 ;Divide by 2 repne scasw ;Scan table for match jcxz no_match ;Go if no match mov ax,es:[di] ;Else get scan code pop es ;Recover registers pop di pop cx push ax ;Save code or ax,1 ;ZeroFlag = NZ pop ax ;Recover code ret no_match: pop es ;Recover registers pop di pop cx xor ax,ax ;Set Z-flag ret translate endp code_seg ends end start