;	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