title LPTx : Line PrinTer Output Capture Routine
page	60,132
;------------------------------------------------------------
;
;	MAIN PROGRAM	Version 6.00
;
;  (C)	Copyright 1987 by Mark DiVecchio, All Rights Reserved
;
.xlist
;
;This program is released for use in non-commercial environments. I
;ask commercial users to register the program with a $25 copyright fee for
;each site (any number of users and computers) at which the program is used.
;
; DISCLAMER : this program tries to perform a function which is
; not supported by DOS. It will work sometime and will not work
; other times. That kind of explains why you don't see this type
; of program on the market.
; I have tested it under DOS 2.1 and DOS 3.1. Some users have
; reported trouble when running under DOS 3.x and for other users
; it works fine. USE AT YOUR OWN RISK.
;
; Mark C. DiVecchio
; 10435 Mountain Glen Terrace
; San Diego, CA 92131
; 619-566-6810
;------------------------------------------------------------
; Updates for Version 6.00		18 Mar 87
;
; Added use of Timer Interrupt and Idle interrupt to permit
; writing to disk
;
; Added a switch to inhibit the output of linefeed characters when
; capturing a file. Program strips linefeed character at the
; end of the line if you turn on this switch. The switch is -l on the
; command line when you open a capture file.
;
; This version does not use the PSP swapping of previous versions.
;
;------------------------------------------------------------
; Updates for Version 5.02		19 Nov 86
;
; Added -i inactivate option. Must be only option on command line :
;		lptx -i
;
; This version adds a check for DOS interuupt 21h
; function 40h for standard printer device = 0004.
;
; This addition was suggested by Dale Letterman of Seattle.
;
; Assembled using MicroSoft MASM v 4.0
;
; Program is called and used in the same way as version 3.00
;
; I now enter DOS with interrupts disabled.
;
; Added a switch to inhibit the checking of the Critical Section Flag
; Add -x to the command line the first time that you run LPTx.
;
;------------------------------------------------------------
; Updates for Version 5.00		13 May 86
;
; This version also takes over the DOS interuupt 21h and specifically
; checks for function 5.
; If that is the call, LPTx captures the character if LPTx has been
; activated. If it is a DOS call, LPTx assumes that DOS wants LPT1 since
; there is no way for the DOS call to specify a line printer number.
;
; Uses undocumented DOS int 21h calls 50h and 51h.
;	50h	Set new current Program Segment Prefix(PSP) from
;		segment number in BX
;	51h	Get current PSP into BX.
; These calls are used before any file is opened by the resident portion
; of LPTx. There is some concern that DOS puts information about open files
; into the current PSP. Before we open our spooler file, we want to set
; the current PSP to our PSP and then restore it after the file I/O
; is complete. This idea was expressed in PC Magazine May 13, 1986 on page
; 314 in an article by Charles Petzold.
;
;
; This version 5.0 does not obsolete versions 4.0 and 3.0. Those versions
; may work under some conditions where this one does not and vica versa.
;------------------------------------------------------------
; Updates for Version 4.01		5 May 86
;
; Had an error in the way LPTx detected if it was already in memory.
; This error existed from back in version 3.00 and may have been
; the cause of this program locking up the system the very first
; time it was called.
;------------------------------------------------------------
; Updates for Version 4.0		25 April 86
;
; Assembled using MicroSoft MASM v 4.0
;
; Program is called and used in the same way as version 3.00
;
; Modified the code to check if DOS was running when the print interrupt
; occurs. If so the print request is routed back to the regular line
; printer. This will limit the use of this capture program to user
; programs which do their own output without going to DOS.
;
; In turn, this guarantees that we do re-enter DOS.
;
; The trick of saving the DOS stack was dropped in this version and
; I have resorted to another trick which I garnered from the
; following message found on info-ibmpc. I use method number 2.
;
; This version 4.0 does not obsolete version 3.0. That version may
; work under some conditions where this one does not and vica versa.
; This one worked fine for me using DOS 2.1 and 123 version 1.A.
; Will not work with Shift PrtSc.
;
comment *
Date: Thu, 30 Jan 86 08:47:51 est
Subject: File I/O from resident programs
To: allegra!seismo!usc-isib.arpa!info-ibmpc

Regarding opening up a file when you are terminate-and-stay-resident:

Be very careful when you attempt this.  Many an FAT has been eaten for
lunch when I first tried doing it.  Two ways that work like a charm:

1)	Take over interrupt 0x28.  This interrupt gets called by DOS
	while its waiting for a key to be hit. Whenever it does get
	called (your program should not be time critical, btw, as
	this routine is never called from CPU intensive tasks), it
	is safe to do with DOS what you will. (Except for certain
	interruptions, such as Search First and Search Next, which
	either you'll screw-up for the foreground task, or they'll
	screw-up for you.)

2)	Get the Critical Section Flag by issuing an int 21, with ah=0x34.
	This returns a pointer to a flag in ES:BX. When this flag is
	NULL, and interrupts are on, it is safe to play DOS games.
	Unless you are the last program to take over the interrupt,
	don't trust the flag word: many "fine" programs like SideKick
	do not give you a true copy of the flag word on the stack, but
	rather give a simple "pushf" after interrupts are turned off.
* ;end of comment
;
; More information from a message posted on USENIX:
;
comment *
From sdcsvax!ihnp4!timeinc!greenber Mon Jul  1 05:12:16 1985
Date: 30 Jun 85 17:12:37 CDT (Sun)
-----------------------------------------------------------
INT 21 - Internal - Return CritSectFlag Pointer (MSDOS generic)
	  REG AH = 34H
	  On Return:
		    ES:BX points to DOS "Critical Section Flag"
	  When byte pointed to is zero, DOS is supposed to be
	  safe to interrupt. NOT RELIABLE according to Chris
	  Dunford.
		    Examination of DOS 2.10 code in this area
	  indicates that the byte immediately FOLLOWING this
	  "Critical Section Flag" must be 00 to permit the
	  PRINT.COM interrupt to be called. This suggests that
	  checking the WORD pointed to, rather than the BYTE,
	  might increase reliability of the test greatly.
-----------------------------------------------------------
INT 28 - Internal routine for MSDOS
	  This interrupt is called from inside the "get input
from keyboard" routine in DOS, if and only if it is safe to use
INT 21 to access the disk at that time. It is used primarily by
the PRINT.COM routines, but any number of other routines could
be chained to it by saving the original vector, and calling it
with a FAR call (or just JMPing to it) at the end of the new
routine.
	  Until PRINT.COM installs its own routine, this
