;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;	FOSSIL/Modem Testing Utility
;;	Copyright (c)  June, 1989
;;	Unique Computing Pty Limited & David Nugent
;;	FidoNet Node 3:632/348.0
;;	Alternet Node 7:833/387.0
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;
; This utility is useful for testing various aspects of FOSSIL-aware software,
; by simulating a modem and enabling the user to set or reset certain status
; information returned by the FOSSIL to an application.
;
; It was created primarily to simulate a remote modem connect for testing
; purposes using a direct wire but allowing direct control from local keyboard.
;
; FUTIL installs as a TSR wedge between the FOSSIL and an application.  It
; requires that a revision 5 FOSSIL driver be installed prior to it, and it
; will capture INT 14H (it looks like a FOSSIL to the application) and
; intercept certain FOSSIL calls in order to carry out its simulation.
;
; This utility is particularly useful because it is release WITH SOURCE.
; It can therefore be modified and re-assembled to carry out any simulation
; required, but this will take a little knowledge in how FOSSIL operates
; and some expertise in PC operations in general.
;
; Please read the accompanying documentation for license details and caveats.
;

; V1.00 June 1989		Original release
; V1.10 July 1989		Small bug fix: purging FOSSIL
;				receive buffer now clears pending input
;				status bit (previously this would look
;				effectively hang a machine with some s/w).

;
; NOTE: MASM 5.10 or TASM 1.0 is required to assembly this program!
;       Earlier versions of MASM will NOT work.
;
; Provides TASM->MASM compatibility

ifdef ??version
	masm51
	quirks
endif

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Definitions/readability section
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

CDSIG		equ 0F23DH		; FUTIL's signature
MAGIC		equ 01954H		; FOSSIL's magic number
DOS		equ 021H		; DOS interrupt
TSR		equ 027H		; .COM TSR call
; Keyboard shift key codes
CTRL		equ 004H		; Ctrl key is down
SHIFTS		equ 003H		; Any shift pressed
LSHIFT		equ 002H		; Left shift
RSHIFT		equ 001H		; Right shift
ALT		equ 008h		; Alt key pressed
NUMLOCK		equ 020H		; Numlock state
; BIOS ports, data area definitions
SH_STATE	equ 0417H		; Shift state byte
KBD_PORT	equ 060H		; Keyboard data port
KBD_CTRL	equ 061h		; Keyboard control port
LSC		equ (LSHIFT or CTRL)	; Shorthand for Ctrl-LShift
KUSED		equ 0FH			; Bits used in shift mask
; FOSSIL status bits
PS_CARRIER	equ	0080H		; Carrier signal from MSREG
PS_RXCHARS	equ	0100H		; Characters in input buffer


;;;;;;;;;;;;;;;;
;;
;; Start program
;;
;;;;;;;;;;;;;;;;

_code segment para public 'code'
	assume CS:_code
	org 100h			; .COM start

