;       KEY-FAKE.ASM -- Fakes keystrokes from internal keyboard buffer.
;       ============

CSEG            Segment
                Assume  CS:CSEG
                Org     0100h
Entry:          Jmp     Initialize

;       Most Resident Data
;       ------------------

                db      'KEY-FAKE (C) Copyright Charles Petzold, 1985'
SearchLabelEnd  Label   Byte

OldInterrupt16  dd      0
Pointer         dw      Offset KeyStrokeBuffer
Counter         db      0

;       New Interrupt 16 (Keyboard)
;       ---------------------------

NewInterrupt16  Proc    Far

                Sti                     ; Allow futher interrupts
                Cmp     CS:[Counter],0  ; See if characters in buffer
                Jz      DoOldInterrupt  ; If not, just do regular interrupt

                Or      AH,AH           ; Check if AH is zero
                Jz      GetCharacter    ; If so, call is to get character

                Cmp     AH,1            ; Check if AH is one
                Jz      GetStatus       ; If so, call is for status

DoOldInterrupt: Jmp     CS:[OldInterrupt16]     ; Otherwise, go away

GetCharacter:   Push    BX
                Mov     BX,CS:[Pointer] ; BX points to current buffer position
                Mov     AX,CS:[BX]      ; Get ASCII code and scan code
                Inc     BX              ; Move buffer pointer ahead
                Inc     BX
                Mov     CS:[Pointer],BX ; Save new pointer
                Dec     CS:[Counter]    ; One less character in counter
                Pop     BX

                Or      AX,AX           ; See if 0 returned
                Jz      NewInterrupt16  ; If so, take it from the top again

                IRet                    ; Return to calling program

GetStatus:      Push    BX
                Mov     BX,CS:[Pointer] ; BX points to current buffer position
                Mov     AX,CS:[BX]      ; Get ASCII code and scan code
                Pop     BX

                Or      AX,AX           ; See if special 0 keystroke
                Jnz     StatusReturn    ; If not, return non-zero flag

                Add     CS:[Pointer],2  ; If so, skip over it
                Dec     CS:[Counter]    ; One less character
                Or      AX,AX           ; Will set zero flag

StatusReturn:   Ret     2               ; Do not pop flags

NewInterrupt16  EndP

;       Beginning of Key Stroke Buffer
;       ------------------------------

KeyStrokeBuffer Label   Byte            ; 256 Byte Buffer for keystrokes

;       Initialization -- Search through Memory and see if label matches
;       ----------------------------------------------------------------
;
;               If so, use the loaded program; if not, create a new interrupt

                Assume  DS:CSEG, ES:CSEG, SS:CSEG

Initialize:     Mov     Word Ptr [Entry],0      ; Slightly modify search label
                Mov     Byte Ptr [Entry + 2],0  ;   so no false matches

                Cld
                Mov     DX,CS                   ; This segment
                Sub     AX,AX                   ; Beginning of search
                Mov     ES,AX                   ; Search segment

SearchLoop:     Mov     SI,100h                 ; Address to search
                Mov     DI,SI                   ; Set pointers to same address
                Mov     CX,Offset SearchLabelEnd - Offset Entry
                Repz    Cmpsb                   ; Check for match
                Jz      ReadyForDecode          ; If label matches

                Inc     AX                      ; Still the search segment
                Mov     ES,AX                   ; ES to next segment

                Cmp     AX,DX                   ; Check if it's this segment
                Jnz     SearchLoop              ; Try another compare

                Mov     Byte Ptr DS:[1],27h     ; Since no match found,
                                                ;   set up PSP for Terminate &
                                                ;   remain resident.

;       Save and Set Interupt 16 if Staying Resident
;       --------------------------------------------

                Sub     AX,AX                   ; Set AX to zero
                Mov     DS,AX                   ; To access vector segment
                Assume  DS:Nothing              ; Tell the assembler

                Mov     AX,Word Ptr DS:[16h * 4]        ; Get vector offset
                Mov     Word Ptr CS:[OldInterrupt16],AX ; Save it
                Mov     AX,Word Ptr DS:[16h * 4 + 2]    ; Get vector segment
                Mov     Word Ptr CS:[OldInterrupt16 + 2],AX     ; and save it

                Cli                                     ; Don't interrupt me
                Mov     DS:[16h * 4],Offset NewInterrupt16      ; Store new
                Mov     DS:[16h * 4 + 2],CS             ; address
                Sti                                     ; Now you can talk

                Push    CS
                Pop     DS                              ; Restore DS
                Assume  DS:CSEG

