        PAGE ,132
        TITLE   BUF160
;  MASM 1.25 gave errors on MOV to DD defined words.  Fixed by adding
;  WORD PTR in appropriate places.  Added comments for previous
;  versions.                  Ted Shapin,  2/11/85.
;
;  The original version
;  improperly handled one of the keyboard service functions (return
;  shift key status:  interrupt 16, function 2), and would crash
;  whenever any program asked for the shift key status.  Apparently
;  many programs just look this value up for themselves instead of
;  going through the BIOS, but Epsilon plays by the rules where
;  possible, and calls this function to determine the shift key
;  settings.
;
;  I've fixed this bug in buf160.asm and am placing these changes in
;  the public domain.
;
;  This file should be assembled to get buf160.obj, which is
;  linked to get buf160.exe, which in turn is run through exe2bin to
;  produce buf160.com.  Ignore the "missing stack segment" message from
;  link.
;
;  Steve Doerfler, Lugaru Software, date unknown.
;
; Original by John Socha in Softalk for the IBM PC, Nov. 83. Translated
; to assembler by Jim Gillogly 11/22/83/
;
VECTORS		SEGMENT	AT 0H
	ORG	9H*4
KEYBOARD_INT_VECTOR	LABEL	DWORD
	ORG	16H*4
KEYBOARD_IO_VECTOR	LABEL	DWORD
VECTORS		ENDS

ROM_BIOS_DATA	SEGMENT AT 40H
	ORG	17H
KBD_FLAG	DB	?
	ORG	1AH
ROM_BUFFER_HEAD	DW	?
ROM_BUFFER_TAIL	DW	?
KB_BUFFER	DW	16D DUP	(?)
KB_BUFFER_END	LABEL	WORD
ROM_BIOS_DATA	ENDS
CODE_SEG	SEGMENT
	ASSUME	CS:CODE_SEG
	ORG	100H
BEGIN:	JMP	INIT_VECTORS		;Initialize vectors and attach to DOS
ROM_KEYBOARD_INT	DD	0	;[0 needed for MS-MASM 3.00 -rag]
ROM_KEYBOARD_IO		DD	0
BUFFER_HEAD		DW	OFFSET KEYBOARD_BUFFER
BUFFER_TAIL		DW	OFFSET KEYBOARD_BUFFER
KEYBOARD_BUFFER		DW	160D DUP(0)	;159 character input buffer
KEYBOARD_BUFFER_END	LABEL	WORD

;--------------------------------------------------------;
; This procedure sends a short beep when the buffer fills
;--------------------------------------------------------;

KB_CONTROL	EQU	61H		;Control bits for keyboard & speaker
ERROR_BEEP	PROC	NEAR
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSHF				;Save old interupt enable flag
	CLI				;Turn off beep during interupt
	MOV	BX,30D			;Number of cycles for 1/8 second tone
	IN	AL,KB_CONTROL		;Get information from speaker port
	PUSH	AX			;Save control information
START_OF_ONE_CYCLE:
	AND	AL,0FCH			;Turn off speaker
	OUT	KB_CONTROL,AL
	MOV	CX,60D			;Delay for one half cycle
OFF_LOOP:
	LOOP	OFF_LOOP
	OR	AL,2			;Turn off speaker
	OUT	KB_CONTROL,AL
	MOV	CX,60D			;Delay for second half cycle
ON_LOOP:
	LOOP	ON_LOOP
	DEC	BX			;200 cycles yet?
	JNZ	START_OF_ONE_CYCLE
	POP	AX			;Recover old keyboard information
	OUT	KB_CONTROL,AL
	POPF				;Restore interupt flag
	POP	CX
	POP	BX
	POP	AX
	RET
ERROR_BEEP	ENDP

;This procedure checks the ROM keyboard buffer to see if some program
;tried to clear this buffer. We know it's been cleared when the ROM
;tail and header overlap. Normally, the new procedures below keep the
;dummy character, word 0,in the buffer.
;
;Uses: BX,DS
;Writes:	BUFFER_HEAD,BUFFER_TAIL,ROM_BUFFER_HEAD,
;		ROM_BUFFER_TAIL
;Reads:		KEYBOARD_BUFFER,KB_BUFFER