interrupt vector simply points to an IRET opcode.
-----------------------------------------------------------
* ;end of comment
;
;------------------------------------------------------------
; Updates for Version 3.0
;
; This version is fully compatible with IBM's PRINT command and
; hopefully most other print spoolers. I changed the method by which
; LPTx determines if a resident copy of itself is already in memory.
;
;------------------------------------------------------------
; This program intercepts the BIOS interrupt 17, the line printer
; interrupt. It will redirect the output of LPT1, LPT2, or LPT3 to a disk
; file. All three redirects may be active at the same time.
;
;
; Background:
;
; The basic problem with this type of program is that PC-DOS as written
; by Microsoft is not re-entrant. That means that if DOS is in control when
; the print interrupt occurs, you can not call DOS again to do some other
; function. Therefore, LPTx can not call DOS to write the captured print
; data to disk. Version 3.00 of LPTx tries to get around this by making
; PC-DOS re-entrant. Version 4.00 of LPTx gets around this by not ever
; trying to re-enter DOS.
;
;*******This Program Must be Converted to a .COM file before running ******
; Assemble with :
;	masm	lptx;
;	link	lptx;
;	exe2bin lptx,lptx.com
;	erase lptx.obj
;	erase lptx.exe
;
;-----------------------------------------------------------------
.list
if1
	%out Pass 1 - Including Macros - v6.00
;
.xlist
;-----------------------------------------------------------------
;
; Macros
;
display	macro	msg
	mov	DX,offset msg
	mov	AH,DISPLAY_OUTPUT
	int	DOS_CALL
	endm
;
; For the PC-AT:
; POPF macro described in the IBM Personal Computer
; Seminar Proceedings Volume 2, Number 4 September 1984
; QUOTE
; "If the system microprocessor executes a POPF instruction in either
; the real or the virtual address mode with CPL <= IOPL, then a 
; pending maskable interrupt (the INTR pin active) may be improperly
; recognized after executing the POPF instruction even if maskable
; interrupts were disabled before the POPF instruction and the value 
; popped had IF=0. If the interrupt is improperly recognized, the
; interrupt is still correctly executed. This errata has no effect
; when interrupts are enabled in either real or virtual address mode.
; The errata has no effect in the virtual address mode when
; CPL > IOPL."
;
popff	macro		;use POPFF instead of POPF
	local	popem,skip
			;simulate popping flags using IRET
	jmp	short skip	;jump around iret
popem:
	iret		;pop CS,IP,flags
skip:
	push	CS
	call	popem	;call within segment
			;program will continue here
	endm

;
call_dos	macro		;Enter DOS with interrupts disabled
	cli
	int	DOS_CALL
	sti
	endm
;
;
.list
else
	%out Pass 2
endif
;-----------------------------------------------------------------
NULL		equ	0
OFF		equ	0
ON		equ	1
EMPTY		equ	0
BEL		equ	7
CR		equ	13
LF		equ	10
DOLLAR		equ	'$'
COLON		equ	':'
BACKSLASH	equ	'\'
BLANK		equ	' '
DASH		equ	'-'
DOS_CALL	equ	21h
;
REQ		equ	00B90h	;LPTx request flag
ACK		equ	0ABBBh	;LPTx acknowledge flag
;
BUFSIZE		equ	4096	;size of DMA buffer
DISPLAY_OUTPUT	equ	9	;for DOS call
DEF_DRIVE	equ	19h
CREATE_FILE	equ	3Ch
OPEN_FILE	equ	3Dh
CLOSE_FILE	equ	3Eh
WRITE_FILE	equ	40h
DELETE_FILE	equ	41h
LSEEK_FILE	equ	42h
DEF_PATH	equ	47h
FIND_FILE	equ	4Eh
;-----------------------------------------------------------------
;
p_block	struc
;
; data structure - these variables are used only in the
; memory resident copy of LPTx. BX is set to point to the offset of the
; allocation of this structure for the selected LPT
; NOTE : all of the labels in this structure are required as place
; holders even if not referenced. Used by the initialization calls
; later on.
;
active	db	OFF		;ON = this LPTx is on, OFF = off
handle	dw	NULL		;handle of disk file used by this LPT
filen	db	'a:\lptx'	; space for redirection disk file name
pnum	db	BLANK
	db	'.lst',NULL
	db	'                          '
	db	'                        '
bufcntr	dw	EMPTY		;bytes used in DMA buffer for this LPT
request	db	OFF		;ON indicates a write request is active
				;for this LPT
prt_status	db	10h	;printer status for this LPT
linefeed	db	ON		;ON = output a linefeed
					;OFF = don't output a linefeed
buffer	db	BUFSIZE dup(0)	;data buffer for this LPT
;
p_block	ends
;
;-----------------------------------------------------------------
;
subttl	Main Code
page
%out Assembling CODE Segment
cseg	segment para public 'CODE'
	assume  CS:cseg,DS:nothing,SS:nothing
	org	100h
lptx:	jmp	lptx_start
;
; What follows are three allocations of the Structure p_block
; One for each line printer that we can support.
; With this, all three line printers have DMA buffers and flag
; variables.
; BX is used to point to the offset of the allocation currently in use
; Line printer 1
lpt1		p_block	<,,,'1'>
; Line printer 2
lpt2		p_block	<,,,'2'>
; Line printer 3
lpt3		p_block	<,,,'3'>
;
crit_flag	db	OFF	;set to ON if critical error occured
off_crit	dw	0	;save old critical error address
seg_crit	dw	0
;
csect_off	dw	0	;pointer to Critical Section flag
csect_seg	dw	0
; cs_switch can be cleared by the transient copy of LPTx
cs_switch	db	ON	;enable check of Critical Section flag
P_NORMAL	equ	90h	;Printed selected and ready
P_TIMEOUT	equ	01h	;Time out
save_psp	dw	0	;Save area for User's PSP Segment Address
lptx_psp	dw	0	;Our PSP Segment Address
byte_count	dw	0	;to save DOS byte count
busy		db	OFF	;ON indicates write is taking place
sound		db	OFF	;ON uses speaker to indicate progress of LPTx
; Old interrupt vector addresses
old_08h	dd	0		;address of old int 08h routine
old_17h	dd	0		;address of old int 17h routine
old_21h	dd	0		;address of old int 21h routine
old_28h	dd	0		;address of old int 28h routine
; New Stack for Interrupt 17h
stk1_save	dd	0	;caller's stack EA
		db	128 dup('STACK   ')
stk1		equ	this byte - 4
; New Stack for Interrupts 08h and 28h
stk2_save	dd	0	;caller's stack EA
		db	128 dup('STACK   ')
stk2		equ	this byte - 4
;-----------------------------------------------------------------
;
; Interrupt handler for interrupt 17h
;
int_17h	proc	far
	sti			;interrupts on
	cmp	AH,3		;AH=3 for LPTx Function
	jne	reg_call	;This is a regular print call
	jmp	ret_ack		;This is an LPTx Call
reg_call:
	push	BX
; set up BX to point to the data area for the requested printer
	cmp	DX,0		;lpt1?
	jne	chk_lpt2	;no
	mov	BX,offset lpt1	;offset to LPT1
	jmp	short bx_set
chk_lpt2:
	cmp	DX,1		;lpt2?
	jne	chk_lpt3	;no
	mov	BX,offset lpt2	;offset to LPT2
	jmp	short bx_set
