Metropoli BBS
VIEWER: lptx.asm MODE: TEXT (CP437)
title LPTx : Line PrinTer Output Capture Routine
page    60,132
;------------------------------------------------------------
;
;       MAIN PROGRAM    Version 7.00
;
;  (C)  Copyright 1987 by Mark DiVecchio, All Rights Reserved
;  (C)  Copyright 1987 by Kepa Zubeldia,  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 7.00               6 Oct 87
;
; Kepa Zubeldia
; Micro Consulting, Inc.
; 1650 Citizens Tower Bldg.
; Oklahoma City, OK 73106
; (405) 528-8133
;
; -Added a pop-up window that allows the control of the redirection from within
;  an application program.
; -Added a switch for monochrome screens combined with color cards, to cancel
;  the color generation and make the window more visible.
; -Deleted the Control-Z that was appended on the last close to a file.
; -Allow for file append when the program is called with the name of a file
;  that already exists. The user still has the option of overwriting the file.
; -Added a switch to perform the append automatically.
; -Corrected the -? switch so it works.
; -Assembled with Microsoft MASM V5.0
;
; The major change is the addition of the pop-up window. The code for this was
; taken from PC Magazine, October 13, 1987, page 401, a productivity utility
; written by Jeff Prosise. It works with CGA, MGA, and EGA. With the EGA it
; will also work in 120 cols. and/or 35 or 43 line modes. Exiting the pop-up
; with an EGA leaves the cursor in underline. This is not a bug, but a feature.
;
; The pop-up window lets you turn redirection on and off from within a
; running program. You first must start LPTx from the command line as
; usual :
;
; lptx output.fil
;
; Then you can press Alt-PrtSc and a pop-up window will appear. When it
; does, the up and down arrow keys select a printer and the right and
; left arrow keys select redirection. The pop-up will state "to file"
; or "to printer" indicating the state of the redirection.
;
; Press <Enter> or <Esc> to close the window.
;
; Remember that you must start LPTx from the command line before the
; pop-up will let you turn redirection on or off.
;
;------------------------------------------------------------
; 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
;
.xlist
;-----------------------------------------------------------------
;
; Macros
;
disply  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
;-----------------------------------------------------------------
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     00B91h          ;LPTx request flag
ACK             equ     0ABCBh          ;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      '0'                     ;default to '0' just in case
        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
;
;-----------------------------------------------------------------
;
bios_data     segment at 40h
              org 4Ah
crt_cols      dw ?                          ;number of display columns
              org 4Eh
crt_start     dw ?                          ;video page offset address
              org 63h
addr_6845     dw ?                          ;CRTC base address
              org 87h
infobyte      label word
ega_info      db ?                          ;EGA info byte
bios_data     ends
;
;-----------------------------------------------------------------
;
subttl  Main Code
page
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
cerrf_off       dw      0               ;pointer to Critical Error flag
cerrf_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
;
flag_10h           db   0                   ;status of interrupt 10h
flag_13h           db   0                   ;status of interrupt 13h
request_popup      db   0                   ;status of processing request
adapter            db   2                   ;0=MDA, 1=CGA, 2=EGA
;The following 5 lines MUST not be changed. They are dynamically set at Run Time
video_segment      dw   0B800h              ;video segment address
border_attr        db   ?                   ;window border attribute
text_attr          db   ?                   ;window text attribute
menu_attr          db   ?                   ;menu line attribute
video_page         db   ?                   ;current video page
cursor_mode        dw   ?                   ;cursor shape
cursor_pos         dw   ?                   ;cursor position
cursor_addr        dw   ?                   ;cursor CRTC address
new_cursor         dw   0607h               ;Popup cursor shape
nocolors           db   0                   ;monochrome only
menu_choice        db   0                   ;popup cursor line (0,1,2)
screen_buffer      db   48    dup('SCREEN BUFFER   ') ;10 * 34 * 2 bytes
color_attr         db   0,0B8h,10h,07h,3fh  ;black/blue,white/black,hi-white/blue
nocolor_attr       db   0,0B8h,70h,07h,70h  ;mono
mono_attr          db   0,0B0h,70h,07h,70h
enable_values      db   2Ch,28h,2Dh,29h,2Ah,2Eh,1Eh
;
; Old interrupt vector addresses
old_08h dd      0                       ;address of old int 08h routine
old_09h dd      0                       ;address of old int 09h routine
old_10h dd      0                       ;address of old int 10h routine
old_13h dd      0                       ;address of old int 13h 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
;
old1Bh_segment     dw ?                     ;old interrupt 1Bh segment
old1Bh_offset      dw ?                     ;old interrupt 1Bh offset
old23h_segment     dw ?                     ;old interrupt 23h segment
old23h_offset      dw ?                     ;old interrupt 23h offset
old24h_segment     dw ?                     ;old interrupt 24h segment
old24h_offset      dw ?                     ;old interrupt 24h offset
;
; 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
; New Stack for Popup window process
stk3_ss         dw      0               ;caller's stack EA
stk3_sp         dw      0
                db      128 dup('STACKP  ')