CHECK_CLEAR_BUFFER	PROC NEAR
	ASSUME	DS:ROM_BIOS_DATA
	MOV	BX,ROM_BIOS_DATA	;Establish pointer to BIOS data
	MOV	DS,BX
	CLI				;Turn off interupts during this check
	MOV	BX,ROM_BUFFER_HEAD	;check to see if buffer cleared
	CMP	BX,ROM_BUFFER_TAIL	;Is the buffer empty?
	JNE	BUFFER_OK		;No, then everything is alright
					;Yes, then clear the internal buffer
	MOV	BX,OFFSET KB_BUFFER	;Reset buffer with word 0 in buffer
	MOV	ROM_BUFFER_HEAD,BX
	ADD	BX,2
	MOV	ROM_BUFFER_TAIL,BX
	ASSUME	DS:CODE_SEG
	MOV	BX,CS
	MOV	DS,BX
	MOV	BX,OFFSET KEYBOARD_BUFFER ;Reset internal buffer
	MOV	BUFFER_HEAD,BX
	MOV	BUFFER_TAIL,BX
BUFFER_OK:
	ASSUME	DS:CODE_SEG
	STI				;Interupts back on
	RET
CHECK_CLEAR_BUFFER	ENDP

;This procedure intercepts the keyboard interrupt and moves any new
;characters to the internal, 80 character buffer.

INTERCEPT_KEYBOARD_INT	PROC	NEAR
	ASSUME	DS:NOTHING
	PUSH	DS
	PUSH	SI
	PUSH	BX
	PUSH	AX
	CALL	CHECK_CLEAR_BUFFER	;Check for buffer cleared
	PUSHF
	CALL	ROM_KEYBOARD_INT	;Read scan code with BIOS routines
;-----	Transfer any characters to internal buffer
	ASSUME	DS:ROM_BIOS_DATA
	MOV	BX,ROM_BIOS_DATA	;Establish pointer to BIOS data
	MOV	DS,BX
	MOV	SI,BUFFER_TAIL
	MOV	BX,ROM_BUFFER_HEAD	;Check if real characters in buffer
	INC	BX			;Skip over dummy character
	INC	BX
	CMP	BX,OFFSET KB_BUFFER_END
	JB	DONT_WRAP		;No need to wrap the pointer
	MOV	BX,OFFSET KB_BUFFER	;Wrap the pointer
DONT_WRAP:
	CMP	BX,ROM_BUFFER_TAIL	;Is there a real character?
	JE	NO_NEW_CHARACTERS	;No then return to caller
	MOV	AX,[BX]			;Yes,move character to internal buffer
	MOV	CS:[SI],AX
	INC	SI
	INC	SI			;Move to next position
	CMP	SI,OFFSET KEYBOARD_BUFFER_END
	JB	NOT_AT_END
	MOV	SI,OFFSET KEYBOARD_BUFFER
NOT_AT_END:
	CMP	SI,BUFFER_HEAD		;Buffer overrun?
	JNE	WRITE_TO_BUFFER		;Yes beep and throw out character
	CALL	ERROR_BEEP
	JMP	SHORT NOT_AT_KB_END
WRITE_TO_BUFFER:
	MOV	BUFFER_TAIL,SI
NOT_AT_KB_END:
	MOV	ROM_BUFFER_HEAD,BX

NO_NEW_CHARACTERS:
;----	See if CTRL + ALT pushed, and clear buffer if so

	MOV	AL,KBD_FLAG		;Get status of shift keys into AL
	AND	AL,0CH			;Isolate Alt and Crtl shift flags
	CMP	AL,0CH			;Are both the Alt and Crtl keys down?
	JNE	DONT_CLEAR_BUFFER	;No, then dont clear the buffer
	MOV	AX,BUFFER_TAIL		;Yes, then clear the buffer
	MOV	BUFFER_HEAD,AX

DONT_CLEAR_BUFFER:
	POP	AX
	POP	BX
	POP	SI
	POP	DS
	IRET
INTERCEPT_KEYBOARD_INT ENDP

;-----------------------------------------------------------------------;
;This procedure replaces the ROM BIOS routines for reading a character  ;
;-----------------------------------------------------------------------;
	ASSUME	DS:CODE_SEG