chk_lpt3:
	cmp	DX,2		;lpt3?
	jne	ill_ptr		;no - bad printer number
	mov	BX,offset lpt3	;offset to LPT3
bx_set:	
	cmp	CS:[BX].active,OFF	;are we active?
	je	sleep17		;no
	mov	CS:[BX].prt_status,P_NORMAL	;signal ready status
	cmp	AH,1		;initialize call?
	je	do_nix		;yes	
	cmp	AH,2		;status call?
	je	do_nix		;yes
	cmp	AH,0		;print call?
	jne	do_nix		;no

	jmp	prt_17		;we are active
do_nix:	mov	AH,CS:[BX].prt_status	;return print status
rtn:	pop	BX
	iret
;
ill_ptr:mov	CS:[BX].prt_status,P_TIMEOUT	;time out status
	jmp	do_nix
;
ret_ack:			;return acknowledgement that I'm here
; note : Change REQ & ACK from version to version so two versions
; of LPTx don't get intermixed.
	cmp	DX,REQ		;my flag to detect that LPTx is
				;already loaded and alive.
	jne	ret_nak		;return a NAK
	mov	DX,ACK		;Memory resident LPTx answers with ACK
	push	CS		;now set up ES to point to the resident
	pop	ES		; data area
ret_nak:
	iret			;return to calling program
;
sleep17:pop	BX		;restore BX before we go to sleep
	jmp	dword ptr CS:old_17h	;jump immediate to original handler
;
prt_17:
	push	AX		; Start the print process.
	push	BX		; Character is in AL.
	push	CX
	push	DX
	push	DS
	push	ES
	push	SI
	push	DI
	push	BP
;
	push	CS		; DS is used as the segment register
	pop	DS		; for all data during the interrupt
;
	pushf
	cli
	mov	SI,SS
	mov	word ptr DS:stk1_save+2,SI	;save caller's stack pointer
	mov	SI,SP
	mov	word ptr DS:stk1_save,SI
	mov	SI,CS
	mov	SS,SI			;give me new bigger stack
	mov	SI,offset stk1
	mov	SP,SI
	popff
	mov	DS:[BX].prt_status,P_NORMAL	;signal ready status
					;prt_status is set before
					;the call to prnt so that prnt
					;can change it if the print to
					;disk fails
	call	prnt			;print the character
;
	pushf
	cli
	mov	SI,word ptr DS:stk1_save
	mov	SP,SI			;restore caller's stack pointer
	mov	SI,word ptr DS:stk1_save+2
	mov	SS,SI
	popff

	pop	BP
	pop	DI
	pop	SI
	pop	ES
	pop	DS
	pop	DX
	pop	CX
	pop	BX
	pop	AX
	jmp	do_nix
int_17h	endp
;-----------------------------------------------------------------
;
; Interrupt handler for interrupt 21h
;
int_21h	proc	far
	sti			;interrupts on
	push	BX
	cmp	AH,5		;is this a DOS printer call?
	je	int_21h_5	;yes
	cmp	AH,40h		;is this a DOS write call?
	je	int_21h_40	;yes
	jmp	sleep21		;no - go on to real DOS
; DOS Function 40h - Write to File or Device. DS:DX contains the address
;of data to write. CX contains the byte count. Return AX = byte count.
int_21h_40:				
	cmp	BX,0004			;Standard Printer?
	jne	sleep21			;no - go on to real DOS
; set up BX to point to the data area for the requested printer
; Since we don't know what the standard printer device is, we
; use LPT1
	mov	BX,offset lpt1	;offset to LPT1
	cmp	CS:[BX].active,OFF	;are we active?
	je	sleep21			;no
	push	AX		;now do it
	push	BX
	push	CX
	push	DX
	mov	CS:byte_count,CX	;save byte count
	mov	BX,DX
;DS:BX points to buffer, CX has byte count
	cmp	CX,EMPTY		;check for zero byte count
	je	prt_21_done
loop21_40:	
	mov	AL,[BX]		;get a character
	mov	AH,0		;set up for call to interrupt 17h
	int	17h		;note : int17h returns a printer status
				;in AH but DOS does not define a way
				;to return that status
	inc	BX
	loop	loop21_40
prt_21_done:
	pop	DX
	pop	CX
	pop	BX
	pop	AX
	mov	AX,CS:byte_count;DOS returns byte count
	jmp	exit21		;to return it to the user.
;
int_21h_5:
; DOS Function 5 Printer Output. The Character in DL is output to the
; standard printer device.
;
; set up BX to point to the data area for the requested printer
; Since we don't know what the standard printer device is, we
; use LPT1
	mov	BX,offset lpt1	;offset to LPT1
	cmp	CS:[BX].active,OFF	;are we active?
	je	sleep21			;no

	push	AX
	mov	AH,0		;set up for call to interrupt 17h
	mov	AL,DL		;the character
	int	17h
	pop	AX
				;note : int17h returns a printer status
				;in AH but DOS does not define a way
				;to return it to the user.
exit21:	pop	BX
	popff			;restore flags
	clc			;never an error from us
	ret			;return to caller (regular FAR return)
;
sleep21:pop	BX		;restore BX before we go to sleep
	jmp	dword ptr CS:old_21h
int_21h	endp
;
;-----------------------------------------------------------------
;
; PRNT - Print a character in AL
;
prnt	proc	near
	push	DS
	cmp	DS:[BX].active,OFF
	je	prtext			;nothing there?
	push	AX
	cmp	DS:[BX].bufcntr,BUFSIZE/2	;buffer half full?
	jne	intadd			;no
;
; set write request but don't actually write anything out.
; we hope that the write can take place before the buffer fills up
;
	mov	DS:[BX].request,ON
	cmp	DS:sound,OFF		;sound on?
	je	intadd			;no
	call	horn			;sound horn to indicate buffer
					; write request has been made
;
intadd:	pop	AX
	cmp	AL,LF			;is it a linefeed?
	jne	intnolf			;no
	cmp	DS:[BX].linefeed,OFF	;we are stripping linefeeds?
	je	prtext			;yes
intnolf:
	mov	DI,BX			;offset of this printer's allocation
	add	DI,offset buffer	;add in offset of buffer
	add	DI,DS:[BX].bufcntr	;add in current byte count
	mov	DS:[DI],AL		;stuff it
	inc	DS:[BX].bufcntr
	cmp	DS:[BX].bufcntr,BUFSIZE	;buffer overflow?
	jne	prtext			;no
	mov	DS:[BX].active,OFF	;yes, nothing to do but deactivate
					; LPTx
	cmp	DS:sound,OFF		;sound on?
	je	prtext			;no
	call	beep			;sound beep twice to indicate that
	call	beep			;we have been deactivated
prtext:	pop	DS
	ret				;done
prnt	endp
;-----------------------------------------------------------------
;
; Critical Error Handler
;
crit_int	proc	far		;got critical error
	mov	CS:crit_flag,ON		; set flag
	mov	AL,0			;tells DOS to ignore the
	iret				;error