stk3            equ     this byte - 4
;
;------------------------------------------------------------------------------
; Window text
;------------------------------------------------------------------------------
;
;You will need an editor capable of handling graphics to edit the window.
;I used PC-Write, but old edlin will also work.
;Do not change the width or the depth of the window !!!
;
window          db "┌──────────────LPTX──────────────┐"
                db "│                                │"
menuline1       db "│       lpt1:  to  PRINTER       │"
menuline2       db "│       lpt2:  to  PRINTER       │"
menuline3       db "│       lpt3:  to  PRINTER       │"
                db "│                                │"
                db "├────────────────────────────────┤"
                db "│ Press:  to move,  to toggle,│"
                db "│       <Enter> to close window. │"
                db "└────────────────────────────────┘"
menufile1       db "│       lpt1:  to  FILE          │"
menufile2       db "│       lpt2:  to  FILE          │"
menufile3       db "│       lpt3:  to  FILE          │"
;------------------------------------------------------------------------------
; Interrupt handler for interrupt 09h (keyboard)
;------------------------------------------------------------------------------
;
keyboard      proc near
              push    ax
              cmp     CS:busy,OFF           ;program already active?
              jne     kb_regular            ; yes, skip processing this key.
              in      al,60h                ;look at the hardware register
              cmp     al,55                 ;is this PrtSc ?
              jne     kb_regular            ; no, process it the old way.
              sti                           ;set interrupt enable flag
              mov     ah,2                  ;get keyboard shift status
              int     16h
              test    al,8                  ;Alt key also pressed?
              je      kb_regular            ; no, process old way.
              mov     CS:request_popup,18   ;set request flag for 1 second.
                                            ;now, call keyboard handling routine
                                            ; to reset keyboard controller
                                            ; and as courtesy to other TSRs.
kb_regular:   pop     ax
              jmp     dword ptr CS:old_09h
keyboard      endp
;
;------------------------------------------------------------------------------
;Interrupt 10h handling routine.
;------------------------------------------------------------------------------
video         proc near
              pushf                         ;push flags onto stack
              inc flag_10h                  ;increment flag
              call dword ptr CS:old_10h     ;call BIOS routine
              dec flag_10h                  ;decrement flag
              iret
video         endp
;
;------------------------------------------------------------------------------
;Interrupt 13h handling routine.
;------------------------------------------------------------------------------
bdisk         proc far
              pushf                         ;push flags onto stack
              inc flag_13h                  ;set 'busy' flag
              call dword ptr CS:old_13h     ;call BIOS routine
              pushf                         ;save output flags
              dec flag_13h                  ;clear flag
              popff                         ;restore output flags
              ret 2                         ;exit without destroying flags
bdisk         endp
;
;
;-----------------------------------------------------------------
;
; 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 lpt1.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
        cmp     CS:flag_10h,0           ;video flag set?
        jne     dectime                 ;yes, then exit
        cmp     CS:flag_13h,0           ;disk flag set?
        jne     dectime                 ;yes, then exit
; if DOS is in its critical section, we skip the write for now and
; hope that we can write before the buffer fills
        push    DS                      ; check the critical section flag
        push    SI
        push    BX
        cmp     CS:cs_switch,OFF        ;is checking off?
        je      no_cs                   ;yes, don't check the flag
        lds     SI,dword ptr CS:csect_off
        cmp     byte ptr [SI],OFF
        jne     poptime                 ;DOS in critical section
                                        ;this indicates that we cannot
                                        ;do any disk operations at this
                                        ;time
        lds     SI,dword ptr CS:cerrf_off
        cmp     byte ptr [SI],OFF
        jne     poptime                 ;DOS in critical error
                                        ;this indicates that we cannot
                                        ;do any disk operations at this
                                        ;time
no_cs:
        cmp     CS:request_popup,0
        je      no_popup
        call    do_popup
no_popup:
        mov     BX,offset lpt1          ;offset to LPT1
        cmp     DS:[BX].request,ON      ;write LPT1 request?
        jne     no_lpt1                 ;no
        call    do_save
no_lpt1:
        mov     BX,offset lpt2          ;offset to LPT2
        cmp     DS:[BX].request,ON      ;write LPT2 request?
        jne     no_lpt2                 ;no
        call    do_save
no_lpt2:
        mov     BX,offset lpt3          ;offset to LPT3
        cmp     DS:[BX].request,ON      ;write LPT3 request?
        jne     poptime                 ;no
        call    do_save
poptime:
        pop     BX
        pop     SI
        pop     DS
