Category : Utilities for DOS and Windows Machines
Archive   : PRNDSK.ZIP
Filename : PRNDSK.ASM

 
Output of file : PRNDSK.ASM contained in archive : PRNDSK.ZIP
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
lpt2_prb printer_struc
lpt3_prb printer_struc
com1_prb printer_struc
com2_prb printer_struc

; 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 - 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
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 ...
call PutChar
mov dl,0Ah ; ...
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


  3 Responses to “Category : Utilities for DOS and Windows Machines
Archive   : PRNDSK.ZIP
Filename : PRNDSK.ASM

  1. Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!

  2. This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.

  3. But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/