crit_int	endp
;-----------------------------------------------------------------
;
; Interrupt handler for interrupt 08h - clock ticks
;
; This function is installed as a handler for hardware interrupt
; type 8. It first calls the previous int 08h handler to service
; the INTEL 8259 Programmable Interrupt Controller. Then it checks
; to see if any write requests are pending. If so, it calls do_save
; and flush to write the buffer to the disk. Note that int_08h
; checks the DOS critical section flag : do_save is called only if
; DOS is available.
;
; This function protects itself against secondary invocations by
; means of the global busy flag.
;
int_08h	proc	far
; call original int 8h handler - ALWAYS
	pushf
	call	dword ptr CS:old_08h
; now we can process if possible
	pushf
	cli
	cmp	CS:busy,OFF		;can we process this?
	jne	i08_exit		;no
; if DOS is in its critical section, we skip the write for now and
; hope that we can write before the buffer fills
	cmp	CS:cs_switch,OFF	;is checking off?
	je	no_cs			;yes, don't check the flag
	push	DS			; check the critical section flag
	push	SI
	lds	SI,dword ptr CS:csect_off
	cmp	byte ptr [SI],OFF
	pop	SI
	pop	DS
	jne	i08_exit		;DOS in critical section
					;this indicates that we cannot
					;do any disk operations at this
					;time
no_cs:
; set busy flag
	mov	CS:busy,ON
	call	do_save
	mov	CS:busy,OFF
i08_exit:
	popff
	iret
int_08h	endp
;-----------------------------------------------------------------
;
; Interrupt handler for interrupt 28h - idle
;
; This function is installed as a handler for hardware interrupt
; type 28h. It first calls the previous int 28h handler. Then it checks
; to see if any write requests are pending. If so, it calls do_save
; and flush to write the buffer to the disk. It does not check the
; Critical Section Flag as int 28h handlers are allowed to perform
; DOS disk I/O but not keyboard I/O since most of the time int 28h
; is called by the keyboard I/O routines when they are waiting
; for a key press.
;
; This function protects itself against secondary invocations by
; means of the global busy flag.
;
int_28h	proc	far
; call original int 28h handler - ALWAYS
	pushf
	call	dword ptr CS:old_28h
; now we can process if possible
	pushf
	cli
	cmp	CS:busy,OFF		;can we process this?
	jne	i28_exit		;no
; set busy flag
	mov	CS:busy,ON
	call	do_save
	mov	CS:busy,OFF
i28_exit:
	popff
	iret
int_28h	endp
;------------------------------------------------------------
;
; Writes buffer to disk
;
do_save	proc	near
	push	AX		; Start the print process.
	push	BX		; Character is in AL.
	push	CX
	push	DX
	push	DS
	push	ES
	push	SI
	push	DI
	push	BP
	pushf
	sti			;interrupts on
;
	push	CS		; DS is used as the segment register
	pop	DS		; for all data during the interrupt
;
	pushf
	cli
	mov	SI,SS
	mov	word ptr DS:stk2_save+2,SI	;save caller's stack pointer
	mov	SI,SP
	mov	word ptr DS:stk2_save,SI
	mov	SI,CS
	mov	SS,SI			;give me new bigger stack
	mov	SI,offset stk2
	mov	SP,SI
	popff
;
; check to see if any write requests are active.
; we do not look at the active flag since the print redirection
; may have been inactivated by the time, we are able to write to
; the disk.
;
; set up BX to point to the data area for the requested printer
	mov	BX,offset lpt1	;offset to LPT1
	cmp	DS:[BX].request,ON	;write LPT1 request?
	jne	c28_lpt2		;no
	call	flush
	mov	DS:[BX].request,OFF
c28_lpt2:
	mov	BX,offset lpt2	;offset to LPT2
	cmp	DS:[BX].request,ON	;write LPT2 request?
	jne	c28_lpt3		;no
	call	flush
	mov	DS:[BX].request,OFF
c28_lpt3:
	mov	BX,offset lpt3	;offset to LPT3
	cmp	DS:[BX].request,ON	;write LPT3 request?
	jne	i28_done		;no
	call	flush
	mov	DS:[BX].request,OFF
i28_done:
	pushf
	cli
	mov	SI,word ptr DS:stk2_save
	mov	SP,SI			;restore caller's stack pointer
	mov	SI,word ptr DS:stk2_save+2
	mov	SS,SI
	popff
;
	popff
	pop	BP
	pop	DI
	pop	SI
	pop	ES
	pop	DS
	pop	DX
	pop	CX
	pop	BX
	pop	AX
	ret
do_save	endp
;
;------------------------------------------------------------
;
; FLUSH - Flush print buffer to disk file
;
flush	proc	near
	cmp	DS:[BX].bufcntr,EMPTY	;buffer empty?
	jne	flush_buf		;no, write it to disk
	ret				;exit
flush_buf:
	call	disk_out
	ret
flush	endp
;------------------------------------------------------------
;
; DISK_OUT - write to disk
;
disk_out	proc	near
;PSP
;	push	BX
;	mov	AH,51h			;get current PSP
;	call_dos
;	mov	DS:save_psp,BX		;and save it
;	mov	BX,DS:lptx_psp		;get our PSP
;	mov	AH,50h
;	call_dos		;set it into DOS
;	pop	BX
;
	push	ES
	push	DS
	mov	AX,DS			;set up ES
	mov	ES,AX
	push	BX
	push	ES
	mov	AX,3524h		;get old critical error vector
	call_dos
	mov	DS:off_crit,BX
	mov	DS:seg_crit,ES
	mov	DX,offset crit_int
	mov	AX,2524h	
	call_dos			;trap critical error vector
	mov	DS:crit_flag,OFF	;clear critical error flag
	pop	ES
	pop	BX
	mov	DX,BX			;open file
	add	DX,offset filen		;filename
	mov	AL,1			;open for writing
	mov	AH,open_FILE
	call_dos
	mov	DS:[BX].handle,AX	;file handle
	jc	disk_err		;error
	cmp	DS:crit_flag,ON		;critical error?
	je	disk_err		;yes
	push	BX
	mov	AH,lseek_FILE
	mov	AL,2			;end of file
	mov	CX,0			;offset 0
	mov	DX,0
	mov	BX,DS:[BX].handle
	call_dos
	pop	BX
	jc	disk_err		;some seek error
	cmp	DS:crit_flag,ON		;critical error?
	je	disk_err		;yes
	mov	CX,DS:[BX].bufcntr	;buffer size
	mov	DX,BX			;offset of structure allocation
	add	DX,offset buffer	;add offset of buffer within the
					;	allocation
	push	BX
	mov	AH,write_FILE
	mov	BX,DS:[BX].handle	;file handle
	call_dos			;buffer address is DS:DX
	pop	BX
	jnc	disk_ok
	cmp	DS:crit_flag,ON		;critical error?
	je	disk_err		;yes
	cmp	AX,DS:[BX].bufcntr	;did DOS write it all?
	je	disk_ok			;yes