dectime:
        cmp     request_popup,0
        je      i08_exit
        dec     request_popup
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
        cmp     CS:flag_10h,0           ;video flag set?
        jne     i28_exit                ;yes, then exit
        cmp     CS:flag_13h,0           ;disk flag set?
        jne     i28_exit                ;yes, then exit
        cmp     CS:cs_switch,OFF        ;is checking off?
        je      i28_no_cs               ;yes, don't check the flag
        push    DS
        push    SI
        lds     SI,dword ptr CS:cerrf_off
        cmp     byte ptr [SI],OFF
        pop     SI
        pop     DS
        jne     i28_exit                ;DOS in critical error
                                        ;this indicates that we cannot
                                        ;do any disk operations at this
                                        ;time
i28_no_cs:
        cmp     CS:request_popup,0
        je      i28_nopopup
        call    do_popup
i28_nopopup:
        call    do_save
i28_exit:
        popff
        iret
int_28h endp
;-----------------------------------------------------------------
;
do_popup proc   near
        mov     CS:busy,ON
        mov     CS:request_popup,0
        cli
        mov     CS:stk3_ss,SS
        mov     CS:stk3_sp,SP
        push    cs
        pop     ss
        mov     sp,offset stk3
        sti                             ;interrupts on
        push    AX                      ; Save everything
        push    BX
        push    CX
        push    DX
        push    DS
        push    ES
        push    SI
        push    DI
        push    BP
        pushf
;
              mov ah,15                     ;get video mode and page
              int 10h
              cmp al,3                      ;mode 0, 1, 2, or 3?
              jbe pu_main1                  ;yes, then continue
              cmp al,7                      ;mode 7?
              je pu_main1                   ;yes, then continue
                                            ;no, we must be in graphics, and
                                            ;I don't know how to open a window
                                            ;while in graphics...
;
;Restore registers and stack before exit.
;
do_pop_exit:
        popff
        pop     BP
        pop     DI
        pop     SI
        pop     ES
        pop     DS
        pop     DX
        pop     CX
        pop     BX
        pop     AX
        cli
        mov     ss,CS:stk3_ss
        mov     sp,CS:stk3_sp
        sti                             ;interrupts on
        mov     CS:busy,OFF
        ret
;
;Set DS and ES segment registers.
;
pu_main1:     push cs                       ;set DS to code segment
              pop ds
              assume ds:cseg
              mov ax,bios_data              ;point ES to BIOS data
              mov es,ax
              assume es:bios_data
              cmp crt_cols,80               ;at least 80 columns displayed?
              jb do_pop_exit                ;no, then exit
;
;Save needed video parameters.
;
              mov video_page,bh             ;active video page
              mov ah,3                      ;get cursor mode and location
              int 10h
              mov cursor_mode,cx
              mov cursor_pos,dx
              mov dx,addr_6845              ;get CRTC base address
              push dx                       ;save it for later
              mov al,14                     ;specify register number
              out dx,al
              inc dx                        ;point DX to data port
              in al,dx                      ;read high byte of address
              mov ah,al                     ;save it
              dec dx                        ;back to index register
              mov al,15                     ;specify register number
              out dx,al
              inc dx                        ;back to data port
              in al,dx                      ;read low byte of address
              mov cursor_addr,ax            ;save cursor address
;
;Determine whether an EGA is present and active in the system.
;
              mov ah,12h                    ;see if EGA is present
              mov bl,10h
              int 10h
              cmp bl,10h                    ;did BL return unchanged?
              je pu_main2                   ;yes, then there's no EGA here
              test ega_info,8               ;is the EGA currently active?
              jnz pu_main2                  ;no, then branch
              mov adapter,2                 ;set ADAPTER for EGA
              push bx                       ;save BX
              mov ax,1130h                  ;get number of scan lines per char
              int 10h
              dec cl                        ;form cursor definition in CX
              mov ch,cl
              sub ch,2
              mov new_cursor,cx             ;save cursor definition
              mov si,offset color_attr      ;point SI to color parms
              pop bx                        ;retrieve BX
              or bh,bh                      ;EGA attached to color monitor?
              je pu_hascolor                ;yes, then branch
              mov si,offset mono_attr       ;no, then point SI to mono parms
              jmp short pu_main4
pu_hascolor:  cmp nocolors,0                ;use monochrome only ?
              je pu_main4                   ; no, use color
              mov si,offset nocolor_attr    ;color card with mono screen
              jmp short pu_main4
;
;Determine whether the active video adapter is a CGA or an MDA.
;
pu_main2:     test addr_6845,40h            ;is bit 6 of the CRTC address set?
              jz pu_main3                   ;no, then it's monochrome
              mov adapter,1                 ;set ADAPTER for a CGA
              mov new_cursor,0607h          ;define cursor shape
              mov si,offset color_attr      ;point SI to color parms
              jmp short pu_hascolor
pu_main3:     mov adapter,0                 ;set ADAPTER for an MDA
              mov new_cursor,0B0Ch          ;define monochrome cursor
              mov si,offset mono_attr       ;point SI to mono parms