INTERCEPT_KEYBOARD_IO PROC	FAR
	STI				;Interrupts back on
	PUSH	DS			;Save current DS
	PUSH	BX			;Save BX temporarily
	CALL	CHECK_CLEAR_BUFFER	;Check for buffer cleared
	MOV	BX,CS			;Establish pointer to data area
	MOV	DS,BX
	OR	AH,AH			;AH=0?
	JZ	READ_CHARACTER		;Yes, read a character
	CMP	AH,1			;AH=1?
	JE	READ_STATUS		;Yes, return the status

	POP	BX			;Pass other calls to the rom bios
	POP	DS
	ASSUME	DS:NOTHING
	JMP	ROM_KEYBOARD_IO		;Let ROM routines do this
	ASSUME	DS:CODE_SEG

;----	Read the key

READ_CHARACTER:				;ASCII read
	STI				;Interrupts back on during loop
	NOP				;Allow an interrupt to occur
	CLI				;Interrupts back off
	MOV	BX,BUFFER_HEAD		;Get pointer to head of buffer
	CMP	BX,BUFFER_TAIL		;Test end of buffer
	JE	READ_CHARACTER		;Loop until something in buffer
	MOV	AX,[BX]			;Get scan code and ASCII code
	ADD	BX,2			;Move to next word in buffer
	CMP	BX,OFFSET KEYBOARD_BUFFER_END	;At end of buffer?
	JNE	SAVE_POINTER		;No, continue
	MOV	BX,OFFSET KEYBOARD_BUFFER	;Yes, reset to buffer start
SAVE_POINTER:
	MOV	BUFFER_HEAD,BX		;Store value in variable
	POP	BX
	POP	DS
	IRET				;Return to caller

;----	ASCII status

READ_STATUS:
	CLI				;Interrupts off
	MOV	BX,BUFFER_HEAD		;Get head pointer
	CMP	BX,BUFFER_TAIL		;If equal (ZF=1) then nothing there
	MOV	AX,[BX]
	STI				;Interrupts back on
	POP	BX			;Recover registers
	POP	DS
	RET	2			;Throw away flags

INTERCEPT_KEYBOARD_IO	ENDP

;-----------------------------------------------------------------;
;This procedure initializes the interrupt vectors.		  ;
;-----------------------------------------------------------------;
INIT_VECTORS	PROC	NEAR
	ASSUME	DS:VECTORS
	PUSH	DS			;Save old Data Segment
	MOV	AX,VECTORS		;Set up the data segment for vectors
	MOV	DS,AX
	CLI				;Dont allow interrupts

	MOV	AX,WORD PTR KEYBOARD_INT_VECTOR	;Save addresses of BIOS routines
	MOV	WORD PTR ROM_KEYBOARD_INT,AX
	MOV	AX,WORD PTR KEYBOARD_INT_VECTOR[2]
	MOV	WORD PTR ROM_KEYBOARD_INT[2],AX
					;Set up new KEYBOARD_INT vector
	MOV	WORD PTR KEYBOARD_INT_VECTOR,OFFSET INTERCEPT_KEYBOARD_INT
	MOV	WORD PTR KEYBOARD_INT_VECTOR[2],CS
	STI				;Allow interrupts again
					;Set up KEYBOARD_IO vector
	MOV	AX,WORD PTR KEYBOARD_IO_VECTOR
	MOV	WORD PTR ROM_KEYBOARD_IO,AX
	MOV	AX,WORD PTR KEYBOARD_IO_VECTOR[2]
	MOV	WORD PTR ROM_KEYBOARD_IO[2],AX
	MOV	WORD PTR KEYBOARD_IO_VECTOR,OFFSET INTERCEPT_KEYBOARD_IO
	MOV	WORD PTR KEYBOARD_IO_VECTOR[2],CS
					;Now set up the the kbd buffer, etc.

	ASSUME	DS:ROM_BIOS_DATA
	MOV	AX,ROM_BIOS_DATA
	MOV	DS,AX
	CLI				;Dont allow interrupts now
	MOV	BX,OFFSET KB_BUFFER
	MOV	ROM_BUFFER_HEAD,BX
	MOV	WORD PTR [BX],0
	ADD	BX,2
	MOV	ROM_BUFFER_TAIL,BX
	STI				;Allow interrupts again

	MOV	DX,OFFSET INIT_VECTORS	;End of resident portions
	INT	27H			;Terminate but stay resident
INIT_VECTORS	ENDP

CODE_SEG	ENDS

	END	BEGIN