disk_err:
	call	beep			;ring bell 4 times
	call	beep
	call	beep
	call	beep
	mov	DS:[BX].active,OFF	;turn us off
	mov	DS:crit_flag,OFF	;clear error flag
	mov	DS:[BX].prt_status,P_TIMEOUT	;signal time out error
					;then try to close the file
					;to save what we can
	jmp	disk_close
disk_ok:
	mov	DS:[BX].bufcntr,EMPTY	;set buffer empty
disk_close:
	push	BX
	mov	BX,DS:[BX].handle
	mov	AH,close_FILE		;close the file
	call_dos
	pop	BX
disk_exit:
	pop	DS
	pop	ES
	push	DS
	lds	DX,dword ptr DS:off_crit
	mov	AX,2524h		;restore critical error vector
	call_dos
	pop	DS
; PSP
;	push	BX
;	mov	BX,DS:save_psp		;get user's PSP
;	mov	AH,50h
;	call_dos		;set it back into DOS
;	pop	BX
;
	ret
disk_out	endp
;
;--------------------------------------------------------------
; 
; Routine to sound the beeper
;
note		equ	0a98h	;2712 = 1193180. / 440Hz
beep	proc	near
	push	AX
	push	BX
	push	CX
	push	DX
	mov	AL,0b6h		;select tim 2,lsb,msb,binary
	out	43h,AL		;set up timer chip
	mov	AX,note		;get note
	out	42h,AL		;write timer 2 count:  lsb...
	mov	AL,AH
	out	42h,AL		;...and msb
	in	AL,61h
	mov	AH,AL		;save current port setting
	or	AL,3
	out	61h,AL		;turn speaker on
	mov	CX,07FFFh
beep_lp:loop	beep_lp
	mov	AL,AH
	out	61h,AL		;restore port setting
	mov	CX,03FFFh
beep1_lp:loop	beep1_lp
	pop	DX
	pop	CX
	pop	BX
	pop	AX
	ret			;return to caller
beep	endp
;-------------------------------------------------------------------
; 
; Routine to sound a key click
;
key_clk		equ	036h	;59 = 1193180. / 20,000Hz
click	proc	near
	push	AX
	push	BX
	push	CX
	mov	AL,0b6h		;select tim 2,lsb,msb,binary
	out	43h,AL		;set up timer chip
	mov	AX,key_clk	;get note
	out	42h,AL		;write timer 2 count:  lsb...
	mov	AL,AH
	out	42h,AL		;...and msb
	in	AL,61h
	mov	AH,AL		;save current port setting
	or	AL,3
	out	61h,AL		;turn speaker on
	mov	CX,0FFh
key_lp:loop	key_lp
	mov	AL,AH
	out	61h,AL		;restore port setting
	mov	CX,0FFh
key1_lp:loop	key1_lp
	pop	CX
	pop	BX
	pop	AX
	ret			;return to caller
click	endp
;--------------------------------------------------------------
; 
; Routine to honk the horn
;
horn	proc	near
	push	AX
	push	BX
	push	CX
	push	DX
	mov	AL,0b6h		;select tim 2,lsb,msb,binary
	out	43h,AL		;set up timer chip
	mov	AX,2e9bh	;2712 = 1193180. / 100Hz
	out	42h,AL		;write timer 2 count:  lsb...
	mov	AL,AH
	out	42h,AL		;...and msb
	in	AL,61h
	mov	AH,AL		;save current port setting
	or	AL,3
	out	61h,AL		;turn speaker on
	mov	CX,07FFFh
horn_lp:loop	horn_lp
	mov	AL,AH
	out	61h,AL		;restore port setting
	mov	CX,03FFFh
horn1_lp:loop	horn1_lp
	pop	DX
	pop	CX
	pop	BX
	pop	AX
	ret			;return to caller
horn	endp
;--------------------------------------------------------------------
end_res	db	0
;
; This is the end of the memory resident portion of LPTx
;
;--------------------------------------------------------------------
;
; All of the following data is in the Code Segment
;
mach_type	db	0
DOS_version	db	0		;Major Version Number
		db	0		;Minor Version Number
lfeed		db	ON		;linefeed enable switch
drive		db	0		;default drive number 0=A etc.
flag_27		db	OFF		; 1=make this copy resident
wrong_dos	db	'DOS 2.0 or later required for LPTx',LF,CR,DOLLAR
up_msg		db	'LPTx - Line Printer Redirection Program - V6.00'
		db	LF,CR,'   Copyright 1987 Mark C. DiVecchio',LF,CR
		db	DOLLAR
lptx_resident	db	LF,CR,'Resident Portion of LPTx Loaded',LF,LF,CR
		db	DOLLAR
lptx_err_3	db	'Could not delete file',LF,CR,DOLLAR
lptx_over	db	CR,LF,'File already exists. Do you want to overwrite '
		db	'it? (y or n)  :',DOLLAR
lptx_nc		db	'File selection canceled',CR,LF,DOLLAR
lptx_lf		db	'Stripping Linefeed Characters',CR,LF,DOLLAR
lptx_sd_on	db	'Sound Enabled',CR,LF,DOLLAR
lptx_cs_off	db	'Critical Section Checking Disabled',CR,LF,DOLLAR
lptx_del	db	'File is being overwritten',LF,CR,DOLLAR
lptx_cr		db	LF,CR,DOLLAR
lptx_bad	db	'Invalid Option',LF,CR
		db	'Calling sequence:',LF,CR
		db	'lptx [?] [-x] [-l] [-i] {-1,-2,-3} {-c -o <d:[pathname]filename>}'
		db	LF,CR,DOLLAR
lptx_on		db	LF,CR,'Redirection started. Disk file opened.'
		db	LF,CR,DOLLAR
lptx_off	db	LF,CR,'Redirection ended. Disk file closed.'
		db	LF,CR,DOLLAR
lptx_creat	db	'Could not create the disk file',LF,CR,DOLLAR
lptx_gone	db	LF,'LPTx - Resident portion inactivated',CR,LF,DOLLAR
; HELP screen
help_msg	db	LF,CR,'Calling sequence : ',LF,LF,CR
		db	'LPTx [?] [-x] [-l] [-i] -p -f <[d:][\pathname\pathname]filename>'
		db	LF,LF,CR
		db	'    where  p = printer number : 1, 2, or 3',LF,CR
		db	'           f = function : o for open a print file'
		db	LF,CR
		db	'                          c for close a print file'
		db	LF,CR
		db	'           drive letter & pathname are optional'
		db	LF,CR
		db	'           x = disable check of Critical Section Flag',
		db	LF,CR
		db	'           l = strip Linefeed characters from output',
		db	LF,CR
		db	'           i = inactivate LPTx',LF,CR
		db	'    defaults : p = 1',LF,CR
		db	'               f = o',LF,CR
		db	DOLLAR