;
;Set video parameters for color or monochrome.
;
pu_main4:     push cs                       ;set ES to the code segment
              pop es
              assume es:cseg
              mov di,offset video_segment   ;point DI to destination
              mov cx,5                      ;5 bytes to move
              cld                           ;clear DF
              rep movsb                     ;transfer the values
;
              cmp adapter,1                 ;disable CGA video
              jne pu_main7
              call disable_cga
pu_main7:     call save_screen              ;save memory to be overwritten
              call make_screen              ;open the Pop-up window
              call make_status              ;update status of the redirection
              cmp adapter,1                 ;enable CGA video
              jne pu_main8
              call enable_cga
pu_main8:     mov ah,1                      ;hide the cursor
              mov ch,20h
              int 10h
;
;Wait for a keystroke and return when ESC is pressed.
;
pu_mainA:     call getkey                   ;wait for a keypress
              or al,al                      ;extended code entered?
              je pu_main12                  ;yes, then branch
              cmp al,32                     ;SPACE pressed?
              je toggle                     ;yes, toggle line
              cmp al,13                     ;ENTER pressed?
              je escape                     ;yes, then get out
              cmp al,27                     ;ESC pressed?
              je escape                     ;yes, then close window and return
              jmp pu_mainA                  ;no, then ignore keypress
;
;An extended code was entered.  Check for an Alt-character key combination.
;
pu_main12:    cmp ah,72                     ;UpArrow?
              je upone                      ;yes, then process it
              cmp ah,80                     ;DownArrow?
              je downone                    ;yes, then process it
              cmp ah,75                     ;LeftArrow?
              je toggle                     ;toggle line
              cmp ah,77                     ;RightArrow?
              je toggle                     ;toggle line
              jmp pu_mainA                  ;return for another keypress
;
upone:        cmp menu_choice,0             ;are we at the top ?
              jne upone1                    ; no, decrement it
              mov menu_choice,3             ; yes, wrap around
upone1:       dec menu_choice
              call make_status              ;update status of the redirection
              jmp pu_mainA

downone:      cmp menu_choice,2             ;are we at the bottom ?
              jne downone1                  ; no, increment
              mov menu_choice,-1            ; yes, wrap around
downone1:     inc menu_choice
              call make_status              ;update status of the redirection
              jmp pu_mainA

toggle:       call make_change             ;change the state
              call make_status              ;update status of the redirection
              jmp pu_mainA
;
;Close the Pop-up window in preparation for return.
;
escape:
              cmp adapter,1                 ;disable CGA video
              jne esc1
              call disable_cga
esc1:         call restore_screen           ;restore screen contents
              cmp adapter,1                 ;enable CGA video
              jne esc2
              call enable_cga
;
;Restore the cursor's former position in the BIOS data area and the CRTC.
;
esc2:         mov ah,2                      ;set cursor position thru BIOS
              mov bh,video_page
              mov dx,cursor_pos
              int 10h
              pop dx                        ;recover CRTC base address
              mov cx,cursor_addr            ;restore cursor address
              mov al,14                     ;CRTC register number
              out dx,al
              inc dx
              mov al,ch
              out dx,al                     ;write high byte of address
              dec dx
              mov al,15                     ;CRTC register number
              out dx,al
              inc dx
              mov al,cl
              out dx,al                     ;write low byte
;
;Display the cursor, bypassing EGA cursor emulation logic, and return.
;
              cmp adapter,2                 ;EGA on board?
              jne esc3                      ;no, then branch
              call show_cursor              ;retain current shape
              jmp do_pop_exit               ; exit from popup
esc3:         mov ah,1                      ;restore cursor shape
              mov cx,cursor_mode
              int 10h
              jmp do_pop_exit               ; exit from popup
do_popup endp
;
;------------------------------------------------------------
;
; Writes buffer to disk
;
do_save proc    near
        mov     CS:busy,ON              ; set busy flag
        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
        mov     CS:busy,OFF
        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 lpt1.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 lpt1.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
;--------------------------------------------------------------------

;------------------------------------------------------------------------------
;SAVE_SCREEN saves the contents of the screen that underlie the window.
;------------------------------------------------------------------------------
video_address dw ?                          ;video address
linesum       dw ?
;
save_screen   proc near
              push ds                       ;save DS
              mov ax,bios_data              ;point it to BIOS data area
              mov ds,ax
              assume ds:bios_data
              mov ax,crt_cols               ;get number of display columns
              mov linesum,ax                ;calculate distance from end of
              sub linesum,34                ;  one line to start of next
              shl linesum,1                 ;  accounting for attribute bytes
              mov bl,6                      ;calculate starting addrss (6 lines)
              mul bl                        ;result in AX
              add ax,18                     ;add line offset (18 columns)
              shl ax,1                      ;double result for attr bytes
              mov si,ax                     ;transfer to SI
              add si,crt_start              ;add page offset
              mov video_address,si          ;save offset address
              mov ds,video_segment          ;then set DS to the video segment
              mov di,offset screen_buffer   ;point DI to storage buffer
              mov cx,10                     ;10 lines to save
