PAGE 80,132 TITLE "StuffIt, Delayed keyboard stuffer. (C) Terje Mathisen 1989-90" Version EQU 301h VerStr EQU '3.01' LOCALS TicksPrDay EQU 1573041 TicksPrHour EQU 65543 BIOS SEGMENT AT 40h ORG 1Ah BufferHead dw ? BufferTail dw ? BufferStart dw 16 dup (?) BufferEnd LABEL word ORG 49h VideoMode db ? CrtWidth dw ? ORG 4Eh CurrStart dw ? Cursor dw ? ORG 6Ch BIOS_Timer dw 2 dup (?) BIOS ENDS BOOT SEGMENT AT 0F000h ORG 0FFF0h RebootLocation LABEL FAR BOOT ENDS REBOOT_CODE EQU 0 ATTIME_CODE EQU 1 DELTATIME_CODE EQU 2 FIND_CODE EQU 3 PROMPT_CODE EQU 4 EXTENDED_CODE EQU 255 GETWORD_CODE EQU 254 CODE SEGMENT PARA PUBLIC 'code' ASSUME CS:CODE,DS:NOTHING,ES:NOTHING ORG 0 PspStart label byte ORG 5Ch ResidentSize dw ? ;LowStart label byte ORG 80h CommandLen db ? CommandLine LABEL BYTE ORG 100h start: jmp init Semafor equ 'ST' ; Short for STuffit LowStart label byte HighStart label byte MoveDown EQU HighStart - LowStart Int2F proc far cmp ax,0E000h jne @@chain cmp dx,Semafor ; Be safe, insist on semafor in DX je @@We_Are_Here @@chain: ; jmp [OldInt2F] db 0EAh OldInt2F dd ? @@We_Are_Here: mov al,0FFh mov dx,cs mov bx,Version iret Int2F endp PUSH_AX_OPCODE EQU 50h IRET_OPCODE EQU 0CFh MyTimer PROC FAR pushf ; call [OldTimer] db 09Ah OldTimer dw ?,? SelfModify label byte ; This will be IRET when idle push ax ; PUSH AX = 50h, IRET = 0CFh inc byte ptr [cs:active-MoveDown] ; INC from -1 to 0 jnz Already_Active STI CLD push bx push cx push dx push si push di push ds push es push cs pop ds ASSUME DS:CODE mov es,[BiosSeg-MoveDown] ASSUME ES:BIOS call word ptr [StuffMode-MoveDown] pop es pop ds pop di pop si pop dx pop cx pop bx ASSUME CS:CODE,DS:NOTHING,ES:NOTHING Already_Active: CLI dec byte ptr [cs:active-MoveDown] pop ax iret MyTimer ENDP ASSUME CS:CODE,DS:CODE,ES:BIOS StuffFinished: mov [SelfModify-MoveDown],IRET_OPCODE ret NextKey proc near mov si,[StuffPtr-MoveDown] GetNext: cmp si,[StuffEnd-MoveDown] jae StuffFinished lodsb cmp al,GETWORD_CODE ja Extended ; Extended function je @@GetBoth ; 254 => char, scan follows mov ah,2 ; Simulate scan = 2 for normal chars cmp al,224 ; Character for Enh.Kbd new keys je @@GetScan or al,al jne stuff @@GetScan: mov ah,[si] inc si jmp short stuff @@GetBoth: lodsw Stuff: CLI mov di,[BufferTail] stos word ptr [BIOS:di] cmp di, OFFSET BufferEnd jb @@1 mov di, OFFSET BufferStart @@1: cmp di,[BufferHead] je @@Overflow mov [BufferTail],di STI StuffOK: mov [StuffPtr-MoveDown],si jmp GetNext @@OverFlow: STI ret NextKey ENDP ExtendedTable label word dw Reboot - MoveDown dw AbsTime - MoveDown dw DeltaTime - MoveDown dw StartFind - MoveDown dw StartPrompt - MoveDown Extended PROC near lodsb ; Get function code! cmp al,4 ja StuffFinished ; Program Error! cbw mov bx,ax shl bx,1 jmp ExtendedTable[bx - MoveDown] Extended endp Reboot proc near mov word ptr [BIOS: 72h],1234h jmp RebootLocation Reboot endp AbsTime: ; Both Abs time & Delta time land here DeltaTime: ; ---- " ---- GetTime proc near cmp al, DELTATIME_CODE lodsw ; Next 3 bytes is # of ticks mov dl,[si] jz @@TimeOk ; Delta time, so wait # of ticks ; Wait until time equal: Calculate remaining ticks sub ax,[Bios_Timer] sbb dl,BYTE PTR [Bios_Timer+2] jae @@TimeOk add ax,TicksPrDay AND 0FFFFh adc dl,TicksPrDay Shr 16 @@TimeOK: inc si mov [StuffPtr-MoveDown],si ; Point to next byte sub dh,dh ; Fill top of DX with 0 or ax,dx jz @@WaitZero ; Special case, wait for empty kbd mov [CountLow-MoveDown],ax mov [CountHigh-MoveDown],dl mov [StuffMode-MoveDown], OFFSET CountDown - MoveDown ret @@WaitZero: mov [StuffMode-MoveDown], OFFSET WaitEmpty - Movedown ret GetTime endp CountDown proc near DEC [CountLow-MoveDown] jnz NoChange SUB [CountHigh-MoveDown],1 jae NoChange StartNextKey: mov [StuffMode-MoveDown], OFFSET NextKey - MoveDown NoChange: ret CountDown endp WaitEmpty proc near mov ax,[BufferHead] cmp ax,[BufferTail] je StartNextKey ret WaitEmpty endp StartPrompt: mov [StuffMode-MoveDown], offset ScanPrompt - MoveDown mov [StuffPtr-MoveDown],si ; si -> dx:BYTE, dy:BYTE, count:WORD, len:BYTE, attr:BYTE, st:BYTE * len ScanPrompt: ; Find start posn'n mov si,[StuffPtr-MoveDown] lodsw ; AL = dx, AH = dy mov cx,[Cursor] ; CL = X, CH = y sub cl,al jae @@1 sub cl,cl @@1: sub ch,ah jae @@2 sub ch,ch @@2: mov al,byte ptr [CrtWidth] mul ch ; AX = Y-offset sub ch,ch add ax,cx shl ax,1 ; AX = Start of scan jmp short Scan1 StartFind: mov [StuffMode-MoveDown], offset ScanText - MoveDown mov [StuffPtr-MoveDown],si ; si -> start:WORD, count:WORD, len:BYTE, attr:BYTE, st:BYTE * len ScanText proc near mov si,[StuffPtr-MoveDown] lodsw ; starting offset in screen Scan1: mov di,ax lodsw mov cx, ax ; # of char cells to search lodsw ; AL = len, AH = attr mov dl,al xor dh,dh ; Text len dec dx ; Skip first char in length mov bx, 0B000h cmp [VideoMode],7 je @@1 cmp [VideoMode],3 ja @@done mov bh,0B8h @@1: mov es,bx ; ES -> video segment ASSUME ES:NOTHING inc si ; Skip first char cmp ah,255 ; ATTR = 255 -> no attr je @@FindChar @@FindCharAttr: mov al,[si-1] ; First char to match repne scasw jne @@done or dx,dx ; Remaining length = 0 jz @@found push cx push si push di mov cx,dx @@l2: lodsb scasw loope @@l2 pop di pop si pop cx je @@found ; Yes, all chars match! jcxz @@done ; No more room! jne @@FindCharAttr ; this BUG was found by rbabcock! @@FindChar: mov al,[si-1] dec di @@l3: jcxz @@done ; No more room to start in! @@l31: inc di scasb loopne @@l31 jne @@done or dx,dx jz @@found push cx push si push di mov cx,dx @@l4: inc di cmpsb loope @@l4 pop di pop si pop cx jne @@l3 @@found: add si,dx ; Point after text to search for mov [StuffMode-MoveDown], offset NextKey - MoveDown mov [StuffPtr-MoveDown],si @@done: ret ScanText Endp ALIGN 2 BiosSeg dw 40h FirstByteToCopy label byte StuffPtr dw OFFSET StuffBuffer - MoveDown StuffEnd dw ? StuffMode dw OFFSET NextKey - MoveDown CountLow dw ? CountHigh db ? ; Use 24 bits for tick counter active db -1 ResidentEnd EQU $ StuffBuffer LABEL byte StartMsg db 'StuffIt V',VerStr,' (C) Terje Mathisen 1989-90',13,10,'$' SyntaxMsg label byte db 'Syntax: Stuffit ',13,10 db ' Valid commands:',13,10 db ' +|=[[hh:]mm:]ss | Delay for(+) or until(=) a specified time.',13,10 db ' +45 will wait for 45 seconds.',13,10 db ' =14:00:00 will wait until 2pm.',13,10 db ' +0 will wait until the kbd buffer is empty.',13,10 db ' | Stuff a given character code.',13,10 db ' 27 = , 13 = etc.',13,10 db ' @ | Stuff a given scan code (char=0).',13,10 db ' @68 = F10, @73 = PgUp etc.',13,10 db ' : | Specify both character and scan.',13,10 db ' 43:74 = ',13,10 db " 'TEXT'",' or "TEXT" | Stuff all the characters in TEXT',13,10 db ' F,,[,attr],"STRING" | Find "STRING" in an area starting at (X,Y),',13,10 db " and beeing (N) char's long.",13,10 db ' Ignore text attributes, unless is specified.',13,10 db ' P,,[,attr],"STRING" | Find Prompt "STRING", starting at',13,10 db " (CursorX-DX,CursorY-DY).",13,10 db ' ! | Reboot. (=0 ! will reboot at midnight.)',13,10 db ' /F:FileName | Read commands from .',13,10 db ' /B:nnnn | Allocate room for bytes in TSR(512 default).',13,10 db 'StuffIt /R will Remove (Unload) StuffIt from RAM.',13,10,'$' UpdateMsg db 'Resident copy updated!',13,10,'$' StayResMsg db 'Resident code loaded!',13,10,'$' RemovedMsg db 'Resident code removed from RAM!',13,10,'$' WrongVerMsg db 'A different version of StuffIt is already resident!',13,10,'$' NotRemovedMsg label byte db 'Resident code cannot be removed, as another ' db 'program is using the',13,10 db 'Timer (Int 8) and/or the Multiplex (Int 2F) ' db 'vector. Please remove all',13,10 db 'programs loaded after StuffIt, ' db 'and retry the operation.',13,10,'$' RamErrMsg label byte db 'Not enough RAM! (Need at least 128 kB to initialize program.)',13,10,'$' FileErrMsg label byte db 'Error reading input file!',13,10,'$' ResidentToSmallMsg label byte db 'Resident buffer to small! Try to remove it (/R) and reload.',13,10,'$' ;FirstBlock dw ? ;SecondBlock dw ? JUMPS ; Allow inefficient code here! Init proc near ASSUME DS:CODE, ES:CODE mov ax,OFFSET StuffBuffer - MoveDown + 512 + 15 and ax,0FFF0h mov [ResidentSize], ax ; Start by relocating the program into a second segment ; mov [FirstBlock],CS ; Save segment addr mov ah,4Ah mov bx,2000h ; Realloc to 128 kB ; mov es,[FirstBlock] int 21h mov dx, offset RamErrMsg jc ErrorMessage ; mov ah,48h ; mov bx,1000h ; int 21h ; Alloc second 64kB block ; mov dx, offset RamErrMsg ; jc ErrorMessage mov ax,cs add ax,1000h ; Point after 1st 64kB mov es,ax ; mov [SecondBlock],es sub si,si mov di,si mov cx,(OFFSET ProgramEnd - OFFSET PspStart + 1) Shr 1 cld rep movsw push es mov ax, OFFSET Continue push ax retf ; pop cs ; Jump into second copy of program! Continue: push ds push cs pop ds pop es ; DS=CS = SecondBlock, ES=FirstBlock ; Move resident part of code as low as possible: mov si, OFFSET HighStart mov di, OFFSET LowStart mov cx, OFFSET ResidentEnd - OFFSET HighStart rep movsb mov dx, OFFSET StartMsg mov ah,9 int 21h call Parse cmp di, OFFSET StuffBuffer - MoveDown je Syntax ; No parameter push es pop ds ; DS,ES,SS = FirstBlock mov [StuffEnd-MoveDown],di ; mov [StuffMode-MoveDown], OFFSET NextKey - MoveDown ; mov [StuffPtr-MoveDown], OFFSET StuffBuffer - MoveDown ; mov [active-MoveDown],-1 ; Initialize [active] flag ; mov [BiosSeg-MoveDown],40h ; Fast load of ES: when resident call TestSecond ; Don't return if second copy! ; Get old int 2F interrupt mov ax,352Fh int 21h mov WORD PTR [OldInt2F-MoveDown],BX mov WORD PTR [OldInt2F+2-MoveDown],ES ; Get old timer interrupt mov ax,3508h int 21h mov WORD PTR [OldTimer-MoveDown],BX mov Word Ptr [OldTimer+2-Movedown],ES ; Enter our routine first mov ax,252Fh mov dx, OFFSET Int2F - MoveDown int 21h mov ax,2508h mov dx, OFFSET MyTimer - MoveDown int 21h mov ES, [DS:2Ch] mov ah,49h int 21h mov word ptr [DS:2Ch],0 ; Signal no environment! push ds push cs pop ds mov dx, OFFSET StayResMsg mov ah,9 int 21h pop ds mov cx,5 @@CloseLoop: mov bx,cx dec bx mov ah,3Eh int 21h loop @@CloseLoop push ss pop ds mov dx, [DS:ResidentSize] cmp dx, [DS:Stuffend-MoveDown] ja @@OK mov dx, [DS:StuffEnd-MoveDown] ; DX = MAX(ResidentSize, StuffEnd) mov [DS:ResidentSize],dx @@OK: add dx,15 mov cl,4 shr dx,cl mov ax,3100h int 21h ; Go TSR with first block Init Endp FindFirst proc near mov dx,Semafor mov ax,0E000h xor bx,bx int 2Fh cmp al,0FFh jne @@done cmp bx, Version je @@done mov dx, offset WrongVerMsg jmp ErrorMessage @@done: ret ; Return ZERO if found FindFirst endp TestSecond proc near call FindFirst jne @@NotFound mov es,dx ; Save segment ; This is the second copy! ES -> to first copy ; Test if enough room in resident program: mov ax,[StuffEnd-MoveDown] cmp ax,[ES:ResidentSize] mov dx, OFFSET ResidentToSmallMsg ja ErrorMessage ; Will now move all data into first copy, including pointers and StuffMode mov [ES:SelfModify-MoveDown], IRET_OPCODE ; Stop resident program mov [DS:SelfModify-MoveDown], IRET_OPCODE ; Stop this version! mov si, offset FirstByteToCopy - MoveDown mov di, si mov cx, ax ; [StuffEnd] sub cx,si rep movsb mov [ES:SelfModify-MoveDown], PUSH_AX_OPCODE ; Restart resident version mov dx, OFFSET UpdateMsg mov ah,9 int 21h mov ax,4C00h int 21h @@NotFound: ParseFinish: ret TestSecond ENDP LocalSyntax: jmp Syntax EOF EQU 26 Parse Proc near cld mov si, OFFSET CommandLine mov bl,[si-1] sub bh,bh mov byte ptr [si+bx],EOF RestartParse: mov di, OFFSET StuffBuffer - MoveDown ParseNextChar: lodsb cmp al,EOF je ParseFinish cmp al,' ' jbe ParseNextChar cmp al,'0' jb @@NotDigit cmp al,'9' ja @@NotDigit @@Digit: dec si mov bx,255 call GetNumber cmp al,':' mov al,bl mov ah,2 jne @@NotTwo push ax mov bx,255 call GetNumber cmp al,':' je LocalSyntax pop ax mov ah,bl @@NotTwo: Call SaveChar jmp ParseNextChar @@NotDigit: cmp al,'/' je ParseOption cmp al,'@' jne @@NotFunc mov bx,255 call GetNumber mov ah,bl xor al,al stosw jmp ParseNextChar @@NotFunc: cmp al,"'" je @@Quote cmp al,'"' jne @@NotQuote @@Quote: mov bl,al ; Save starting quote char @@2: lodsb cmp al,13 ; Missing last quote je Syntax cmp al,bl ; Ending quote? je ParseNextChar ; Yes, restart mov ah,2 ; Assume scan = 2 call SaveChar jmp @@2 @@NotQuote: cmp al,'+' jne @@3 jmp DeTime @@3: cmp al,'=' jne @@4 jmp AtTime @@4: ; Use ! to signal Reboot cmp al,'!' je SignalReboot cmp al,'f' je FindNear cmp al,'F' je FindNear cmp al,'p' je PromptNear cmp al,'P' je PromptNear ; Fall into syntax error! Parse endp Syntax Proc near mov dx, OFFSET SyntaxMsg ErrorMessage: push cs pop ds mov ah,9 int 21h mov ax,4C01h int 21h Syntax Endp FindNear: jmp FindText PromptNear: jmp PromptText SignalReBoot: mov ax,EXTENDED_CODE + (REBOOT_CODE * 256) stosw jmp ParseNextChar ReadFile proc near cmp byte ptr [si],':' jne @@Skip inc si @@Skip: mov dx,si @@Next: lodsb cmp al,' ' ja @@Next dec si mov ax,3D00h ; Open file for Read_Only mov byte ptr [si],al ; Make ASCIIZ filename int 21h mov dx, OFFSET FileErrMsg jc ErrorMessage mov bx,ax mov ah,3Fh ; Read File mov dx, OFFSET ProgramEnd mov si,dx mov cx, - (OFFSET ProgramEnd) ; Max Size in segment int 21h mov dx, OFFSET FileErrMsg jc ErrorMessage add si, ax mov byte ptr [si],EOF sub si, ax ; Point back to start of filebuffer mov ah,3Eh int 21h ; Close this file jmp RestartParse ; Parse file buffer! ReadFile endp SetBufferSize proc near cmp byte ptr [si],':' jne @@Skip inc si @@Skip: mov bx,-( OFFSET StuffBuffer - MoveDown) + 32; Max text buffer call GetNumber add bx,OFFSET StuffBuffer - MoveDown + 15 and bx,0FFF0h mov [ES:ResidentSize],bx jmp ParseNextChar SetBufferSize endp ParseOption Proc near lodsb cmp al,'a' jb @@Upper cmp al,'z' ja @@Upper sub al,'a'-'A' @@Upper: cmp al,'B' je SetBufferSize cmp al,'F' je ReadFile cmp al,'R' jne Syntax @@Remove: call FindFirst jnz Syntax ; First copy, nothing to remove! mov ax,3508h int 21h cmp bx, OFFSET MyTimer - MoveDown jne @@CannotRemove mov ax, es cmp ax, dx jne @@CannotRemove ; Other TSR has timer vector! mov ax,352Fh int 21h cmp bx, OFFSET Int2F - MoveDown jne @@CannotRemove mov ax, es cmp ax, dx jne @@CannotRemove ; Other TSR has Int 2F vector! ; OK to remove previous copy from RAM ; First, restore timer vector push ds mov dx,[word ptr ES:OldTimer-MoveDown] mov ds,[word ptr ES:OldTimer+2-MoveDown] mov ax,2508h int 21h ; Then, retore Int 2F vector mov dx,[word ptr ES:OldInt2F-MoveDown] mov ds,[word ptr ES:OldInt2F+2-MoveDown] mov ax,252Fh int 21h pop ds ; Next, release the memory segment ; ES -> to previos copy! mov ah,49h int 21h mov dx, OFFSET RemovedMsg mov ah,9 int 21h mov ax,4C00h int 21h @@CannotRemove: mov dx, OFFSET NotRemovedMsg mov ah,9 int 21h mov ax,4C01h int 21h ParseOption Endp hour dw ? min dw ? sec dw ? DeTime Proc near mov ax,EXTENDED_CODE + (DELTATIME_CODE * 256) jmp short ParseTime AtTime: mov ax,EXTENDED_CODE + (ATTIME_CODE * 256) ParseTime: stosw ; Save marker for time ; FIX BUG found by davidgb. HOUR and Min MUST be initialized to ZERO! xor ax,ax mov [hour],ax mov [min],ax ; END OF bug-fix mov bx,59 ; Max value call GetNumber cmp al,':' jne @@SaveSec mov [min],bx mov bx,59 call GetNumber cmp al,':' jne @@SaveSec xchg bx,[min] mov [hour],bx mov bx,59 call GetNumber cmp al,':' je NearError @@SaveSec: mov [sec],bx ; Now convert hour:min:sec into Timer ticks: ; Ticks= (hour*TicksPrHour) + (((min*60)+sec) * 34829 + 956) DIV 1913 mov ax,(TicksPrHour - 65536) mul [hour] add dx,[hour] ; DX:AX = hour*TicksPrHour push ax ; Save DX:AX push dx ; --- " --- mov al,60 mul [byte ptr min] add ax,[sec] mov dx,34829 mul dx add ax,1913 Shr 1 adc dx,0 mov bx,ax mov ax,dx xor dx,dx mov cx,1913 div cx xchg ax,bx div cx ; BX:AX = Ticks in (min*60+sec) pop dx pop cx ; DX:CX = Ticks in hours add ax,cx adc dx,bx ; DX:AX = Total Ticks stosw ; Save Low word of count mov al,dl stosb ; Save high byte of count jmp ParseNextChar DeTime Endp PromptText: mov ax,EXTENDED_CODE + (PROMPT_CODE * 256) stosw mov bx,127 call GetNumber cmp al,',' jne NearError mov al,bl ; Save X value stosb mov bx,60 ; 0 flag for Find Text stosw mov bx,132 ; 0 0 mov ax,bx stosw ; Save buffer length mov bx,255 cmp byte ptr [si],'"' je @@SkipAttr cmp byte ptr [si],"'" je @@SkipAttr call GetNumber cmp al,',' jne NearError @@SkipAttr: mov ah,bl lodsb cmp al,'"' je @@1 cmp al,"'" jne GetError @@1: mov bx,di ; Save current pos for len stosw ; Store len + attr mov ah,al xor cx,cx @@2: lodsb cmp al,EOF je GetError cmp al,13 je GetError cmp al,ah je @@3 inc cx ; INC len stosb ; Save Text jmp @@2 @@3: mov [ES:bx],cl ; Save actual length! jmp ParseNextChar FindText endp GetError: jmp syntax GetNumber proc near ; input: SI -> first char to convert, BX = max value push cx ; Use as temp buffer push dx ; For mul push di ; For sign flag sub cx,cx mov ah,ch mov di,cx ; Zero DI -> Positive cmp byte ptr [si],'-' jne @@GetLoop dec di inc si @@GetLoop: lodsb cmp al,' ' jbe @@GetEnd cmp al,':' je @@GetEnd cmp al,',' je @@GetEnd sub al,'0' jb GetError cmp al,9 ja GetError ; Valid decimal digit! xchg ax,cx mov dx,10 mul dx add cx,ax jmp @@GetLoop @@GetEnd: cmp al,EOF jne @@1 dec si ; Prepare to reload AL @@1: cmp cx,bx ; Valid value? ja GetError mov bx,cx or di,di jz @@done neg bx ; return -BX @@done: pop di pop dx pop cx ret GetNumber endp SaveChar proc near ; AL, AH = char, scan to save in StuffBuffer or al,al je @@Save2 cmp al,224 je @@Save2 cmp al,254 jae @@Save3 cmp ah,2 ; Scan for normal chars jne @@Save3 mov ah,14 cmp al,8 je @@Save3 mov ah,15 cmp al,9 je @@Save3 mov ah,28 cmp al,13 je @@Save3 mov ah,1 cmp al,27 je @@Save3 ; Normal character, store just the char itself stosb ret @@Save2: stosw ret @@Save3: mov byte ptr [es:di],254 inc di stosw ret SaveChar endp ProgramEnd label byte CODE ENDS END start