;
stat_stat	db	CR,LF,'LPTx Status :',CR,LF,DOLLAR
stat_lp		db	'lpt'
stat_ptr	db	' : ',DOLLAR
stat_off	db	' not redirected',CR,LF,DOLLAR
stat_dir	db	' redirected to disk file '
stat_fn		db	60 dup (BLANK)
;
yn_max		db	2	;max # of char
yn_act		db	0
yn_in		db	2 dup (0)
;
;--------------------------------------------------------------------
;
; This is the main routine which is executed each time that LPTx is 
; called. In this routine, DS points to the data segment which is
; transient. ES points to the data segment which is permanently
; resident. ES:BX points to the data structure for the selected 
; line printer, 1, 2, or 3.
; The offsets are the same for both. If this is the first
; time that LPTx is run, then ES=DS.
;
lptx_start:
	push	DS	;Save DS
	xor	AX,AX	;clear AX for return IP
	push	AX	;put 0 on stack
;
;to check for machine type look at
; F000:FFFE				; I don't use this currently
;	= FF	IBM PC
;	= FE	IBM XT or Portable
;	= FD	IBM PCjr
;	= FC	IBM PC AT
;	= F9	IBM Convertible
	mov	AX,0F000h
	mov	ES,AX
	mov	BX,0FFFEh
	mov	CL,ES:[BX]	;get machine type
	mov	mach_type,CL	;save machine type
;
;PSP
	mov	lptx_psp,DS	;put away our PSP Segment address
;
; get the DOS version number
; returns zero for pre DOS 2.0 releases
	mov	AH,30h
	int	DOS_CALL	;call DOS
	mov	word ptr DOS_version,AX	
; Requires at least version 2.0 and may or may not work
; with versions above 2.1
	cmp	DOS_version,2	;is it DOS 2.+
	jge	dos_ok		;yes
	display	wrong_dos	;print error message
	mov	AH,0
	int	DOS_CALL	;terminate
dos_ok:	mov	AH,def_drive	;get current default drive
	int	DOS_CALL
	mov	drive,AL	;save the drive number
	display	up_msg		;print program ID
	mov	flag_27,OFF	;to not make resident
; is a copy of LPTx already resident in memory?
	mov	DX,REQ		;check if LPTx is already resident
	mov	AH,3		;get status - special call
	int	17h		;call int 17h - BIOS
	cmp	DX,ACK		;my handler sets DX to ACK
				;and sets ES 
	je	in_core		;LPTx is resident - ES loaded with
				; segment address
	mov	flag_27,ON	;to make this copy resident
	push	CS
	pop	ES		;set ES to CS for segment address
	mov	AL,drive
	add	AL,'a'		;make it a letter
	mov	BX,offset lpt1
	mov	ES:[BX].filen,AL	;put it into the filename
	mov	BX,offset lpt2
	mov	ES:[BX].filen,AL	;put it into the filename
	mov	BX,offset lpt3
	mov	ES:[BX].filen,AL	;put it into the filename
; ----------------------------------------------------
in_core:				;ES is ok
; ES now points to resident data area
; set up ES:BX to point to default data structure
	mov	BX,offset lpt1		;offset - default to LPT1
;get options and file name
;scan input line for line printer number
	mov	SI,81h			;starting offset
	mov	CL,DS:80h		;length of input line
	mov	CH,0
	cmp	CX,0			;nothing?
	jne	inp_lp			;no
	jmp	make_res		;yes, then just make LPTx resident
					;if it isn't already
inp_lp:
	;cmp	byte ptr DS:[SI],'?'	;a ?  ?
	;jne	cont_scan		;no
	;jmp	help			;yes - go show help data
cont_scan:
	cmp	byte ptr DS:[SI],'?'	;a ?  ?
	jne	not_Q
	jmp	help			;yes - go show help data
not_Q:
	cmp	byte ptr DS:[SI],dash	;a dash ?
	je	got_opt			;yes
	cmp	byte ptr DS:[SI],CR	;a carriage return?
	je	scan_done		;yes
	cmp	byte ptr DS:[SI],BLANK	;a blank?
	je	inp_ret			;yes
	jmp	no_b			;assume that we got a file name
					;without the -o option
inp_ret:
	inc	SI			;ignore blanks
	loop	cont_scan			;continue to scan
;
; Scan of whole line is complete without a "-o", "-c" or filename
scan_done:
	jmp	make_res
;
got_opt:				;we got an option
	inc	SI			;to option
	cmp	byte ptr DS:[SI],'1'	;LPT1?
	jne	chk_2
	mov	BX,offset lpt1		;offset from ES
	jmp	short inp_ret
chk_2:	cmp	byte ptr DS:[SI],'2'	;LPT2?
	jne	chk_3
	mov	BX,offset lpt2		;offset from ES
	jmp	short inp_ret
chk_3:	cmp	byte ptr DS:[SI],'3'	;LPT3?
	jne	chk_fil
	mov	BX,offset lpt3		;offset from ES
	jmp	short inp_ret
chk_fil:				;is it file?
	cmp	byte ptr DS:[SI],'o'	;open a file
	je	file_op			;yes
	cmp	byte ptr DS:[SI],'c'	;close a file
	je	file_cl			;yes
	cmp	byte ptr DS:[SI],'x'	;inhibit check for Critical Section?
	je	cs_check_off		;yes
	cmp	byte ptr DS:[SI],'l'	;linefeed switch ?
	je	lf_off			;yes
	cmp	byte ptr DS:[SI],'s'	;enable sound?
	je	sound_on		;yes
	cmp	byte ptr DS:[SI],'i'	;inactivate?
	jne	bad_opt
	jmp	inactivate
bad_opt:
	display	lptx_bad		;incorrect option
	jmp	nor_ex
;
; [-x]
; turn OFF Critical Section check
;
cs_check_off:
	mov	ES:cs_switch,OFF
	display	lptx_cs_off
	jmp	inp_ret
;
;[-l]
; Turn linefeeds off in captured file
;
lf_off:	mov	lfeed,OFF		;turn LFs off
	display	lptx_lf
	jmp	inp_ret			;continue the scan
;
; [-s]
; turn ON Sound
;
sound_on:
	mov	ES:sound,ON
	display	lptx_sd_on
	jmp	inp_ret
;
; [-c]
; close output file
;
file_cl:cmp	ES:[BX].active,ON	;are we active?
	jne	no_close		;no
	mov	AL,1AH			;CTRL-Z
	push	DS
	push	ES
	pop	DS			;set DS to point to resident
					;data segment
	call	prnt			;print end of file mark
	call	flush
	pop	DS			;restore DS
	mov	ES:[BX].active,OFF	;make us inactive
	display lptx_off		;redirection off message
no_close:
	jmp	nor_exit		;nothing to close so exit
;
; [-o]
; open a file for ouput
;
file_op:				;get the file name
	inc	SI			;to next chracter
	cmp	byte ptr DS:[SI],BLANK	;a blank?
	jne	no_b			;no
	inc	SI			;skip over blank
no_b:
; at this point, we have found a new file name. We close the old
; file if one was open
	cmp	ES:[BX].active,ON	;are we active?
	jne	no_cl			;no
	mov	AL,1AH			;CTRL-Z
	push	DS
	push	ES
	pop	DS			;set DS to point to resident
					;data segment
	call	prnt			;print end of file mark
	call	flush
	pop	DS			;restore DS
	mov	ES:[BX].active,OFF	;make us inactive
	display lptx_off		;redirection off message