save_screen1: push cx                       ;save line count
              mov cx,34                     ;34 characters per line
              rep movsw                     ;transfer one line to storage
              pop cx                        ;retrieve line count
              add si,linesum                ;point SI to next video line
              loop save_screen1             ;loop until all lines are saved
              pop ds                        ;restore DS
              assume ds:cseg
              ret                           ;exit
save_screen   endp
;
;------------------------------------------------------------------------------
;RESTORE_SCREEN writes the stored screen image to video memory.
;------------------------------------------------------------------------------
restore_screen proc near
              push es                       ;save ES register value
              mov di,video_address          ;point DI to starting video offset
              mov es,video_segment          ;point ES to video memory
              mov si,offset screen_buffer   ;point DS:SI to screen image
              mov cx,10                     ;10 lines to restore
restore1:     push cx                       ;save line count
              mov cx,34                     ;34 characters per line
              rep movsw                     ;restore one line
              pop cx                        ;retrieve line count
              add di,linesum                ;set DI to next video line
              loop restore1                 ;loop until done
              pop es                        ;restore ES
              ret
restore_screen endp
;
;------------------------------------------------------------------------------
;MAKE_SCREEN writes an image of the Pop-up window to video memory.
;------------------------------------------------------------------------------
make_screen   proc near
              push es                       ;save ES
              mov es,video_segment          ;point ES:DI to video memory
              mov di,video_address
              mov si,offset window          ;line 1
              mov cx,34                     ;do 34 characters
              mov ah,border_attr            ;this makes the top border
make0:        lodsb
              stosw
              loop make0
              add di,linesum
              mov cx,5                      ;5 lines of user text
make1:        push cx
              call formline
              add di,linesum
              pop cx
              loop make1
              mov cx,4                      ;4 lines of instructions on bottom
              mov ah,border_attr
make2:        push cx
              mov cx,34                     ;do next 34 characters
make3:        lodsb
              stosw
              loop make3
              add di,linesum
              pop cx
              loop make2
              pop es                        ;restore ES
              ret
make_screen   endp
;
;------------------------------------------------------------------------------
;MAKE_STATUS writes the new status of the redirection window to video memory.
;------------------------------------------------------------------------------
make_status   proc near
              push es                       ;save ES
              mov es,video_segment          ;point ES:DI to video memory
              mov di,video_address          ;this point to the beginning of w.
              add di,34 * 2                 ;skip over the text
              add di,linesum                ;skip to next line
              add di,34 * 2                 ;skip over the text
              add di,linesum                ;go down one line

              cmp adapter,1                 ;disable CGA video
              jne msts0
              call disable_cga
msts0:
              mov bx,offset lpt1            ;structure #1
              cmp ds:[bx].active,OFF        ;printer redirected ?
              mov si,offset menuline1       ; no, text says PRINTER
              je msts1
              mov si,offset menufile1       ; yes, text says FILE
msts1:        cmp menu_choice,0             ;is this the highlighted choice?
              je msts2
              call formline                 ; no, display normal
              jmp short msts3
msts2:        call cursline                 ; yes, display highlighted
msts3:        add di,linesum                ;go down one line

              mov bx,offset lpt2            ;structure #2
              cmp ds:[bx].active,OFF        ;printer redirected ?
              mov si,offset menuline2       ; no, text says PRINTER
              je msts4
              mov si,offset menufile2       ; yes, text says FILE
msts4:        cmp menu_choice,1             ;is this the highlighted choice?
              je msts5
              call formline                 ; no, display normal
              jmp short msts6
msts5:        call cursline                 ; yes, display highlighted
msts6:        add di,linesum                ;go down one line

              mov bx,offset lpt3            ;structure #3
              cmp ds:[bx].active,OFF        ;printer redirected ?
              mov si,offset menuline3       ; no, text says PRINTER
              je msts7
              mov si,offset menufile3       ; yes, text says FILE
msts7:        cmp menu_choice,2             ;is this the highlighted choice?
              je msts8
              call formline                 ; no, display normal
              jmp short msts9
msts8:        call cursline                 ; yes, display highlighted
msts9:
              cmp adapter,1                 ;enable CGA video
              jne msts10
              call enable_cga
msts10:
              pop es                        ;restore ES
              ret
make_status   endp
;
;------------------------------------------------------------------------------
;MAKE_CHANGE changes the status of the redirection.
;------------------------------------------------------------------------------
make_change   proc near
              push es                       ;save ES

              cmp menu_choice,0             ;is this the highlighted choice?
              jne mkchg1
              mov bx,offset lpt1            ;structure #1
