home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Simtel MSDOS 1992 September
/
Simtel20_Sept92.cdr
/
msdos
/
printer
/
prndsk.arc
/
PRNDSK.ASM
next >
Wrap
Assembly Source File
|
1987-03-03
|
41KB
|
1,789 lines
page 60,132
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
; * *
; * PRNDSK.ASM Version 1.00 (B) *
; * *
; * A utility to redirect output to a printer or comm port to *
; * an MS-DOS disk file. *
; * *
; * Copyright 1987 by David H. Rifkind *
; * *
; * Version 1.00 2 Mar 87 *
; * *
; * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
MAXSPEC equ 80 ; Maximum length of a filespec
;
; Buffer size and flush point definitions
;
buffer_size equ 800h ; Size of each device buffer
buffer_mark equ 400h ; Flush point for all buffers
;
; Beginning of program segment
;
Code SEGMENT para
assume cs:Code,ds:Code
; PSP structure definitions
org 2Ch
env_seg_ptr label word ; Environment segment pointer
org 81h
command_line label byte ; Program command line tail
;
; Beginning of COM binary image
;
org 100h
Start: jmp PrnDsk ; COM entry point vector
program_id db "PRNDSK 1.00",0 ; Program identification
id_length equ $-program_id
;
; Resident data area
;
tsr_pid dw ? ; Program ID of TSR part of PRNDSK
dos_version db ? ; DOS major version number
reentry_flag db 0 ; Non-zero indicates our stack in use
under_0Ch_flag db 0 ; DOS service 1 <= AH <= 0Ch in use
over_0Ch_flag db 0 ; DOS service 0 or > 0Ch in use
defer_flag db 0 ; Non-zero indicates buffer(s) waiting
crit_flag db ? ; Non-zero indicates critical error
ctxt_caller dw ? ; InContext/OutContext return address
user_pid dw ? ; User's (calling program's) PID
user_ax dw ? ; User's AX (temporary storage)
user_sp dw ? ; User's SP
user_ss dw ? ; and SS
user_cc db ? ; User's break (control-C) check flag
old_int_14h dw ?,? ; Original vectors for INT 14h,
old_int_17h dw ?,? ; INT 17h,
old_int_21h dw ?,? ; and INT 21h
old_int_24h dw ?,? ; User's critical error vector
;
; Printer Redirection Block structure definition
;
printer_struc STRUC
prb_buf_ptr dw ? ; Pointer to start of buffer area
prb_buf_size dw ? ; Size of buffer
prb_buf_mark dw ? ; Buffer flush point
prb_buf_count dw ? ; Number of bytes currently in buffer
prb_sys_flags db 0 ; System flag byte (see below)
prb_user_flags db 0 ; User flag byte (see below)
prb_handle dw ? ; File handle for this device
prb_status db ? ; Last DOS file status
prb_filespec db MAXSPEC dup (?) ; Redirection file specification
printer_struc ENDS
; Definitions of bits in prb_sys_flags (prbs_xxxx) and
; prb_user_flags (prbu_xxxx)
prbs_active equ 80h ; 1 indicates device being redirected
prbs_open equ 40h ; 1 indicates redirection file is open
prbu_append equ 80h ; 1 indicates file to be appended
prbu_transp equ 40h ; 1 indicates transparent redirection
; Printer Redirection Blocks - one for each redirectable device
lpt1_prb printer_struc <lpt1_buf,buffer_size,buffer_mark>
lpt2_prb printer_struc <lpt2_buf,buffer_size,buffer_mark>
lpt3_prb printer_struc <lpt3_buf,buffer_size,buffer_mark>
com1_prb printer_struc <com1_buf,buffer_size,buffer_mark>
com2_prb printer_struc <com2_buf,buffer_size,buffer_mark>
; Table of pointers to PRBs
printer_prbs label word ; Pointers to printer PRBs
dw offset lpt1_prb
dw offset lpt2_prb
dw offset lpt3_prb
comm_prbs label word ; Pointers to comm port PRBs
dw offset com1_prb
dw offset com2_prb
page
;
; InContext switches to the TSR's stack area and data segment.
; User registers are saved at fixed locations on the stack (stack_ax,
; stack_es, etc.) and can be directly referenced.
;
InContext PROC near
mov cs:reentry_flag,0FFh ; Set reentry flag
pop cs:ctxt_caller ; Save return address
mov cs:user_ax,ax ; (Temporary)
mov cs:user_sp,sp ; Save user's stack pointer
mov cs:user_ss,ss ; and segment
mov ax,cs
cli
mov ss,ax ; Use our stack pointer
mov sp,offset stack_top
sti
mov ax,cs:user_ax ; Reload AX...
push ax ; ...and store it again
push bx ; Save all registers
push cx
push dx
push bp
push si
push di
push ds
push es
push cs ; Move CS...
pop ds ; ...to DS
jmp ctxt_caller ; Return to caller
InContext ENDP
;
; Switch back to the user's stack and data segment. Registers
; are first restored from our stack; some may have been modified.
;
OutContext PROC near
pop ctxt_caller ; Save return address
pop es ; Restore user's registers
pop ds
pop di
pop si
pop bp
pop dx
pop cx
pop bx
pop ax
cli
mov ss,cs:user_ss ; Restore user's stack pointer
mov sp,cs:user_sp
sti
push cs:ctxt_caller ; Push return address
mov cs:reentry_flag,0h ; Clear reentrancy flag
ret
OutContext ENDP
;
; Prepare for DOS calls from within the TSR. Sets the break check
; flag, critical error vector and current PID.
;
InTSR PROC near
mov ax,3300h ; DOS service:
int 21h ; Get break flag
mov user_cc,dl ; Save user's break flag
mov dl,0h
mov ax,3301h ; DOS service:
int 21h ; Set break flag (0 = no break check)
mov ax,3524h ; DOS service:
int 21h ; Get INT 24h vector
mov old_int_24h+0,bx ; Save user's critical error vector
mov old_int_24h+2,es
mov dx,offset Int_24h ; Point to our critical error handler
mov ax,2524h ; DOS service:
int 21h ; Set INT 24h vector
mov crit_flag,0h ; Clear critical error flag
mov ah,51h ; DOS service:
int 21h ; Get Process ID
mov user_pid,bx ; Save user's PID
mov bx,tsr_pid ; Load our Process ID
mov ah,50h ; DOS service:
int 21h ; Set PID
ret
InTSR ENDP
;
; Restore DOS's state for the user's program. Flags and vectors
; changed by InTSR are restored.
;
OutTSR PROC near
mov bx,user_pid ; Load user's PID
mov ah,50h ; DOS service:
int 21h ; Set Process ID
mov dx,old_int_24h+0 ; Point to user's critical error
mov ax,old_int_24h+2 ; handler
push ds
mov ds,ax
mov ax,2524h ; DOS service:
int 21h ; Set INT 24h vector
pop ds
mov dl,user_cc ; Get user's break flag
mov ax,3301h ; DOS service:
int 21h ; Set break flag
ret
OutTSR ENDP
;
; Critical error (INT 24h) interrupt handler
;
; Called when a critical error occurs while performing DOS
; commands from within the TSR, to prevent unwanted "Retry,
; Ignore, Abort?" messages.
;
Int_24h PROC far
mov cs:crit_flag,0FFh ; Set critical error flag
mov al,0 ; INT 24h "ignore" flag
cmp cs:dos_version,3 ; DOS 3.xx or better?
jb int_24h_exit
mov al,3 ; Yes - return "fail" flag
int_24h_exit:
iret
Int_24h ENDP
page
;
; Flush a redirection buffer. On entry, SI points to the PRB
; for an active device. If the associated file is not yet open,
; it is opened (this can happen only in one special case).
; InTSR should be called BEFORE calling Flush.
;
Flush PROC near
test prb_sys_flags[si],prbs_open
; Is the file open?
jnz flush_write ; Yes - go write to file
call FileOpen ; No - open the file
test prb_status[si],0FFh ; Check for error
jnz flush_exit
flush_write:
mov bx,prb_handle[si] ; BX = file handle
mov cx,prb_buf_count[si] ; CX = number of bytes to write
mov dx,prb_buf_ptr[si] ; DX = address of buffer
mov ah,40h ; DOS service:
int 21h ; Write to file
jc flush_error
test crit_flag,0FFh ; Check for critical error
jnz flush_crit
mov prb_buf_count[si],0 ; Mark buffer as empty
jmp flush_exit
flush_error:
test crit_flag,0FFh
jz flush_err_1
flush_crit:
mov al,83 ; Fake DOS 3.xx critical error code
flush_err_1:
mov prb_status[si],al ; Set file error status byte
flush_exit:
ret
Flush ENDP
;
; Open a redirection file. On entry, SI points to the PRB
; for a device.
;
FileOpen PROC near
test prb_user_flags[si],prbu_append
; File to be appended to?
jz file_create ; No - go truncate file
lea dx,prb_filespec[si] ; DX points to filespec
mov ax,3D01h ; DOS service:
int 21h ; Open file (access = write)
jc open_1
test crit_flag,0FFh ; Check for critical error
jnz open_crit
jmp open_done
open_1: ; Can't open file
cmp al,2 ; Check for "file not found"
jnz open_error ; No - it's a real error
file_create:
lea dx,prb_filespec[si] ; DX points to filespec
mov cx,0 ; CX = attribute (0 = normal file)
mov ah,3Ch ; DOS service:
int 21h ; Create or truncate file
jc open_error
test crit_flag,0FFh ; Check for critical error
jnz open_crit
open_done:
mov prb_handle[si],ax ; Save file handle
or prb_sys_flags[si],prbs_open
; Flag file as open
mov ax,user_pid ; If the user's PID matches ours,
cmp ax,tsr_pid ; the file will be closed on exit
jnz open_2
and prb_sys_flags[si],not prbs_open
; Mark file as closed (!!!)
open_2:
mov bx,prb_handle[si] ; BX = file handle
xor cx,cx ; Set offset (CX and DX) to zero
xor dx,dx
mov ax,4202h ; DOS service:
int 21h ; Move file pointer (to EOF)
jc open_error
test crit_flag,0FFh ; Check for critical error
jz open_exit
jmp open_crit
open_error:
test crit_flag,0FFh
jz open_err_1
open_crit:
mov al,83 ; Fake DOS 3.xx critical error code
open_err_1:
mov prb_status[si],al ; Set file status byte
open_exit:
ret
FileOpen ENDP
;
; Check the current redirection buffer and see if it needs
; to (and can) be flushed. Calls InTSR and OutTSR to set up
; the environment for the Flush call.
;
; Entry: SI points to PRB
;
CheckBuf PROC near
test prb_sys_flags[si],prbs_active
; Redirection active for this device?
jz check_exit ; No - skip it
test prb_status[si],0FFh ; Redirection stopped by error?
jnz check_exit ; Yes - skip it
mov ax,prb_buf_count[si] ; Check whether buffer is over the
cmp ax,prb_buf_mark[si] ; trigger point
jb check_exit ; No - don't flush now
test over_0Ch_flag,0FFh ; Test whether DOS is idle
jnz cant_flush ; Not idle - can't flush
cmp dos_version,3 ; DOS version 3 or better...
jae check_buf_1 ; ...doesn't care about 1 - 12
test under_0Ch_flag,0FFh
jnz cant_flush ; Not idle - can't flush
check_buf_1:
call InTSR ; Set up for DOS access
call Flush ; Flush the buffer
call OutTSR ; Put DOS back the way you found it
check_exit:
ret
cant_flush:
mov defer_flag,0FFh ; Request for service when DOS
; becomes free
ret
CheckBuf ENDP
;
; Add a character to a redirection buffer and call CheckBuf
; to try to flush it.
;
; Entry: AL = character
; SI points to PRB
;
BufChar PROC near
mov di,prb_buf_count[si] ; Get buffer index
cmp di,prb_buf_size[si] ; Check for space in buffer
jnb buf_char_1 ; Full - discard this character
mov bx,prb_buf_ptr[si] ; Pointer to start of buffer
mov [bx+di],al ; Store character in buffer
inc di ; Increment buffer count
mov prb_buf_count[si],di ; and store it
buf_char_1:
call CheckBuf ; Now try to flush the buffer
ret
BufChar ENDP
page
;
; DOS service (INT 21h) interrupt handler
;
; This routine replaces the MS-DOS INT 21h service request
; handler. It keeps track of entries and exits to make sure
; that DOS will not be called reentrantly when flushing a
; redirection buffer.
;
Int_21h PROC far
sti
cmp ah,0h ; Service 0h uses second stack
jz over_0Ch
cmp ah,4Bh ; EXEC requires special treatment
jz service_4Bh
cmp ah,50h ; Services 50h and 51h use first
jz under_0Ch ; stack (DOS 2.xx only, but not
cmp ah,51h ; worth the trouble to handle
jz under_0Ch ; as a special case)
cmp ah,0Ch ; All other services over 0Ch use
ja over_0Ch ; second stack
under_0Ch:
mov cs:under_0Ch_flag,0FFh ; Flag DOS as busy
pushf
call dword ptr cs:old_int_21h
; Call the old INT 21h handler
mov cs:under_0Ch_flag,0h ; Clear "DOS busy" flag
jmp int_21h_1
over_0Ch:
mov cs:over_0Ch_flag,0FFh ; Flag DOS as busy
pushf
call dword ptr cs:old_int_21h
; Call the old INT 21h handler
mov cs:over_0Ch_flag,0h ; Clear the "DOS busy" flag
jmp int_21h_1
service_4Bh:
mov cs:over_0Ch_flag,0FFh ; Flag DOS busy
jmp dword ptr cs:old_int_21h
; Jump directly to old INT 21h
; Now see whether any redirection buffers need flushing.
int_21h_1:
pushf
test cs:reentry_flag,0FFh ; Check for recursive call
jnz int_21h_exit
test cs:defer_flag,0FFh ; Check for deferred service request
jz int_21h_exit
test cs:under_0Ch_flag,0FFh ; Check whether DOS is really idle
jnz int_21h_exit
test cs:over_0Ch_flag,0FFh
jnz int_21h_exit
call InContext ; Use local stack and registers
mov bx,0 ; Start with buffer for LPT1
int_21h_loop:
push bx
shl bx,1 ; Offset into word table
mov si,printer_prbs[bx] ; Get address of PRB
call CheckBuf ; Consider flushing this buffer
pop bx
inc bx ; Next PRB...
cmp bx,5 ; (Highest PRB number is 4)
jb int_21h_loop
mov defer_flag,0h ; Clear deferred service flag
call OutContext ; Restore user's stack and registers
int_21h_exit:
popf
ret 2 ; Return flags to caller
Int_21h ENDP
page
;
; Printer service (INT 17h) interrupt handler
;
; This replaces the original INT 17h handler (and chains to
; it as needed). In addition to redefining the standard printer
; services, it adds seven new ones (function codes 80h to 86h)
; to control redirection.
;
; Original services
; -------- --------
; DX = printer number (0 to 2)
;
; Entry: AH = 0 to print a character
; AL = character to be printed
; AH = 1 to initialize printer
; AH = 2 to return printer status
;
; Exit: AH = printer status:
; Bit 0 = printer timeout
; Bit 3 = I/O error
; Bit 4 = device online
; Bit 5 = out of paper
; Bit 6 = acknowledge
; Bit 7 = printer ready
;
; New Services
; --- --------
; DX = device number (0 to 4) for services 81h to 85h
;
; Entry: AH = 80h to return installed state flag
; Exit: AX = 5A5Ah
; ES points to TSR code segment
;
; Entry: AH = 81h to start redirecting a device
; AL = option flags (prb_user_flags)
; DS:SI points to filespec
; AH = 82h to terminate redirection and close file
; AH = 83h to flush buffer to disk and update file
; Exit: AL = DOS error code (zero if no error) (1 byte only!)
;
; Entry: AH = 84h to return pointer to redirection block
; Exit: ES:BX points to printer redirection block
;
; Entry: AH = 85h to change the option flags
; AL = new option flags
;
; Entry: AH = 86h to return the option flags
; Exit: AL = option flags
;
Int_17h PROC far
test cs:reentry_flag,0FFh ; Reentrant call (unlikely case)
jnz jmp_old_17h ; Yes - can't do anything safely
call InContext ; Switch in our stack and segments
sti ; Allow hardware interrupts
cmp ah,80h ; New service? (80h <= AH <= 85h)
jae new_service
cmp dx,2 ; Check printer number 0 to 2
ja use_old_17h ; Greater than 2 - can't deal with it
mov bx,dx
shl bx,1 ; Times two for word offset
mov si,printer_prbs[bx] ; SI points to PRB
test prb_sys_flags[si],prbs_active
; Device being redirected?
jz use_old_17h ; No - skip it
cmp ah,0
jz prt_char ; Service 0
cmp ah,1
jz prt_init ; Service 1
cmp ah,2
jz prt_stat ; Service 2
jmp use_old_17h ; Unknown service number
new_service:
cmp ah,80h ; Installation check?
jz install_flag ; Yes - don't check DX
cmp dx,4 ; Device number 0 to 4 (3 & 4 are COM)?
ja use_old_17h ; No - can't handle it
mov bx,dx
shl bx,1
mov si,printer_prbs[bx] ; SI points to redirection block
cmp ah,81h
jz set_redir ; Service 81h
cmp ah,82h
jz clear_redir ; Service 82h
cmp ah,83h
jz flush_redir ; Service 83h
cmp ah,84h
jz redir_stat ; Service 84h
cmp ah,85h
jz redir_flags ; Service 85h
cmp ah,86h
jz return_flags ; Service 86h
use_old_17h:
mov ax,stack_ax ; Registers changed by our handler
mov dx,stack_dx
pushf ; Simulate an INT...
call dword ptr old_int_17h ; ...to the old INT 17h handler
mov stack_ax,ax ; Store return value
int_17h_done:
call OutContext ; Restore user registers and segments
int_17h_exit:
iret
jmp_old_17h:
jmp cs:dword ptr old_int_17h
; Routines for individual INT 17h services
prt_char: ; Print a character:
test prb_status[si],0FFh ; Stopped on DOS error?
jnz prt_error ; Yes - don't buffer anything
call BufChar ; No - put character in buffer
prt_init: ; Initialize printer (no operation)
prt_stat: ; Return printer status
test prb_status[si],0FFh ; Stopped on DOS error?
jnz prt_error ; Yes - return printer error
test prb_user_flags[si],prbu_transp
; Transparent redirection?
jnz use_old_17h ; Yes - chain to old handler
mov stack_ah,90h ; Printer OK return code
jmp int_17h_done
prt_error:
mov stack_ah,08h ; Printer I/O error code
jmp int_17h_done
install_flag: ; Return installed state flag
mov stack_es,ds
mov stack_ax,5A5Ah
jmp int_17h_done
set_redir: ; Redirect device
call InTSR
call SetRedir
call OutTSR
return_status: ; Use channel status for return code
mov al,prb_status[si]
mov stack_al,al
jmp int_17h_done
clear_redir: ; Terminate redirection
call InTSR
call ClearRedir
call OutTSR
jmp return_status
flush_redir: ; Flush file to disk
call InTSR
call FlushRedir
call OutTSR
jmp return_status
redir_stat: ; Return PRB pointer
mov stack_bx,si
mov stack_es,ds
jmp int_17h_done
redir_flags: ; Set user flag byte
mov prb_user_flags[si],al
jmp int_17h_done
return_flags: ; Return user flag byte
mov al,prb_user_flags[si]
mov stack_al,al
jmp int_17h_done
Int_17h ENDP
page
;
; Serial device service (INT 14h) interrupt handler
;
; This handler replaces the original INT 14h handler. Like
; the INT 17h handler, it handles redirection to files, but
; adds no new services (the INT 17h services work for both
; printer and comm port redirection).
;
; Entry: DX = comm port number (0 or 1)
; AH = 0 to initialize comm port
; AL = initialization parameters (see Tech. Ref.)
; AH = 1 to transmit a character
; AL = character to send
; AH = 2 to receive a character
; AH = 3 to return serial port status
;
; Exit: AH = comm port status:
; Bit 0 = data ready
; Bit 1 = overrun error
; Bit 2 = parity error
; Bit 3 = framing error
; Bit 4 = break detect
; Bit 5 = xmit holding register empty
; Bit 6 = xmit shift register empty
; Bit 7 = time out or general error
;
; AL = received character for service 2
; AL = line status for service 3 (see Tech. Ref.)
;
Int_14h PROC near
test cs:reentry_flag,0FFh ; Check for reentry
jnz jmp_old_14h ; Reentry - can't do anything
call InContext ; Use our stack and registers
sti ; Allow hardware interrupts
cmp dx,1 ; Comm port number 0 or 1?
ja use_old_14h ; No - can't handle it
mov bx,dx
shl bx,1
mov si,comm_prbs[bx] ; SI points to PRB
test prb_sys_flags[si],prbs_active
; Redirection active for this port?
jz use_old_14h ; No - let the old handler have it
cmp ah,0
jz comm_init ; Service 0
cmp ah,1
jz comm_out ; Service 1
cmp ah,2
jz comm_in ; Service 2
cmp ah,3
jz comm_stat ; Service 3
use_old_14h:
mov ax,stack_ax ; Registers changed by handler
mov dx,stack_dx
pushf ; Simulate and interrupt
call dword ptr old_int_14h ; to the old handler
mov stack_ax,ax ; Save return code
int_14h_done:
call OutContext ; Restore user registers and stack
int_14h_exit:
iret
jmp_old_14h:
jmp dword ptr cs:old_int_14h
; Routines to handle individual INT 14h services
comm_out: ; Output a character
test prb_status[si],0FFh ; Stopped on DOS error?
jnz comm_error
call BufChar ; No - buffer the character
comm_init: ; Initialize comm port (no op)
comm_stat: ; Return comm port status
test prb_status[si],0FFh ; Stopped on error?
jnz comm_error
test prb_user_flags[si],prbu_transp
; Transparent redirection?
jnz use_old_14h ; Yes - chain to old handler
mov stack_ax,6030h ; Return "all OK" code
jmp int_14h_done
comm_in: ; Input from comm port
test prb_user_flags[si],prbu_transp
; Transparent redirection...
jnz use_old_14h ; ...uses old handler...
; ...else, return an error
comm_error:
mov stack_ax,8000h ; General-purpose serial error code
jmp int_14h_done
Int_14h ENDP
page
;
; Set device redirection. Close any file already associated with
; this device, then copy the new filespec and user flags and open
; the file. If this is the first call to SetRedir (i.e., PRNDSK
; is just in the process of installing itself), the file will end
; up open but flagged as closed, and will actually be closed when
; the program exits (see FileOpen for details).
;
SetRedir PROC near
call ClearRedir ; Close old file (ignore return code)
mov al,stack_al ; Copy user flags (transparent and
mov prb_user_flags[si],al ; append mode bits)
mov es,stack_ds
mov bx,stack_si ; ES:BX points to filespec
lea di,prb_filespec[si] ; DS:DI is destination for filespec
mov cx,MAXSPEC-1 ; Maximum non-zero characters to copy
copy_spec:
mov al,byte ptr es:[bx] ; Move one character
mov byte ptr [di],al
inc bx ; Advance character pointers
inc di
test al,al ; Check for end of name
loopnz copy_spec ; Repeat until end
mov byte ptr [di],0 ; Just in case of truncation
or prb_sys_flags[si],prbs_active
; Flag device as active
call FileOpen ; Go open the file
ret
SetRedir ENDP
;
; Clear device redirection and close file. If the file is active
; (and not in an error state), its buffers are flushed. The file
; is then closed, regardless of its error state. This routine
; always returns zero in prb_status.
;
ClearRedir PROC near
test prb_sys_flags[si],prbs_active
; Is redirection active?
jz clear_redir_1
test prb_status[si],0FFh ; Skip flush on DOS error
jnz clear_redir_1
call Flush ; Flush the redirection buffer
clear_redir_1:
and prb_sys_flags[si],not prbs_active
; Device no longer active
test prb_sys_flags[si],prbs_open
; Is the file open?
jz clear_redir_2 ; Not open - can't close
mov bx,prb_handle[si] ; BX = file handle
mov ah,3Eh ; DOS service:
int 21h ; Close file
mov crit_flag,0h ; Ignore ALL errors
and prb_sys_flags[si],not prbs_open
; File no longer open
clear_redir_2:
mov prb_status[si],0 ; Clear channel status (just in case)
mov prb_buf_count[si],0 ; Make sure buffer is empty
ret
ClearRedir ENDP
;
; Flush buffer to disk. This does the ol' standard trick of
; duplicating the file handle then closing the duplicate. Note
; that an error trying to duplicate the handle does not cause
; an error status for the channel.
;
FlushRedir PROC near
test prb_sys_flags[si],prbs_active
; Redirection active?
jz flush_redir_exit ; No - can't flush anything
test prb_status[si],0FFh ; Redirection stopped by error?
jnz flush_redir_exit ; Yes - can't flush
call Flush ; Flush redirection buffer
test prb_status[si],0FFh ; Error while flushing buffer?
jnz flush_redir_exit ; Yes - quit while we're ahead
mov bx,prb_handle[si] ; BX = handle to duplicate
mov ah,45h ; DOS service:
int 21h ; Duplicate file handle
jc flush_redir_exit ; Error - just leave quietly
mov bx,ax ; BX = new handle
mov ah,3Eh ; DOS service:
int 21h ; Close file
jc flush_redir_error
test crit_flag,0FFh ; Check for critical errors
jnz flush_redir_crit
jmp flush_redir_exit
flush_redir_error:
test crit_flag,0FFh
jnz flush_redir_1
flush_redir_crit:
mov al,83 ; Fake DOS 3.xx critical error code
flush_redir_1:
mov prb_status[si],al
flush_redir_exit:
ret
FlushRedir ENDP
;
; Reserved stack for interrupt handlers
;
db 1EEh dup (?) ; Total of 200h bytes stack
stack_es dw ?
stack_ds dw ?
stack_di dw ?
stack_si dw ?
stack_bp dw ?
stack_dx dw ?
stack_cx dw ?
stack_bx dw ?
stack_ax label word
stack_al db ?
stack_ah db ?
stack_top equ $ ; Initial value of SP
;
; Device redirection buffers. The actual space for the buffers
; overlays the transient part of the program. Printer output
; (say, via PrtSc) between the time the INT 17h service 81h
; call occurs and the time the main program TSRs could cause
; some pretty interesting problems.
;
lpt1_buf equ $
lpt2_buf equ lpt1_buf+buffer_size
lpt3_buf equ lpt2_buf+buffer_size
com1_buf equ lpt3_buf+buffer_size
com2_buf equ com1_buf+buffer_size
resident_size equ (com2_buf+buffer_size)-Code
; Total size of resident section
page
;
; Transient data section
;
switch_char db ? ; Command line switch character
device_number dw ? ; LPT1, 2, 3 = 0, 1, 2; COM1, 2 = 4, 5
user_filespec db MAXSPEC dup (?) ; User-input filespec
dos_filespec db MAXSPEC dup (?) ; DOS's translated filespec
switch_byte db 0 ; Command line switches:
sw_a equ 80h ; /A - append
sw_c equ 40h ; /C - close
sw_f equ 20h ; /F - flush
sw_n equ 10h ; /N - not transparent
sw_t equ 8h ; /T - transparent
switch_names db "ACFNT " ; Switch characters (8 bytes)
leave_resident db 0 ; Non-zero to leave via TSR
prb_ptr dw ?,? ; Pointer to device redirection block
device_names label byte
db "LPT1",0 ; The first five names consist of
db "LPT2",0 ; five bytes apiece, and are used
db "LPT3",0 ; by DevStat as well as GetDevice
db "COM1",0
db "COM2",0
db "PRN",0
db "AUX",0
db 0
device_ids db 0,1,2,3,4,0,3 ; Translates PRN -> LPT1, AUX -> COM1
bad_dos_msg db "Incompatible DOS version",0Dh,0Ah,0
bad_dev_msg db "Unknown device name",0Dh,0Ah,0
bad_switch_msg db "Unknown or illegal switch",0Dh,0Ah,0
bad_cmd_msg db "Unrecognized command",0Dh,0Ah,0
not_there_msg db "Redirection software not installed",0Dh,0Ah,0
general_msg db "General-purpose error message",0Dh,0Ah,0
five_spaces db " ",0
gen_err_msg db "DOS error code 0x",0
dos_messages label byte
db "File not found",0Dh,0Ah,0 ; 2
db "Path not found",0Dh,0Ah,0 ; 3
db "Too many open files",0Dh,0Ah,0 ; 4
db "Access denied",0Dh,0Ah,0 ; 5
db "Invalid handle",0Dh,0Ah,0 ; 6
db "Critical I/O error",0Dh,0Ah,0 ; 83
dos_codes db 2,3,4,5,6,83 ; DOS codes for the error messages
; above
page
;
; COM program entry point
;
PrnDsk PROC near
mov ah,30h ; DOS service:
int 21h ; Get DOS version number
mov dos_version,al ; Save version number
test al,al ; DOS 1.x?
jnz prndsk_1 ; No - breathe a sigh of relief
mov bx,offset bad_dos_msg ; "Incompatible DOS version"
call PutStr
mov ah,0 ; DOS service:
int 21h ; Exit program (DOS 1.x style)
prndsk_1:
mov ax,3700h ; DOS service:
int 21h ; Get switch character
mov switch_char,dl
; Get initial switches and device name from command line.
mov si,offset command_line
call SkipBl
call GetSwitch
jc bad_switch
call GetDevice
jnc prndsk_device ; Branch if device name found
; No device name found. Next character had better be a CR.
test switch_byte,not (sw_c+sw_f)
; Only /C and /F allowed here
jnz bad_switch
call SkipBl
cmp al,0Dh ; End of line?
jnz bad_command ; No - something's wrong
call IsThere ; Check that PRNDSK is installed
jnz not_there
call FormOne ; Go handle the command
jmp prndsk_done
; Device name found. Look for more switches and "="
prndsk_device:
mov device_number,dx ; Save the device number
call SkipBl
call GetSwitch
jc bad_switch
call SkipBl
cmp al,'='
jz prndsk_spec ; Branch if "=" found
test switch_byte,not (sw_c+sw_f+sw_n+sw_t)
; Allowed here: /C/F/N/T
jnz bad_switch
cmp al,0Dh ; End of line better be next
jnz bad_command ; No - something's wrong
call IsThere ; Can't do if PRNDSK not installed
jnz not_there
call FormTwo ; Go handle the command
jmp prndsk_done
; "=" found after device name. Get file specification and
; any switches following it.
prndsk_spec:
inc si ; Skip the "="
call SkipBl
call GetSpec
call GetSwitch
jc bad_switch
test switch_byte,not (sw_a+sw_t)
; Only /A and /T allowed
jnz bad_switch
call SkipBl
cmp al,0Dh ; Followed by end of line?
jnz bad_command
call FormThree ; Handle the command
jmp prndsk_done
bad_switch: ; Unknown or illegal switch seen
mov bx,offset bad_switch_msg
jmp error_message
bad_command: ; Bad device name or other error
mov bx,offset bad_cmd_msg
jmp error_message
not_there: ; PRNDSK wasn't found in memory
mov bx,offset not_there_msg
jmp error_message
error_message:
call PutStr
prndsk_done:
test leave_resident,0FFh ; Have the vectors been installed?
jnz prndsk_tsr ; Yes - leave PRNDSK in memory
prndsk_exit:
mov ax,4C00h ; DOS service:
int 21h ; Exit with return code = 0
prndsk_tsr:
mov es,env_seg_ptr ; ES points to PRNDSK's environment
mov ah,49h ; DOS service:
int 21h ; Free allocated memory
mov dx,(resident_size+15)/16
; DX = resident length (paragraphs)
mov ax,3100h ; DOS service:
int 21h ; Terminate and Keep Resident
PrnDsk ENDP
;
; Handle commands of the form PRNDSK[/switches]
;
FormOne PROC near
test byte ptr switch_byte,0FFh
jz list_all
test byte ptr switch_byte,sw_c
jnz close_all
test byte ptr switch_byte,sw_f
jnz flush_all
ret
list_all: ; PRNDSK <cr> - device status list
mov dx,0 ; Start with device 0 (PRN)
list_loop:
push dx
call DevStat ; Give status of device
pop dx
inc dx ; Next device
cmp dx,5
jb list_loop
ret
close_all: ; PRNDSK/C - close all devices
mov dx,0
close_loop:
push dx
mov ah,82h ; Extended printer service:
int 17h ; Close and terminate
pop dx
inc dx
cmp dx,5
jb close_loop
ret
flush_all: ; PRNDSK/F - flush all channels
mov dx,0
flush_loop:
push dx
mov ah,83h ; Extended printer service:
int 17h ; Flush to disk
pop dx
inc dx
cmp dx,5
jb flush_loop
ret
FormOne ENDP
;
; Handle commands of the form PRNDSK dev[/switches]
;
FormTwo PROC near
test byte ptr switch_byte,0FFh
jz list_dev
test byte ptr switch_byte,sw_c
jnz close_dev
test byte ptr switch_byte,sw_f
jnz flush_dev
form_2_1:
test byte ptr switch_byte,sw_t
jnz dev_transp
test byte ptr switch_byte,sw_n
jnz not_transp
ret
list_dev: ; PRNDSK device <cr>
mov dx,device_number
call DevStat
ret
close_dev: ; PRNDSK device/C
mov dx,device_number
mov ah,82h ; Extended printer service:
int 17h ; Close and terminate redirection
test al,al
jnz form_2_error
ret
flush_dev: ; PRNDSK device/F
mov dx,device_number
mov ah,83h ; Extended printer service:
int 17h ; Flush to disk
test al,al
jnz form_2_error
jmp form_2_1
dev_transp: ; PRNDSK device/T
mov bl,prbu_transp
dev_transp_1:
mov dx,device_number
mov ah,86h ; Extended printer service:
int 17h ; Get user flag byte
and al,not prbu_transp
or al,bl
mov ah,85h ; Extended printer service:
int 17h ; Set user flag byte
ret
not_transp: ; PRNDSK device/N
mov bl,0h
jmp dev_transp_1
form_2_error:
call DOSError
ret
FormTwo ENDP
;
; Handle a command of the form PRNDSK dev=filespec[/switches]
;
FormThree PROC near
push ds ; Move DS...
pop es ; ...to ES
mov si,offset user_filespec ; DS:SI points to original filespec
mov di,offset dos_filespec ; ES:DI points to translated filespec
mov cx,MAXSPEC
cmp dos_version,3 ; Service 60 doesn't exist
jb form_3_dos_2 ; in DOS 2.xx
mov ah,60h ; DOS service:
int 21h ; Translate filespec
jc form_3_error
jmp form_3_install
form_3_dos_2: ; Copy filespec, convert to upper case
lodsb
call Fold
stosb
test al,al
loopnz form_3_dos_2
form_3_install:
call Install ; Make sure PRNDSK services installed
; Set the "append" and "transparent" bits in the user flag byte
xor al,al
test byte ptr switch_byte,sw_a
jz form_3_1
or al,prbu_append ; /A - set append flag
form_3_1:
test byte ptr switch_byte,sw_t
jz form_3_2
or al,prbu_transp ; /T - set transparent flag
form_3_2:
mov si,offset dos_filespec ; DS:SI points to filespec
mov dx,device_number ; DX = device number
mov ah,81h ; Extended printer service:
int 17h ; Start device redirection
test al,al
jnz form_3_error
ret
form_3_error:
call DOSError
ret
FormThree ENDP
page
;
; Check whether PRNDSK is already installed
;
; Exit: ZF = true if PRNDSK is there
;
IsThere PROC near
mov ah,80h ; Extended printer service:
int 17h ; Return PRNDSK installation flag
cmp ax,5A5Ah
jnz is_there_exit
; Installation flag ok; check program IDs to make sure
mov si,offset program_id
mov di,si
mov cx,id_length
repz cmpsb
is_there_exit:
ret
IsThere ENDP
;
; Make sure PRNDSK is installed - install it if necessary
;
Install PROC near
call IsThere ; Already installed?
jz install_exit ; Yes - we're done
mov ah,51h ; DOS service:
int 21h ; Get Process ID
mov tsr_pid,bx ; Save TSR's PID
mov ax,3521h ; DOS service:
int 21h ; Get interrupt vector 21h
mov old_int_21h+0,bx ; Store old INT 21h vector
mov old_int_21h+2,es
mov dx,offset Int_21h ; Point to our INT 21h handler
mov ax,2521h ; DOS service:
int 21h ; Set interrupt vector 21h
mov ax,3517h ; DOS service:
int 21h ; Get interrupt vector 17h
mov old_int_17h+0,bx ; Store old printer services vector
mov old_int_17h+2,es
mov dx,offset Int_17h ; Point to our INT 17h handler
mov ax,2517h ; DOS service:
int 21h ; Set interrupt vector 17h
mov ax,3514h ; DOS service:
int 21h ; Get interrupt vector 14h
mov old_int_14h+0,bx ; Store old serial services vector
mov old_int_14h+2,es
mov dx,offset Int_14h ; Point to our INT 14h handler
mov ax,2514h ; DOS service:
int 21h ; Set interrupt vector 14h
mov leave_resident,0FFh ; Indicate handlers to be left resident
install_exit:
ret
Install ENDP
;
; Skip blanks on command line
;
; Exit: AL = next non-blank character
;
SkipBl PROC near
skipbl_loop:
mov al,[si]
cmp al,' '
jnz skipbl_exit
inc si
jmp skipbl_loop
skipbl_exit:
ret
SkipBl ENDP
;
; Fold a lower-case character to upper-case
;
; Entry: AL = character
;
Fold PROC near
cmp al,'a'
jb fold_exit
cmp al,'z'
ja fold_exit
sub al,'a'-'A'
fold_exit:
ret
Fold ENDP
;
; Get a list of switches
;
; Entry: SI points to next character on command line
;
; Exit: Switch bits have been added to switch_byte
;
GetSwitch PROC near
next_switch:
call SkipBl
cmp al,switch_char ; Another switch?
jnz get_switch_exit
inc si ; Point to next character
mov al,[si] ; Get switch identifier
call Fold
mov dh,80h ; Bit 7 for first switch in list
mov bx,0 ; Index into switch name table
switch_loop:
cmp al,switch_names[bx] ; Do the names match?
jz got_switch
inc bx ; No - next switch name
shr dh,1 ; and next switch bit
jnc switch_loop
ret ; With carry set
got_switch:
or switch_byte,dh ; Set switch bit
inc si ; Swallow character from command line
jmp next_switch
get_switch_exit:
clc
ret
GetSwitch ENDP
;
; Get a device name from the command line
;
GetDevice PROC near
mov di,offset device_names ; DI points into device name table
xor dx,dx ; DX is entry number in table
get_dev_1:
test byte ptr [di],0FFh ; Found end of table?
jz no_device
xor bx,bx ; BX is offset into device name
get_dev_2:
test byte ptr [di+bx],0FFh ; Found end of name in table?
jz got_device
mov al,[si+bx] ; Get next character from command line
call Fold ; Convert to upper case
cmp al,[di+bx] ; Does it match the device name?
jnz skip_device ; No - skip this name
inc bx ; Advance to next character
jmp get_dev_2
skip_device: ; This device doesn't match - get next
mov al,[di]
inc di ; Advance device name table pointer
test al,al ; until a zero is seen
jnz skip_device
inc dx ; Increment device number
jmp get_dev_1
no_device: ; No match for name - return error
stc
ret
got_device:
add si,bx ; Advance command line pointer
cmp byte ptr [si],':' ; Check for ":" after name
jnz got_dev_1
inc si ; Eat ":"
got_dev_1:
mov bx,dx
mov dl,device_ids[bx] ; This maps PRN to LPT1, AUX to COM1
clc
ret
GetDevice ENDP
;
; Get a filespec from the command line. Characters are copied
; until finding a blank, end of line, or the switch character.
;
; Entry: SI points to command line (current position)
;
; Exit: SI points to first character after filespec
;
GetSpec PROC near
mov di,offset user_filespec ; Destination pointer
mov cx,MAXSPEC-1 ; Maximum characters to copy
spec_loop:
mov al,[si] ; Get next character
cmp al,0Dh ; Check for EOL...
jz got_spec
cmp al,' ' ; ...or blank...
jz got_spec
cmp al,switch_char ; ...or switch character
jz got_spec
mov [di],al ; Store character in buffer
inc si ; Advance pointers
inc di
loop spec_loop
got_spec:
mov byte ptr [di],0 ; Just in case CX ran out
ret
GetSpec ENDP
;
; Print the status of a redirectable device. The device name
; and filespec are printed, along with the transparency flag
; and (if appropriate) error message.
;
; Entry: DX = device number (0 to 4)
;
DevStat PROC near
mov ah,84h ; Extended printer service:
int 17h ; Get PRB pointer
test es:prb_sys_flags[bx],prbs_active
; Redirection active for this device?
jz dev_stat_2
mov prb_ptr+0,bx ; Save PRB pointer
mov prb_ptr+2,es
mov bx,dx ; BX = DX * 5 (offset into device
shl bx,1 ; names table)
shl bx,1
add bx,dx
add bx,offset device_names ; BX points to device name
call PutStr
mov dl,'='
call PutChar
push ds
lds bx,dword ptr cs:prb_ptr ; DS:BX points to PRB...
add bx,prb_filespec ; ...now points to filespec
call PutStr
pop ds
les bx,dword ptr cs:prb_ptr
test es:prb_user_flags[bx],prbu_transp
; Check transparency flag
jz dev_stat_1
mov dl,switch_char ; Print a "/" (or something like it)
call PutChar
mov dl,'T' ; Print a "T"
call PutChar
dev_stat_1:
mov dl,0Dh
call PutChar
mov dl,0Ah
call PutChar
mov al,es:prb_status[bx] ; Get device status byte
test al,al
jz dev_stat_2 ; Branch if no error
push ax
mov bx,offset five_spaces
call PutStr
pop ax
call DOSError
dev_stat_2:
ret
DevStat ENDP
;
; Print a DOS error message
;
; Entry: AL = error code
;
DOSError PROC near
; First, search the dos_codes table to see if there is a
; specific error message for this code.
xor bx,bx
find_message:
test dos_codes[bx],0FFh ; End of table?
jz no_message
cmp al,dos_codes[bx] ; Found a match for error code?
jz got_msg_number
inc bx ; Next table entry
jmp find_message
; No special message, use the general-purpose one.
no_message:
push ax
mov bx,offset gen_err_msg ; "DOS error code 0x"
call PutStr
pop ax
call PutHex ; Print error number
mov dl,0Dh ; Print <CR>...
call PutChar
mov dl,0Ah ; ...<LF>
call PutChar
ret
; BX contains the message number. Scan through the message
; table to find the string represented by that number.
got_msg_number:
mov cx,bx ; CX counts down from message number
mov bx,offset dos_messages ; Point to start of messages
jcxz got_msg_ptr ; Branch for message 0
find_msg_ptr:
mov al,[bx] ; Get message character
inc bx ; and advance pointer
test al,al ; Found end of a string?
jnz find_msg_ptr ; No - continue scanning
loop find_msg_ptr ; Loop till correct number passed
got_msg_ptr:
call PutStr
ret
DOSError ENDP
;
; Print a zero-terminated string
;
; Entry: BX points to string
;
PutStr PROC near
put_str_loop:
mov dl,[bx]
test dl,dl
jz put_str_exit
call PutChar
inc bx
jmp put_str_loop
put_str_exit:
ret
PutStr ENDP
;
; Print a character
;
; Entry: DL = character
;
PutChar PROC near
mov ah,2h
int 21h
ret
PutChar ENDP
;
; Print two hex digits
;
; Entry: AL = hex byte to print
;
PutHex PROC near
push ax
shr al,1
shr al,1
shr al,1
shr al,1
call put_hex_digit
pop ax
and al,0Fh
put_hex_digit:
add al,90h
daa
adc al,40h
daa
mov dl,al
call PutChar
ret
PutHex ENDP
Code ENDS
END Start