no_cl:	mov	DI,BX			;base of structure
	add	DI,offset filen		;add offset of destination
	push	SI			;save pointer to file name
; search for a drive letter
	inc	SI			;should point to a colon if
					;one is there
	cmp	byte ptr [SI],COLON	;?
	je	got_drive		;yes
get_drive:
	mov	AL,drive		;get drive letter
	add	AL,'a'			;make it a letter
	mov	ES:[DI],AL		;put it in file name
	inc	DI
	mov	byte ptr ES:[DI],COLON	;put in a colon
	inc	DI
	jmp	path_search
got_drive:
	pop	SI			;move pointer back to start
	mov	AL,[SI]			;get the given drive
	mov	ES:[DI],AL		;move it
	sub	AL,'a'			;make it a number
	mov	drive,AL		;save the drive number
	inc	SI
	inc	DI
	mov	byte ptr ES:[DI],COLON
	inc	DI
	inc	SI
	push	SI			;save new start pointer
path_search:
; now search for a backslash which says that a pathname was given
bk_s_lp:cmp	byte ptr [SI],BACKSLASH
	je	got_path		;a path
	cmp	byte ptr [SI],CR	;end of the file name?
	je	get_path		;yes with no path
	inc	SI
	jmp	short bk_s_lp			;loop
get_path:
	mov	byte ptr ES:[DI],BACKSLASH	;create the path
	inc	DI
	mov	DL,drive		;the current drive
	inc	DL			;bump it for DOS
	push	DS
	push	ES
	pop	DS			;set up DS for DOS
	mov	SI,DI			;set up SI for pathname
	mov	AH,def_path		;get current directory
	int	DOS_CALL		;path goes into DS:SI
	pop	DS			;restore DS
	cmp	byte ptr ES:[SI],NULL	;null path?
	je	null_path		;yes - root directory
path_lp:				;now find the end of the string
	cmp	byte ptr ES:[SI],NULL	;null byte marks end of pathname
	je	end_path		;now append the file name
	inc	SI
	jmp	short path_lp
end_path:
	mov	byte ptr ES:[SI],BACKSLASH
	inc	SI
null_path:
	mov	DI,SI			;DI is destination
got_path:
	pop	SI			;restore source of filename
; pick up everything to next blank
get_lp:
	mov	AL,DS:[SI]		;character
	mov	ES:[DI],AL		;put it away
	cmp	AL,CR			;was it a Carriage Return?
	je	end_line
	cmp	AL,BLANK		;was it a space?
	je	end_line
	inc	SI
	inc	DI
	jmp	short get_lp		;no so get next character
end_line:
	mov	byte ptr ES:[DI],NULL	;zero out the CR or blank
					;at the end of the filename
					;it becomes an ASCIIZ string
	sub	DI,BX			;now take out the base and
	cmp	DI,offset filen		; make sure that we got something
	jne	lptx_make		;file name was ok
	display lptx_creat		;could not understand the file name
	jmp	nor_exit		;don't stay resident
nor_ex:	jmp	nor_exit

;make_res:
lptx_make:
; default DTA used by Find File is set by DOS to an offset of
; 80h into this program's Program Segment Prefix
	push	DS
	push	ES
	pop	DS			;uses DS:DX
	mov	DX,BX
	add	DX,offset filen		;file name
	mov	AH,find_FILE
	mov	CX,0			;normal files only
	int	DOS_CALL		;find first match
	pop	DS
	jnc	lptx_d			;file was found
	jmp	lptx_create		;not there - which is ok
;file already exists
lptx_d:	display lptx_over
	mov	DX,offset yn_max;input buffer
	mov	AH,0AH
	int	DOS_CALL
	cmp	yn_act,0		;anything typed?
	display	lptx_cr
	je	lptx_x			;no - exit
	cmp	yn_in,'y'		;a yes?
	je	lptx_d_yes		;yes
	cmp	yn_in,'Y'		;a yes?
	je	lptx_d_yes		;yes
lptx_x:	display	lptx_nc
	jmp	nor_exit	;all done if we can't overwrite
				;see if we should abort the host
lptx_d_yes:
	display lptx_del
	push	DS
	push	ES
	pop	DS			;uses DS:DX
	mov	DX,BX
	add	DX,offset filen		;file name
	mov	AH,delete_FILE
	int	DOS_CALL		;delete file
	pop	DS
	jnc	lptx_create		;ok its gone
	display lptx_err_3		;can't delete it
	jmp	nor_exit
lptx_create:				; create the file
	push	DS
	push	ES
	pop	DS			;uses DS:DX
	mov	DX,BX			;base of this LPT's structure
	add	DX,offset filen		;file name
	mov	AH,create_FILE
	mov	CX,0			;normal files only
	int	DOS_CALL		;find first match
	pop	DS
	jnc	creat_ok
	display lptx_creat		;could not create the file
	jmp	nor_exit		;don't stay resident
creat_ok:				;now close the file
	push	BX
	mov	BX,AX			;AX was loaded by the create file
					;	call
	mov	AH,close_FILE		;close the file
	int	DOS_CALL
	pop	BX
	display	lptx_on
; set the program up for writing
	mov	ES:[BX].bufcntr,EMPTY	;set buffer empty
	mov	ES:[BX].active,ON	;set us on
	mov	AL,lfeed
	mov	ES:[BX].linefeed,AL	;save linefeed switch
;make_res:
m_res:
	cmp	flag_27,ON		;make this one resident?
	je	resident		;yes
	jmp	nor_exit		;no
;
make_res:
	cmp	ES:[BX].active, ON
	je 	m_res
	jmp 	lptx_make
resident:
	push	ES
	push	BX
; get old interrupt handler addressses
	mov	AL,17h		;get current vector address for 17h
	mov	AH,35h
	int	DOS_CALL
	mov	word ptr old_17h,BX
	mov	word ptr old_17h[2],ES	;save it for later use

	mov	AL,DOS_CALL		;get current vector address for 21h
	mov	AH,35h
	int	DOS_CALL
	mov	word ptr old_21h,BX
	mov	word ptr old_21h[2],ES	;save it for later use

	mov	AL,08h		;get current vector address for 08h
	mov	AH,35h
	int	DOS_CALL
	mov	word ptr old_08h,BX
	mov	word ptr old_08h[2],ES	;save it for later use

	mov	AL,28h		;get current vector address for 28h
	mov	AH,35h
	int	DOS_CALL
	mov	word ptr old_28h,BX
	mov	word ptr old_28h[2],ES	;save it for later use

;
; Set LPTx up as the new int 17h interrupt handler
	mov	AX,2517h		;set interrupt vector
	mov	DX,offset int_17h	;BIOS printer
	int	DOS_CALL