mkchg1:
              cmp menu_choice,1             ;is this the highlighted choice?
              jne mkchg2
              mov bx,offset lpt2            ;structure #2
mkchg2:
              cmp menu_choice,2             ;is this the highlighted choice?
              jne mkchg3
              mov bx,offset lpt3            ;structure #3
mkchg3:
              cmp ds:[bx].active,OFF        ;flip printer redirection
              je mkchg4
              mov ds:[bx].active,OFF        ;turn redirection OFF
              mov ax,ds:[bx].bufcntr        ;look at the buffer size
              or ah,al                      ;is there something in it ?
              jz mkchg5                     ; no, buffer was empty
              mov ds:[bx].request,ON        ; yes, request a buffer flush.
              jmp short mkchg5
mkchg4:       mov ax,ds:[bx].handle         ;get the file handle to check it
              or ah,al                      ;if it has ever been used.
              jz mkchg5                     ; no, don't activate
              mov ds:[bx].active,ON         ; yes, activate redirection
mkchg5:       call click                    ;give a little noisy feedback
              pop es                        ;restore ES
              ret
make_change   endp
;
;------------------------------------------------------------------------------
;FORMLINE is called by MAKE_SCREEN to help with the dirty work.
;------------------------------------------------------------------------------
formline      proc near
              mov ah,border_attr
              lodsb
              stosw
              mov cx,32                     ;do next 32 characters
              mov ah,text_attr
form1:        lodsb
              stosw
              loop form1
              mov ah,border_attr
              lodsb
              stosw
              ret
formline      endp
;
;
;------------------------------------------------------------------------------
;CURSLINE is called by MAKE_SCREEN to help with the dirty work.
;------------------------------------------------------------------------------
cursline      proc near
              mov ah,border_attr
              lodsb
              stosw
              mov cx,32                     ;do next 32 characters
              mov ah,menu_attr
curs1:        lodsb
              stosw
              loop curs1
              mov ah,border_attr
              lodsb
              stosw
              ret
cursline      endp
;
;
;------------------------------------------------------------------------------
;DISABLE_CGA and ENABLE_CGA disable and enable CGA video output.
;------------------------------------------------------------------------------
disable_cga   proc near
              mov dx,3DAh                   ;address of Status Register
disable1:     in al,dx                      ;get status
              test al,8                     ;vertical retrace active?
              je disable1                   ;no, then wait
              sub dx,2                      ;MSR address in DX
              mov al,25h                    ;value to disable video
              out dx,al                     ;disable video output
              ret
disable_cga   endp
;
enable_cga    proc near
              mov ah,15                     ;get video mode
              int 10h
              mov bx,offset enable_values   ;get value to enable display
              xlat                          ;value in AL
              mov dx,3D8h                   ;MSR address
              out dx,al                     ;enable video output
              ret
enable_cga    endp
;
;------------------------------------------------------------------------------
;GETKEY waits for a keypress and returns the keycode in AX.
;Exit:  AX - keycode
;This function is careful to generate int28 for other TSR that may need them
;while the user is waiting to press a key.
;------------------------------------------------------------------------------
getkey        proc near
              mov ah,1                      ;check keyboard buffer
              int 16h
              jne getkey1                   ;jump if buffer contains a keycode
              int 28h                       ;no key pressed - issue int 28h
              jmp getkey                    ;loop back to try again
getkey1:      mov ah,0                      ;get keycode from buffer
              int 16h
              ret                           ;exit with keycode in AX
getkey        endp
;
;
;------------------------------------------------------------------------------
;SHOW_CURSOR displays the cursor and discounts EGA cursor emulation logic.
;Entry:  NEW_CURSOR - starting and ending scan lines
;------------------------------------------------------------------------------
show_cursor   proc near
              cmp adapter,2                 ;is an EGA currently active?
              jne cursor1                   ;no, then branch
              push es                       ;save ES
              mov ax,bios_data              ;point ES to BIOS data area
              mov es,ax
              assume es:bios_data
              push infobyte                 ;save EGA info byte
              or ega_info,1                 ;disable EGA cursor emulation
cursor1:      mov ah,1                      ;display the cursor
              mov cx,new_cursor
              int 10h
              cmp adapter,2                 ;is an EGA active?
              jne cursor_exit               ;no, then exit
              pop infobyte                  ;restore EGA info byte
              pop es                        ;restore ES
              assume es:nothing
cursor_exit:  ret
show_cursor   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
appending       db      0               ;appending to an existing file
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 - V7.00'
                db      LF,CR,'   Copyright 1987 Mark C. DiVecchio'
                db      LF,CR,'   Copyright 1987 Kepa Zubeldia',LF,CR
                db      DOLLAR