;       Parameter decoding when program segment has been found
;       ------------------------------------------------------
;
;               ES = segment of loaded program (could be CS)

ReadyForDecode: Mov     SI,80h          ; SI points to parameter area
                Mov     DI,Offset KeyStrokeBuffer
                Mov     ES:[Pointer],DI ; ES:DI points to buffer area
                Mov     ES:[Counter],0  ; Set keystroke counter to zero

                Lodsb                   ; Get parameter count
                Cbw                     ; Convert to word
                Mov     CX,AX           ; CX = parameter count
                Inc     CX              ; So catch last delimiter (0D)
                Or      AX,AX           ; Check if parameter present
                Jnz     GoDecodeLoop    ; If so, continue
                Jmp     EndDecode       ; If not, cut out

GoDecodeLoop:   Jmp     DecodeLoop

;       End of Residence is end of Key Stroke Buffer
;       --------------------------------------------

                Org     256 + Offset KeyStrokeBuffer

EndResidence    Label   Byte

;       Data for Parameter Decoding
;       ---------------------------

QuoteSign       db      0               ; Flag for quoted strings
DoingNumber     db      0               ; Flag for doing a number
DoingExtended   db      0               ; Flag for doing extended ASCII
CalcNumber      db      0               ; A calculated number
Ten             db      10              ; For MUL convenience

;       Routine for doing quoted text
;       -----------------------------

DecodeLoop:     Lodsb                   ; Get character
                Cmp     [QuoteSign],0   ; Check if doing quoted text
                Jz      NotDoingQuote   ; If not, continue checks

                Cmp     AL,[QuoteSign]  ; Check first if character is quote
                Jz      EndQuote        ; If so, finish quoted text

                Sub     AH,AH           ; Set scan code to zero
                Stosw                   ; Save it in buffer
                Inc     ES:[Counter]    ; One more character
                Jmp     DoNextCharacter ; Go to bottom of routine

EndQuote:       Mov     [QuoteSign],0   ; End of quoted text
                Jmp     DoNextCharacter ; Get the next character

;       Routine for Extended Ascii Character (@)
;       ----------------------------------------

NotDoingQuote:  Cmp     AL,'@'          ; See if character is for extended
                Jnz     NotExtended     ; If not, hop over a little code

                Mov     [DoingExtended],1       ; Flag for extended ASCII
                Jmp     Delimiter               ; To possibly dump number

;       Routine for Quote Sign ' or "
;       -----------------------------

NotExtended:    Cmp     AL,'"'          ; Check for a double quote sign
                Jz      Quote
                Cmp     AL,"'"          ; Check for a single quote sign
                Jnz     NotAQuote

Quote:          Mov     [QuoteSign],AL  ; Save the quote sign
                Jmp     Delimiter       ; To possibly dump number

;       Routine for decimal number
;       --------------------------

NotAQuote:      Cmp     AL,'0'          ; See if character >= 0
                Jb      Delimiter
                Cmp     AL,'9'          ; See if character <= 9
                Ja      Delimiter

                Mov     [DoingNumber],1         ; If so, doing number

                Sub     AL,'0'                  ; Convert to binary
                Xchg    AL,[CalcNumber]         ; Get previously calculated
                Mul     [Ten]                   ; Multiply by 10
                Add     [CalcNumber],AL         ; Add it to new digit

                Jmp     DoNextCharacter         ; And continue

;       Anything else is considered a delimiter
;       ---------------------------------------

Delimiter:      Cmp     [DoingNumber],1         ; Check if doing a number
                Jnz     DoNextCharacter         ; If not, do not dump

                Mov     AL,[CalcNumber]         ; Set AX to ASCII number
                Sub     AH,AH                   ; Zero out scan code part
                Cmp     [DoingExtended],1       ; Check if doing scan code
                Jnz     NumberOK

                Xchg    AL,AH                   ; Switch ASCII and scan code

NumberOK:       Stosw                           ; Store the two codes
                Inc     ES:[Counter]            ; One more character in buffer

                Mov     [DoingNumber],0         ; Clear out all flags
                Mov     [DoingExtended],0
                Mov     [CalcNumber],0

DoNextCharacter:Dec     CX                      ; One less character to do
                Jz      EndDecode               ; If no more, we're done
                Jmp     DecodeLoop              ; Otherwise, get next one

;       End Decode -- Ready to terminate (and possibly stay resident)
;       -------------------------------------------------------------

EndDecode:      Mov     DX,Offset EndResidence  ; End of resident part
                Ret                             ; Int 20h or 27h

CSEG            EndS

                End Entry