; Jump to install (which is unloaded on TSR

start:
	jmp install

;
; Int 14H entry point
;
	even
Int_14:
	jmp SHORT parse

;
; FOSSIL data section
;

oldvec	dd 0				; Old INT 14H vector
	dw MAGIC			; Make it look like a FOSSIL
	dw 001bH			; Revision 5 type
	dw CDSIG
kbdvec	dd 0				; Old INT 09H vector
;
port	dw 0				; Port number
;
; Status information
;
; WARNING: DO NOT CHANGE THE ORDER AND CONTENTS OF THIS SECTION
;          WITHOUT CONSIDERING THE 'HOTKEYS' SECTION BELOW.  THESE
;          VARIABLES CORRESPOND DIRECTLY TO THE HOKEY STRUCTURE!!
;
even
cstate	dw 0				; Carrier detect mask (default no extra)
cdmask	dw -1				; Status mask out bits (default leave alone)
tdata	dw 0				; Ignore transmit data flag
rdata	dw 0				; Receive data flag
isdata	dw 0				; Is data to send flag
strp	dw 0				; Pointer to current string

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;	INT 14H handler
;;	Only a subset of commands are intecepted
;;	Tests code in AH, otherwise passes control
;;	to the installed FOSSIL driver
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

parse	PROC NEAR

	sti
	cld
	cmp DL,BYTE PTR CS:[port]		; Select only for current port
	je @F
	jmp DWORD PTR CS:[oldvec]
@@:
	cmp AH,01H			; Transmit data
	je @istxch
	cmp AH,02H			; Receive data
	je @isrxch
	cmp AH,03H			; Port status
	jne @F
	jmp @isstat
@@:
	cmp AH,0AH			; Receive buffer purge
	je @isrp
	cmp AH,0BH			; Transmit no wait
	je @istxnw
	cmp AH,0CH			; Read, no wait
	je @ispeek
	cmp AH,18H			; Receive block
	jne @F
	jmp @isrxb
@@:
	cmp AH,19H			; Transmit block
	jne @norm
	jmp @istxb


; ---------------------
; Read, non-destructive
; ---------------------

@ispeek:
	cmp CS:[isdata],0		; Is there data to be returned?
	je @norm
	push BX
	mov BX,CS:[strp]
	mov AL,CS:[BX]			; Get next character
	pop BX
	xor AH,AH
	jmp @bye

; ---------------------
; Transmit no wait call
; ---------------------

@istxnw:
	cmp CS:[tdata],0		; Eat character
	je @norm
	mov AX,1			; 1 = successfully sent
	jmp @bye

; -----------------------------------------
; Purge receive buffers (and pending input)
; -----------------------------------------

@isrp:
	mov CS:[isdata],0
	mov CS:[strp],0
	and CS:[cstate],not PS_RXCHARS	; Reset "chrs in rx buf" status bit
	jmp SHORT @norm

; ------------------
; Transmit character
; ------------------

@istxch:
	cmp CS:[tdata],0		; Eat character
	jne @F
	jmp @isstat
@@:
	mov AL,03H			; Force a status call
	jmp @isstat

@norm:
	jmp DWORD PTR CS:[oldvec]	; Jump straight to old vector

; -----------------
; Receive character
; -----------------

@isrxch:
	cmp CS:[isdata],0		; Is there data to be returned?
	je @rch
@isrxch1:
	push BX
	mov BX,CS:[strp]
	mov AX,CS:[BX]			; Get next character
	pop BX
	inc CS:[strp]			; Move pointer to next character
	or AH,AH			; Test for end of string
	jne @F
	and CS:[cstate],not PS_RXCHARS	; Reset "chrs in rx buf" status bit
	mov CS:[isdata],0
@@:
	xor AH,AH
	jmp SHORT @bye

@rch:
	cmp CS:[rdata],0		; Give control to FOSSIL?
	je @norm
	sti
	push AX
	mov AH,3			; Else get status
	int 14H
	test AX,PS_RXCHARS		; Are there characters in buffer?
	pop AX
	je @isrxch
	cmp CS:[isdata],0		; Is there data to be returned?
	jne @isrxch1
	jmp SHORT @norm

; -------------------
; Transmit block data
; -------------------

@istxb:
	cmp CS:[tdata],0		; Eat character
	je @norm
	mov AX,CX			; Return number of characters 'sent'
	jmp SHORT @bye

; ------------------
; Receive block data
; ------------------

@isrxb:
	cmp CS:[isdata],0		; Is there data to be returned?
	je @norm
	push DI				; Hmm, better get to work ...
	push CX
	push BX

	cld				; Required for string opcodes
	mov BX,CS:[strp]		; Get pointer to string
	jcxz @up			; Opps, zero chrs requested
@@:
	mov AX,CS:[BX]			; Get next character
	inc BX
	stosb				; Store it in user buffer
	or AH,AH			; Test for end of string
	je @F
	loop @B				; Until user buffer full
	jmp SHORT @up
@@:					; No more available at this point
	and CS:[cstate],not PS_RXCHARS	; Reset "rx chrs avail" status bit
	mov CS:[isdata],0
	xor BX,BX
@up:
	mov CS:[strp],BX		; Update string pointer
	mov AX,CX			; Save remaining number of characters

	pop BX				; Restore all registers
	pop CX
	pop DI
	sub AX,CX			; Calculate/return # of bytes received
	neg AX
	jmp SHORT @bye

; -------------------
; Status call handler
; -------------------

@isstat:
	pushf				; Simulate the old INT 14H
	call DWORD PTR CS:[oldvec]
	or AX,CS:[cstate]		; OR  in set mask
	and AX,CS:[cdmask]		; AND out reset mask
@bye:
	iret				; Return to user

parse	ENDP


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Configuration section
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;
; This section contains information which can be modified to suit.
;

;
; Modem control strings
; Various strings returned by a typical modem, available by 'hotkey'
;

; STATUS
nocarrier    db	'NO CARRIER',13,10,0
ok           db 'OK',13,10,0
error        db 'ERROR',13,10,0
busy         db 'BUSY',13,10,0
ring         db 'RING',13,10,0
rring        db 'RRING',13,10,0
voice        db 'VOICE',13,10,0
; CONNECTS
connect      db 'CONNECT',13,10,0
connect300   db 'CONNECT 300',13,10,0
connect1200  db 'CONNECT 1200',13,10,0
connect2400  db 'CONNECT 2400',13,10,0
connect24rel db 'CONNECT 2400/REL',13,10,0
connect9600  db 'CONNECT 9600',13,10,0
connectfast  db 'CONNECT FAST',13,10,0


;
; This next section is a series of structures which allows a 'hotkey'
; combination to be tied to a given action.  The structures contain the
; contents of the INT 14H variables used in the above intercept routines
; and pressing the hotkey combination simply copies in values for these
; variables setting of a reaction by the next intercepted INT 14H call.
;

notkey STRUC
 scanc  db ?		; Keyboard scan code (returned by KB ROM)
 kshft  db ?		; Keyboard shift state (BIOS)
 cmask	dw ?		; Status OR mask (force these bits on)
 cbits  dw ?		; Status AND mask (force these bits off)
 itx	dw ?		; Ignore transmited data flag (don't give to FOSSIL)
 irx	dw ?		; Don't call FOSSIL to receive data flag
 srx	dw ?		; Is there data to 'receive' flag
 dofs	dw ?		; Offset of string to 'receive'
notkey ENDS

SZ_ka	equ <size notkey>		; Size of one hotkey structure

KEYS equ 20				; Defines how many structures to scan

;
; Hot keys table
;
even
KeyCodes label word
notkey <29H, LSC, 0000H, not 0000H, 00H, 00H, 00H, 0           >  ; cs` Turns all processing off
notkey <0bH, LSC, 0000H, not 0080H, 00H, 00H, 00H, 0           >  ; cs0 Force no carrier
notkey <18H, LSC, 0100H, not 0080H, 00H, 01H, 01H, ok          >  ; csO 'OK'
notkey <12H, LSC, 0100H, not 0080H, 00H, 01H, 01H, error       >  ; csE 'ERROR'
notkey <31H, LSC, 0100H, not 0080H, 00H, 01H, 01H, nocarrier   >  ; csN 'NO CARRIER'
notkey <30H, LSC, 0100H, not 0080H, 00H, 01H, 01H, busy        >  ; csB 'BUSY'
notkey <21H, LSC, 0180H, not 0000H, 00H, 01H, 01H, connectfast >  ; csF 'CONNECT FAST'
notkey <0aH, LSC, 0180H, not 0000H, 00H, 01H, 01H, connect9600 >  ; cs9 'CONNECT 9600'
notkey <04H, LSC, 0180H, not 0000H, 00H, 01H, 01H, connect24rel>  ; cs3 'CONNECT 2400/REL'
notkey <03H, LSC, 0180H, not 0000H, 00H, 01H, 01H, connect2400 >  ; cs2 'CONNECT 2400'
notkey <02H, LSC, 0180H, not 0000H, 00H, 01H, 01H, connect1200 >  ; cs1 'CONNECT 1200'
notkey <13H, LSC, 0100H, not 0080H, 00H, 01H, 01H, ring        >  ; csR 'RING'
notkey <2fH, LSC, 0100H, not 0080H, 00H, 01H, 01H, voice       >  ; csV 'VOICE'
notkey <17H, LSC, 0100H, not 0080H, 00H, 01H, 01H, rring       >  ; csI 'RRING'
notkey <00H, 00H, 0000H, not 0000H, 00H, 00H, 00H, 0           >
notkey <00H, 00H, 0000H, not 0000H, 00H, 00H, 00H, 0           >
notkey <00H, 00H, 0000H, not 0000H, 00H, 00H, 00H, 0           >
notkey <00H, 00H, 0000H, not 0000H, 00H, 00H, 00H, 0           >
notkey <00H, 00H, 0000H, not 0000H, 00H, 00H, 00H, 0           >
notkey <00H, 00H, 0000H, not 0000H, 00H, 00H, 00H, 0           >


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;	Keyboard (hardware) interrupt handler
;;
;;	This routine is triggered on make or break (press or release)
;;	of ANY key on the keyboard.  The KB port is then inspected to
;;	see which key was pressed, and the current shift status read.
;;	The above hotkeys table is then scanned for a match; if found,
;;	the rest of the structure is copied into the variable space,
;;	otherwise the call passes to the previous INT 9H handler.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


Int_9 PROC FAR

	assume DS:nothing,ES:nothing	; Could (and will) be anywhere

	push AX				; Must preserve EVERY register here
	push CX
	push DI
	push ES
	xor AX,AX
	mov ES,AX

	in AL,KBD_PORT			; Scan code of key pressed
	mov AH,ES:[SH_STATE]		; Current shift state bits
	and AH,KUSED			; Mask out unused bits
	mov DI,offset KeyCodes		; Scan key codes table
	mov CX,KEYS
@@:
	cmp AX,CS:[DI]
	je @F
	add DI,SZ_ka
	loop @B
					; Key was not found
	pop ES				; Restore saved registers
	pop DI
	pop CX
	pop AX
	jmp CS:[kbdvec]			; Pass though to original vector

;
; Key combo found; take action
;

@@:
	in AL,KBD_CTRL			; Save value of kbd control lines
	mov AH,AL
	or AL,80H			; Set kbd enable bit
	out KBD_CTRL,AL
	mov AL,AH
	out KBD_CTRL,AL
	mov AL,20H			; Signal end of HW interrupt to 8259A
	out 20H,AL
	push BX
	xor BX,BX
	mov CX,SZ_ka/2
	jmp SHORT @F
@Clp:
	mov AX,CS:[DI+BX]		; Copy variables to intercept data area
	mov CS:[BX+offset cstate-2],AX
@@:
	inc BX
	inc BX
	loop @Clp

	pop BX				; Restore registers
	pop ES
	pop DI
	pop CX
	pop AX
	iret

Int_9 ENDP


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;	End resident section
;;
;;	The rest of this code is discarded on installation as a TSR, and
;;	does not consume additional memory
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; Installation messages

msg_nofos    db 7,'No FOSSIL driver installed',13,10,'$'
msg_instald  db 7,'FUTIL is already installed',13,10,'$'
msg_instnot  db 7,'FUTIL is not installed',13,10,'$'
msg_instok   db 'FUTIL is now active',13,10,'$'
msg_inval    db 7,'Invalid command line option',13,10,'$'
msg_unloaded db 'FUTIL successfully unloaded',13,10,'$'
msg_start    db 'FUTIL v1.10  FOSSIL Communications Companion Utility',13,10
             db 'Copyright (C) 1989  David Nugent & Unique Computing Pty Ltd',13,10
             db '$'

; ----------------------
; Installation procedure
; ----------------------

install PROC NEAR
	assume ds:_code				; DS = CS by default
	mov DX,offset msg_start			; Output our logo
	mov AH,9
	int DOS
	cld					; Required for string opcodes
	mov SI,81H				; Start of command line
@Top:
	lodsb
	cmp AL,13				; Test for end of cmdline
	je @Load
	cmp AL,0
	je @Load
	cmp AL,32				; Skip spaces,
	je @Top
	cmp AL,9				; and tabs
	je @Top
	cmp AL,'/'				; Looking for a valid switch
	je @F					; character (- or / will do)
	cmp AL,'-'
	je @F
@nogood:
	mov AL,3				; Something else is invalid
	mov DX,offset msg_inval
	jmp ExitMsg
;
; Read switch value
;
@@:
	lodsb
	cmp AL,'a'				; Convert to uppercase
	jb @F
	cmp AL,'z'
	ja @F
	sub AL,'a'-'A'
@@:
	cmp AL,'U'				; /U = Uninstall
	je @Unload
	cmp AL,'P'				; /P = Port number
	jne @nogood
	lodsb
	cmp AL,'0'				; Convert # to binary
	jb @nogood
	cmp AL,'9'
	ja @nogood
	sub AL,'0'
	xor AH,AH
	mov port,AX				; Save in our local variable
	jmp SHORT @Top

;
; Unload from memory
;

@Unload:
	call ChkLoad				; See if FUTIL is loaded
	cmp ES:[BX+10],CDSIG			; Is this program installed?
	mov DX,offset msg_instnot
	mov AL,2
	jne ExitMsg
	push DS					; Preserve DS for later
	push ES
	push ES
	pop DS
	mov DX,word ptr [kbdvec]
	mov DS,word ptr [kbdvec+2]
	mov AX,2509H				; Release keyboard
	int DOS
	pop DS
	mov DX,word ptr [oldvec]
	mov DS,word ptr [oldvec+2]
	mov AX,2514H				; Release INT 14H
	int DOS
	pop DS
	mov AH,49H				; Deallocate memory (at ES)
	int DOS
	mov DX,offset msg_unloaded
	xor AL,AL
	jmp SHORT ExitMsg

;
; Load into memory
;

@Load:
	call ChkLoad				; Check to see if we're loaded
	cmp ES:[BX+10],CDSIG			; Is this program installed?
	mov DX,offset msg_instald
	mov AL,2
	je ExitMsg
	mov DX,offset Int_14			; Install vector for INT 14H
	mov AX,2514H
	int DOS
	mov AX,3509H				; Get keyboard vector
	int DOS
	mov word ptr [kbdvec],BX
	mov word ptr [kbdvec+2],ES
	mov DX,offset Int_9
	mov AX,2509H				; Intercept keyboard
	int DOS
	mov AX,CS
	mov ES,AX
	mov ES,ES:[02cH]			; Free our environment
	mov AH,049H
	int DOS
	mov DX,offset msg_instok
	mov AH,09H				; Print message
	int DOS
	mov DX,offset msg_nofos			; Stay resident
	int TSR

install ENDP

;
; Exit (abort) with a message
;

ExitMsg	PROC NEAR

	push AX					; Save exit code
	mov AH,09H				; Print message
	int DOS
	pop AX
	mov AH,4cH				; And exit
	int DOS

ExitMsg ENDP

;
; Check to see if already loaded
;

ChkLoad PROC NEAR
	mov AX,3514H				; Get int 14H vector
	int DOS
	mov word ptr [oldvec],BX		; And save it
	mov word ptr [oldvec+2],ES
	cmp ES:[BX+6],MAGIC			; Is a FOSSIL there?
	mov DX,offset msg_nofos
	mov AL,1
	je @F
	pop BX
	mov BX,offset ExitMsg
	push BX
@@:
	ret
ChkLoad ENDP

_code ends

end start