lptx_resident   db      LF,CR,'Resident Portion of LPTx Loaded',LF,CR
                db      'The pop-up window will be activated by Alt-PrtSc'
                db      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_mono       db      'Monochrome only, graphic card ignored',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 [?] [-m] [-x] [-l] [-i] {-1,-2,-3} {-c -o -a <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_open       db      'Could not open 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 [?] [-m] [-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      '                          a for append to 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      '           m = monochrome mode only, even with graphics cards'
                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      '    The pop-up window is activated with Alt-PrtSc'
                db      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
        disply  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
        disply  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     cont_scan               ;no
        jmp     make_res                ;yes, then just make LPTx resident
                                        ;if it isn't already

;
; [?]
; HELP printer
;
help:   disply  help_msg                ;display the HELP screen
        jmp     bail_out
;
; [-m]
; Monochrome monitor, even on graphic card
;
mono_only:
        mov     ES:nocolors,1
        disply  lptx_mono
        jmp     short inp_ret
;
; [-a]
; appending to a file, similar to open.
;
append:
        mov     appending,1
        jmp     file_op
;
;
cont_scan:
        cmp     byte ptr DS:[SI],'?'    ;a ?  ?
        je      help                    ;yes
        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", "-a", "-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],'?'    ;help ?
        je      help                    ;yes
        cmp     byte ptr DS:[SI],'o'    ;open a file
        je      file_op                 ;yes
        cmp     byte ptr DS:[SI],'a'    ;append to a file
        je      append                  ;yes
        cmp     byte ptr DS:[SI],'c'    ;close a file
        je      file_cl                 ;yes
        cmp     byte ptr DS:[SI],'m'    ;monochrome only ?
        je      mono_only               ;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:
        disply  lptx_bad                ;incorrect option
        jmp     nor_ex
;
; [-x]
; turn OFF Critical Section check
;
cs_check_off:
        mov     ES:cs_switch,OFF
        disply  lptx_cs_off
        jmp     inp_ret
;
;[-l]
; Turn linefeeds off in captured file
;
lf_off: mov     lfeed,OFF               ;turn LFs off
        disply  lptx_lf
        jmp     inp_ret                 ;continue the scan
;
; [-s]
; turn ON Sound
;
sound_on:
        mov     ES:sound,ON
        disply  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
        disply  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
        disply  lptx_off                ;redirection off message
no_cl:  mov     DI,BX                   ;base of structure
        add     DI,offset lpt1.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 lpt1.filen    ; make sure that we got something
        jne     lptx_make               ;file name was ok
        disply  lptx_creat              ;could not understand the file name
        jmp     nor_exit                ;don't stay resident
nor_ex: jmp     nor_exit

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 lpt1.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: cmp     appending,1             ;are we in append mode?
        je      lptx_x                  ; yes
        disply  lptx_over
        mov     DX,offset yn_max;input buffer
        mov     AH,0AH
        int     DOS_CALL
        cmp     yn_act,0                ;anything typed?
        disply  lptx_cr
        je      lptx_x                  ;no - try to append
        cmp     yn_in,'y'               ;a yes?
        je      lptx_d_yes              ;yes
        cmp     yn_in,'Y'               ;a yes?
        je      lptx_d_yes              ;yes
lptx_x: push    DS                      ;we can't overwrite, try to append
        push    ES
        pop     DS                      ;uses DS:DX
        mov     DX,BX                   ;base of this LPT's structure
        add     DX,offset lpt1.filen    ;file name
        mov     AL,1                    ;open for writing
        mov     AH,open_FILE
        mov     CX,0                    ;normal files only
        int     DOS_CALL                ;find first match
        pop     DS
        jnc     creat_ok
        disply  lptx_open               ;could not open the file
        disply  lptx_nc
        jmp     nor_exit                ;don't stay resident

lptx_d_yes:
        disply  lptx_del
        push    DS
        push    ES
        pop     DS                      ;uses DS:DX
        mov     DX,BX
        add     DX,offset lpt1.filen    ;file name
        mov     AH,delete_FILE
        int     DOS_CALL                ;delete file
        pop     DS
        jnc     lptx_create             ;ok its gone
        disply  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 lpt1.filen    ;file name
        mov     AH,create_FILE
        mov     CX,0                    ;normal files only
        int     DOS_CALL                ;find first match
        pop     DS
        jnc     creat_ok
        disply  lptx_creat              ;could not create the file
        jmp     nor_exit                ;don't stay resident
creat_ok:                               ;now close the file
        push    BX
        mov     ES:[BX].handle,AX       ;save the handle, for popup to know.
        mov     BX,AX                   ;AX was loaded by the create file
                                        ;       call
        mov     AH,close_FILE           ;close the file
        int     DOS_CALL
        pop     BX
        disply  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:
        cmp     flag_27,ON              ;make this one resident?
        je      resident                ;yes
        jmp     nor_exit                ;no