;
; Set LPTx up as the new int 21h interrupt handler
;	mov	AX,2521h		;set interrupt vector
;	mov	DX,offset int_21h	;DOS Functions
;	int	DOS_CALL
;
;
; Set LPTx up as the new int 08h interrupt handler
	mov	AX,2508h		;set interrupt vector
	mov	DX,offset int_08h	;Timer
	int	DOS_CALL
;
;
; Set LPTx up as the new int 28h interrupt handler
	mov	AX,2528h		;set interrupt vector
	mov	DX,offset int_28h	;Idle
	int	DOS_CALL
;
	mov	AH,34h
	int	DOS_CALL		;Call Special DOS interrupt
					;returns pointer to critical
					;section flag in ES:BX
					;With DOS 2.1, this returns
					;00EC:012D. I used the XRAY
					;program to look at this
					;byte while DOS was running.
	mov	csect_seg,ES		;save the pointer
	mov	csect_off,BX
	pop	BX
	pop	ES
	display lptx_resident		;resident loaded message
	call	stat			;display status
	mov	DX,offset end_res
	int	27h			;terminate but stay resident
;
; [?]
; HELP printer
;
help:	display	help_msg		;display the HELP screen
	jmp	short nor_exit
;
; Normal exit for transient copy of LPTx
nor_exit:
	call	stat			;display status
bail_out:
	mov	AH,0
	int	DOS_CALL		;terminate
;
; [-i]
; unhook LPTx from interrupt vectors 08h, 17h, 21h, and 28h
inactivate:
;
	cmp	word ptr ES:old_17h,0	;is it sill 0?
	je	bail_out		;yes, we weren't installed yet
; flush all buffers
	mov	BX,offset lpt1
	cmp	ES:[BX].active,ON	;are we active?
	jne	no_cl1			;no
	mov	AL,1AH			;CTRL-Z
	push	DS
	push	ES
	pop	DS			;set DS to point to resident
					;data segment
	call	prnt			;print end of file mark
	call	flush
	pop	DS			;restore DS
	mov	ES:[BX].active,OFF	;make us inactive
	display lptx_off		;capturing off message
;
no_cl1:
	mov	BX,offset lpt2
	cmp	ES:[BX].active,ON	;are we active?
	jne	no_cl2			;no
	mov	AL,1AH			;CTRL-Z
	push	DS
	push	ES
	pop	DS			;set DS to point to resident
					;data segment
	call	prnt			;print end of file mark
	call	flush
	pop	DS			;restore DS
	mov	ES:[BX].active,OFF	;make us inactive
	display lptx_off		;capturing off message
;
no_cl2:
	mov	BX,offset lpt3
	cmp	ES:[BX].active,ON	;are we active?
	jne	no_cl3			;no
	mov	AL,1AH			;CTRL-Z
	push	DS
	push	ES
	pop	DS			;set DS to point to resident
					;data segment
	call	prnt			;print end of file mark
	call	flush
	pop	DS			;restore DS
	mov	ES:[BX].active,OFF	;make us inactive
	display lptx_off		;capturing off message
no_cl3:
	push	DS
	mov	DX,word ptr ES:old_17h	;original vector, offset
	mov	AX,word ptr ES:old_17h[2];original vector, segment
	mov	DS,AX
	mov	AX,2517h		;set interrupt vector to DS:DX
	int	DOS_CALL
	pop	DS

	push	DS
	mov	DX,word ptr ES:old_21h	;original vector, offset
	mov	AX,word ptr ES:old_21h[2];original vector, segment
	mov	DS,AX
	mov	AX,2521h		;set interrupt vector to DS:DX
	int	DOS_CALL
	pop	DS


	push	DS
	mov	DX,word ptr ES:old_08h	;original vector, offset
	mov	AX,word ptr ES:old_08h[2];original vector, segment
	mov	DS,AX
	mov	AX,2508h		;set interrupt vector to DS:DX
	int	DOS_CALL
	pop	DS


	push	DS
	mov	DX,word ptr ES:old_28h	;original vector, offset
	mov	AX,word ptr ES:old_28h[2];original vector, segment
	mov	DS,AX
	mov	AX,2528h		;set interrupt vector to DS:DX
	int	DOS_CALL
	pop	DS

	display	lptx_gone		;inactivated
	jmp	bail_out
;------------------------------------------------------------------------
;
; displays the status of each of the three line printers
;
stat	proc	near
; display each LPTx with a message "not redirected"
;			or redirected to <filename>
	push	AX
	push	BX
	push	DX
	push	SI
	push	DI
	display	stat_stat
stat_1:	mov	BX,offset lpt1		;first printer
	mov	stat_ptr,'1'
	display	stat_lp
	cmp	ES:[BX].active,ON	;are we active?
	je	stat_1_a		;yes
	display stat_off
	jmp	short stat_2
stat_1_a:
	mov	SI,BX			;base
	add	SI,offset filen		;offset
	mov	DI,offset stat_fn
stat_1_lp:
	mov	AL,ES:[SI]
	mov	[DI],AL
	inc	SI
	inc	DI
	cmp	AL,NULL			;loop till a null byte is found
	jne	stat_1_lp
	mov	byte ptr [DI],CR
	inc	DI
	mov	byte ptr [DI],LF
	inc	DI
	mov	byte ptr [DI],DOLLAR
	display stat_dir		;display file name
stat_2:
	mov	BX,offset lpt2		;second printer
	mov	stat_ptr,'2'
	display	stat_lp
	cmp	ES:[BX].active,ON	;are we active?
	je	stat_2_a		;yes
	display stat_off
	jmp	short stat_3
stat_2_a:
	mov	SI,BX			;base
	add	SI,offset filen		;offset
	mov	DI,offset stat_fn
stat_2_lp:
	mov	AL,ES:[SI]
	mov	[DI],AL
	inc	SI
	inc	DI
	cmp	AL,NULL			;loop till a null byte is found
	jne	stat_2_lp
	mov	byte ptr [DI],CR
	inc	DI
	mov	byte ptr [DI],LF
	inc	DI
	mov	byte ptr [DI],DOLLAR
	display stat_dir		;display file name
stat_3:	mov	BX,offset lpt3		;third printer
	mov	stat_ptr,'3'
	display	stat_lp
	cmp	ES:[BX].active,ON	;are we active?
	je	stat_3_a		;yes
	display stat_off
	jmp	short stat_done
stat_3_a:
	mov	SI,BX			;base
	add	SI,offset filen		;offset
	mov	DI,offset stat_fn
stat_3_lp:
	mov	AL,ES:[SI]
	mov	[DI],AL
	inc	SI
	inc	DI
	cmp	AL,NULL			;loop till a null byte is found
	jne	stat_3_lp
	mov	byte ptr [DI],CR
	inc	DI
	mov	byte ptr [DI],LF
	inc	DI
	mov	byte ptr [DI],DOLLAR
	display stat_dir		;display file name
stat_done:
	pop	DI
	pop	SI
	pop	DX
	pop	BX
	pop	AX
	ret
stat	endp
;
cseg	ends
%out EOF
	end	lptx