;
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,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,09h                  ;get current vector address for 09h
        mov     AH,35h
        int     DOS_CALL
        mov     word ptr old_09h,BX
        mov     word ptr old_09h[2],ES  ;save it for later use

        mov     AL,10h                  ;get current vector address for 10h
        mov     AH,35h
        int     DOS_CALL
        mov     word ptr old_10h,BX
        mov     word ptr old_10h[2],ES  ;save it for later use

        mov     AL,13h                  ;get current vector address for 13h
        mov     AH,35h
        int     DOS_CALL
        mov     word ptr old_13h,BX
        mov     word ptr old_13h[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,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 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 09h interrupt handler
        mov     AX,2509h                ;set interrupt vector
        mov     DX,offset keyboard      ;Keyboard
        int     DOS_CALL
;
; Set LPTx up as the new int 10h interrupt handler
        mov     AX,2510h                ;set interrupt vector
        mov     DX,offset video         ;Video
        int     DOS_CALL
;
; Set LPTx up as the new int 13h interrupt handler
        mov     AX,2513h                ;set interrupt vector
        mov     DX,offset bdisk         ;Disk
        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 28h interrupt handler
        mov     AX,2528h                ;set interrupt vector
        mov     DX,offset int_28h       ;Idle
        int     DOS_CALL
;
; get the address of the critical section flag
;
        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
;
; get the address of the critical error flag. This piece of code comes from
; PC Magazine, October 13, 1987, page 416, CARDFILE.ASM, by Jeff Prosise.
;
              mov cerrf_seg,ES              ;save the pointer for critical error
              mov ax,3E80h                  ;CMP opcode
              mov cx,2000h                  ;max search length
              mov di,bx                     ;start at critical section address
ce_init4:     repne scasw                   ;do the search
              jcxz ce_init5                 ;branch if search failed
              cmp byte ptr es:[di+5],0BCh   ;verify this is it
              je ce_found                   ;branch if it is
              jmp ce_init4                  ;resume loop if it's not
ce_init5:     mov cx,2000h                  ;search again
              inc bx                        ;search odd addresses this time
              mov di,bx
ce_init6:     repne scasw                   ;look for the opcode
              jcxz ce_notfound              ;not found if loop expires
              cmp byte ptr es:[di+5],0BCh   ;verify this is it
              je ce_found
              jmp ce_init6
ce_notfound:  mov ax,csect_off              ;flag not found, fake it
              jmp ce_end
ce_found:     mov ax,es:[di]                ;get flag offset address
ce_end:       mov cerrf_off,ax              ;save it
;
        pop     BX
        pop     ES
        disply  lptx_resident           ;resident loaded message
        call    stat                    ;display status
        mov     DX,offset end_res
        int     27h                     ;terminate but stay resident
;
; 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, 09h, 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
        disply  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
        disply  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
        disply  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_09h  ;original vector, offset
        mov     AX,word ptr ES:old_09h[2] ;original vector, segment
        mov     DS,AX
        mov     AX,2509h                ;set interrupt vector to DS:DX
        int     DOS_CALL
        pop     DS


        push    DS
        mov     DX,word ptr ES:old_10h  ;original vector, offset
        mov     AX,word ptr ES:old_10h[2] ;original vector, segment
        mov     DS,AX
        mov     AX,2510h                ;set interrupt vector to DS:DX
        int     DOS_CALL
        pop     DS


        push    DS
        mov     DX,word ptr ES:old_13h  ;original vector, offset
        mov     AX,word ptr ES:old_13h[2] ;original vector, segment
        mov     DS,AX
        mov     AX,2513h                ;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

        disply  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
        disply  stat_stat
stat_1: mov     BX,offset lpt1          ;first printer
        mov     stat_ptr,'1'
        disply  stat_lp
        cmp     ES:[BX].active,ON       ;are we active?
        je      stat_1_a                ;yes
        disply  stat_off
        jmp     short stat_2
stat_1_a:
        mov     SI,BX                   ;base
        add     SI,offset lpt1.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
        disply  stat_dir                ;display file name
stat_2:
        mov     BX,offset lpt2          ;second printer
        mov     stat_ptr,'2'
        disply  stat_lp
        cmp     ES:[BX].active,ON       ;are we active?
        je      stat_2_a                ;yes
        disply  stat_off
        jmp     short stat_3
stat_2_a:
        mov     SI,BX                   ;base
        add     SI,offset lpt1.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
        disply  stat_dir                ;display file name
stat_3: mov     BX,offset lpt3          ;third printer
        mov     stat_ptr,'3'
        disply  stat_lp
        cmp     ES:[BX].active,ON       ;are we active?
        je      stat_3_a                ;yes
        disply  stat_off
        jmp     short stat_done
stat_3_a:
        mov     SI,BX                   ;base
        add     SI,offset lpt1.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
        disply  stat_dir                ;display file name
stat_done:
        pop     DI
        pop     SI
        pop     DX
        pop     BX
        pop     AX
        ret
stat    endp
;
cseg    ends
        end     lptx


[ RETURN TO DIRECTORY ]