home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Share Gallery 1
/
share_gal_1.zip
/
share_gal_1
/
UT
/
UT019.ZIP
/
VPRINT.ASM
< prev
next >
Wrap
Assembly Source File
|
1988-03-23
|
73KB
|
1,729 lines
;=======================================================
; VPRINT version 5.00
;
; Printer redirection utility.
; Memory resident software to capture
; printer output and save to disk.
;
; Revision History:
;
; Version Comments
; 1.00 Very primative DOS 1 version, to upgrade EasyWriter
; 2.00 Updated DOS 2 version, not released
; 2.01 Released as U/S
; 3.00 Add int 28H buffer dump, watch dos critical flag before writes.
; 3.01 Correct bug in dos critical byte handling
; 3.02 Add selective trapping of PrtSc operation
; 4.00 Add int 21H buffer dump & function 40H handler, variable buf size
; 5.00 Add emulation of async output COM1: and COM2:, kill risky mode
;============================================================================
; Copyright Information
;
; This program is copyright (c) 1986, 1987, 1988 by D. Whitman
; The source code is provided to registered users of the program
; for educational purposes, and to allow them to customize for
; their OWN use. UNDER NO CIRCUMSTANCES MAY YOU FURTHER DISTRIBUTE
; THIS FILE, MODIFIED VERSIONS OF VPRINT, OR TRANSLATIONS INTO
; INTO OTHER LANGUAGES.
;============================================================================
; References:
;
; The following articles provided insights into safely obtaining
; DOS services from within a TSR:
;
; Dr. Dobb's Journal, Feb 87, p 103
; Byte Magazine, Sept 86, p 399
; PC Magazine, Apr 14, 1987, p 313
;
; (Thanks to Wayne B'Rells for pointing out these articles!)
;=============================================================================
;==============
; Equates
;==============
cr equ 0DH ;carrage return character
lf equ 0AH ;line feed character
true equ 0FFH ;boolean "TRUE"
false equ 00H ;boolean "FALSE"
stdin equ 0000H ;standard input handle
stdout equ 0001H ;standard output handle
off_line equ 0C8H ;bad printer status value (=printer turned off)
clear2send equ 0010H ;aux port ready for output
not_clear equ 8000H ;aux port has timed out
ready equ 90H ;good printer status value
param_count equ [80H] ;# of param chars in command line
param_area equ [81H] ;text of DOS command line
environ_ptr equ [2CH] ;pointer to our copy of environment
@prnstr equ 09H ;DOS print screen function
@create equ 3CH ;DOS create file function
@open equ 3DH ;DOS open file function
@write equ 40H ;DOS write block to file/device function
@close equ 3EH ;DOS close file function
@dosver equ 30H ;DOS get DOS version function
@alloc equ 48H ;DOS allocate memory block
@free equ 49H ;DOS free allocated memory
@setblock equ 4AH ;DOS modify allocated memory block
entry_point jmp main ;jump over resident stuff when starting up
;===========================================================
; NEW_INT14
; This section of code traps interrupt 14H, BIOS RS232_IO.
; We test for whether we're enabled, and whether this is an
; output call for of our emulated aux ports. If so, handle the
; call, otherwise pass it along to the original service routine.
;===========================================================
new_int14
sti ;enable interupts
push ax ;save state
push bx ; ditto
push dx ; ditto
push ds ; ditto
push es ; ditto
mov bx, cs ;and establish addressability
mov ds, bx ; ditto
cmpb enable_flag, true ;are we enabled?
jne pass_14 ;if not, pass call down the pipe
inc dl ;convert printer code for AND
test dl, enabled_aux ;request for one of ours?
jz pass_14 ;if not, pass call down the pipe
cmp ah, 02H ;receive character request?
je pass_14 ;if so, pass along to BIOS
cmp ah, 01H ;output char request?
jne ni14_status ;if not, just return status
call output_char ;else handle char output
ni14_short_status
pop es
pop ds
pop dx
pop bx
pop ax
mov ah, cs:aux_status
iret
ni14_status
pop es
pop ds
pop dx
pop bx
pop ax
mov ax, cs:aux_status
iret ;return to caller
pass_14 pop es ;restore state
pop ds
pop dx
pop bx
pop ax
;===================================================
;jump to the original vector. Note CS: prefix,
;since we just lost our addressability by popping DS
;===================================================
cli ;real INT turns off interupts
jmpf cs:old_int14 ;allow BIOS to do it
;===========================================================
; NEW_INT17
; This section of code traps interrupt 17H, BIOS PRINTER_IO.
; We test for whether we're enabled, and whether this is a
; one of our emulated printers. If so, handle the call,
; otherwise pass it along to the original service routine.
;===========================================================
new_int17
sti ;enable interupts
push ax ;save state
push bx ; ditto
push dx ; ditto
push ds ; ditto
push es ; ditto
mov bx, cs ;and establish addressability
mov ds, bx ; ditto
cmpb enable_flag, true ;are we enabled?
jne pass_17 ;if not, pass call down the pipe
cmpb enabled_printers, 8 ;are we in PrtSc only mode?
jne r1 ;skip if not
les bx, ps_status_ptr ;else get pointer to PrtSc status
testb es:[bx], 0FFH ;is PrtSc active?
jz pass_17 ;if not, pass call down the pipe
jmps r2 ;otherwise, handle print request
r1 inc dl ;convert printer code for AND
test dl, enabled_printers ;request for one of ours?
jz pass_17 ;if not, pass call down the pipe
or ah, ah ;request to print?
jnz return_status ;if not, either init or status
r2 call output_char ;else handle char output
return_status
pop es
pop ds
pop dx
pop bx
pop ax
mov ah, cs:prn_status
iret ;return to caller
pass_17 pop es ;restore state
pop ds
pop dx
pop bx
pop ax
;============================================
;Fake an interupt instruction and jump to the
;original vector. Note CS: prefix, since we
;just lost our addressability by popping DS
;============================================
cli ;real INT turns off interupts
jmpf cs:old_int17 ;allow BIOS to do it
;========================================================
; NEW_INT21
;
; This section of code intercepts interupt 21H, the DOS function
; dispatcher. We do this for two reasons:
;
; 1. It gives us a chance to empty our buffer every time
; an application makes a DOS call, at a time when DOS
; *has* to be idle. (It's idle, because it hasn't
; received the application's call yet - sneaky, eh?)
; Note that to be strictly safe, we still check the
; DOS_CRITICAL byte to rule out the situation where
; DOS is calling itself.
;
; 2. We can trap function 40H and do it ourselves. A debug
; session revealed that DOS goes critical during function 40H
; processing. If the application requests output of a block
; larger than our buffer size, with DOS critical the whole time,
; our buffer will overflow. If function 40H gets called with
; a handle we're emulating, we'll do it ourselves, and INT 17H
; can dump the buffer since DOS isn't critical yet.
;
; This allows us to support essentially all software. The only
; hitch is if a program opens the printer or COM port as if it
; was a file, we won't be able to recognise the generated handle.
; As long as applications either use the pre-defined handles, or
; only write blocks smaller than our buffer, we're OK.
;============================================================
new_int21 proc far
pushf ;save caller's flags
sti ;allow interupts
cmpb cs:need_dump, true ;do we need a buffer dump?
jne test_function ;no, skip
;try to dump the buffer
push es ;else get DOS critical byte
push bx
les bx, cs:dos_c_ptr ;get pointer to dos critical byte
cmpb es:[bx], 01H ;is dos critical?
pop bx
pop es
jge test_function ;abort if so
call dump_buffer
test_function cmp ah, @write ;is this a write block call?
jne ni21_pass ;nope, pass along
cmpb cs:enable_flag, true ;are we enabled?
jne ni21_pass ;if not, pass along
;=============================================================
; Cut ourselves some extra insurance here. If the requested
; block is bigger than the available room in our buffer, force
; a buffer flush, whether we recognize the handle or not.
; This maximizes the probability we can take care of output
; when the user has opened his device as if it was a file.
;=============================================================
cmpw cs:numchars, 0000H ;anything at all in buffer?
je test_handles ;skip if nothing there
push di
mov di, cs:buf_size ;calculate room left in buffer
sub di, cs:numchars
cmp di, cx ;is there enough room for this call?
pop di
jae test_handles ;skip if enough room
movb need_dump, true ;assert need_dump, in case we can't
push es ;else try to flush the buffer
push bx
les bx, cs:dos_c_ptr ;get pointer to dos critical byte
cmpb es:[bx], 01H ;is dos critical?
pop bx
pop es
jge test_handles ;abort if so
call dump_buffer
;=======================================================
; Now check for the handles we're specifically emulating
;=======================================================
test_handles cmp bx, 4 ;check for std printer handle
jne ni21_aux ;no, check for aux
testb cs:enabled_printers, 1 ;are we emulating the std printer?
jz ni21_pass ;if not, pass along
call handle_40_prn ;else do the function 40 ourselves
jmps ni21_exit
ni21_aux cmp bx, 3 ;check for std aux handle
jne ni21_pass ;pass on if not
testb cs:enabled_aux, 1 ;are we emulating the std aux device?
jz ni21_pass ;if not, pass along
call handle_40_aux ;else do the function 40 ourselves
jmps ni21_exit
;==========================================================
; DOS returns from interupts in a strange way, not via IRET
; We need to return the callers original flags, but with
; the carry flag cleared (it's set if DOS had an error).
; We do this by popping our saved user flags, clearing the
; carry bit, and doing a RET 2 to discard the flag set
; pushed by the original INT 21H instruction.
;===========================================================
ni21_exit popf ;restore saved user flags
clc ;indicate no errors
ret 2 ;simulate DOS's return
ni21_pass
popf ;restore saved user flags
cli ;real INT turns off interupts
jmpf cs:old_int21 ;and pass call to DOS
endp
;======================================================
; NEW_INT28
;
; This section of code intercepts interrupt 28H.
; This is an undocumented DOS interrupt, which DOS
; seems to call periodically during idle periods.
; The DOS external command PRINT works by intercepting
; int 28H. If our buffer is more than half full
; during an int 28H call, try to empty it to disk.
;
; Intercepting int 28H allows us to empty the buffer
; under conditions where the DOS critical byte is set
; during calls to VPRINT's BIOS interupts.
;
; According to the PC magazine article referenced in the
; prolog, it's safe to call DOS during int 28H processing,
; AS LONG AS WE ONLY USE DOS FUNCTIONS HIGHER THAN 0CH.
; Using the lower functions will clobber one of DOS's
; internal stacks. Since we only use high functions,
; we don't need to check the DOS critical byte before
; requesting services.
;========================================================
new_int28
sti ;be polite, answer the phone
push ax
push bx
push ds
mov ax, cs ;establish local addressability
mov ds, ax
mov ax, numchars ;how many chars in our buffer?
cmp ax, trigger ;are we above the trigger amount?
jb idle_exit ;abort if below
call dump_buffer
idle_exit pop ds
pop bx
pop ax
cli
jmpf cs:old_int28 ;now service original int 28 routine.
;===================================================
; HANDLE_40_PRN
;
; This routine emulates a DOS "write block" function
; to the standard printer device. We send the block
; one character at a time to our int 17H handler.
;
; By emulating this function, we prevent DOS from
; going critical during the int 17H calls, so
; they can safely empty our buffer as needed.
;===================================================
handle_40_prn proc near
jcxz h4p_exit ;if nothing to write, abort
push si ;else save state and do it
push cx
push dx
cld ;8088 <-- autoincrement mode
mov si, dx ;ds:si <-- pointer to string
xor dx, dx ;handle 4 is printer id = 0
h4p_loop lodsb ;get a char
xor ah, ah ;print char function
int 17H ;call our routine
loop h4p_loop ;loop til CX = 0
pop dx ;restore regs
pop cx
pop si
h4p_exit mov ax, cx ;tell caller we wrote all his chars
ret
endp
;===================================================
; HANDLE_40_AUX
;
; This routine emulates a DOS "write block" function
; to the standard auxiliary device. Implementation
; and rationale are analogous to HANDLE_40_PRN.
;===================================================
handle_40_aux proc near
jcxz h4a_exit ;if nothing to write, abort
push si ;else save state and do it
push cx
push dx
cld ;8088 <-- autoincrement mode
mov si, dx ;ds:si <-- pointer to string
xor dx, dx ;handle 3 is async id = 0
h4a_loop lodsb ;get a char
mov ah, 01H ;print char function
int 14H ;call our routine
loop h4a_loop ;loop til CX = 0
pop dx ;restore regs
pop cx
pop si
h4a_exit mov ax, cx ;tell caller we wrote all his chars
ret
endp
;=================================================
; OUTPUT_CHAR
;
; We implement 3 printing modes: verbatim, and 2
; "trapping" modes to force proper CR/LF terminated
; lines, since some programs don't bother to send
; both CR and LF to a printer. This routine
; handles the various trapping modes, and then
; calls PRINT_CHAR for the actual output.
;=================================================
output_char proc near
push ax
push bx
push ds
mov bx, cs ;establish local addressability
mov ds, bx
cmpb trap_char, lf ;are we in trap linefeed mode?
jne try_cr ;nope, skip
cmp al, lf ;is this a line feed?
je oc_exit ;exit if so
call print_char ;otherwise print it
cmp al, cr ;was that a carriage return?
jne oc_exit ;nope, we're done
mov al, lf ;if so, add line feed
call print_char
jmps oc_exit ;and exit
try_cr cmpb trap_char, cr ;trap carrage return mode?
jne no_traps ;nope, skip
cmp al, cr ;is this a CR?
je oc_exit ;exit if so
cmp al, lf ;line feed?
jne no_traps ;nope, skip
mov al, cr ;yes, send CR first
call print_char
mov al, lf ;& THEN send line feed
no_traps call print_char
oc_exit pop ds
pop bx
pop ax
ret
endp
;============================================
; PRINT_CHAR
;
; Add the character in AL to the buffer.
; If the buffer is full enough, dump it to disk.
;============================================
print_char proc near
push ax
push bx
push di
push ds
push es
mov bx, cs ;establish local addressability
mov ds, bx
mov di, numchars ;position in buffer for char
cmp di, buf_size ;is buffer overflowing?
jae pc_0 ;drop char & try to dump buffer
mov bx, buffer_seg ;else char to buffer
mov es, bx ; establish addr. of buffer
mov es:[di], al ; and move char into buffer
inc di ;bump number of characters
mov numchars, di ;and save
cmp di, trigger ;is buffer full enough to dump?
jb pc_exit ;if not, return
;===============================================================
; If we call for DOS services when DOS is in a re-entrant condition,
; we can mung up the system by trashing one of DOS's internal stacks.
; We can tell if it's safe by examining the DOS_CRITICAL byte,
; an undocumented flag maintained by DOS. If this byte is zero,
; it is safe to call DOS for i/o.
;=================================================================
; During installation, we determined the location of this byte by
; using undocumented DOS function 34H. Function 34H returns a
; pointer to DOS_CRITICAL in ES:BX. Note that we *had* to do this
; during installation, since if DOS is critical now, we're in a
; catch-22 situation, where we could crash the system by calling
; DOS for the pointer. Fortunately, the byte doesn't seem to move.
;=================================================================
pc_0 les bx, dos_c_ptr ;get pointer to DOS_CRITICAL
cmpb es:[bx], 01H ;is dos critical?
jge pc_fail ;abort if so
pc_1 call dump_buffer
jnc pc_exit ;if successful, exit
pc_fail movb need_dump, true ;couldn't empty, so set flag
mov ax, numchars
cmp ax, buf_size ;is the buffer completely full?
jb pc_exit ;if not, hope we can dump later
movb prn_status, off_line ;else send bad status to caller
movw aux_status, not_clear ;ditto
pc_exit
pop es
pop ds
pop di
pop bx
pop ax
ret
endp
;======================================================
; DUMP_BUFFER
;
; Attempts to dump the print buffer to disk.
; Clears carry flag if sucessful, sets it if not.
;
; No safety checking is performed. DUMP_BUFFER
; should only be called if the caller knows it's
; safe to call DOS for services.
;
; We don't use any of the old DOS 1.x "traditional
; character device" calls, so it should be safe to call
; DUMP_BUFFER from an INT 28H handler, regardless of the
; state of DOS_CRITICAL.
;
; Preserves all registers.
;=======================================================
dump_buffer proc near
movb cs:need_dump, false ;don't let other routines call again
push ax ;save state
push bx
push cx
push dx
push ds
mov ax, cs ;establish addressability
mov ds, ax ; ditto
mov dx, offset(filename) ;point to filename
mov ax, 3D01H ;open file for write
call real_DOS ;skip our int 21H trap routine
jnc db_cont ;continue if ok
;===================================================================
; if we can't find our file, maybe the user changed the disk on us.
; Try making a new file.
;===================================================================
mov ah, @create
xor cx, cx ;normal file attribute
mov dx, offset(filename)
call real_DOS ;skip our int 21H trap routine
jnc db_fail
db_cont mov bx, ax ;get handle from last call
mov ax, 4202H ;seek EOF
xor cx, cx
xor dx, dx
call real_DOS ;skip our int 21H trap routine
push ds ;establish addr. of buffer
mov ax, buffer_seg ;ditto
mov ds, ax ;ditto
mov ah, @write ;request file write
mov cx, cs:numchars ;number of bytes in buffer
xor dx, dx ;from offset 0 in buffer
call real_DOS ;skip our int 21H trap routine
pop ds ;reestablish local addressability
mov ah, @close ;close file
call real_DOS ;skip our int 21H trap routine
;=================
; Exit Sucessfully
;=================
movw numchars, 0000H ;buffer is now empty
movb prn_status, ready ;send normal status back to caller
movw aux_status, clear2send ;ditto
pop ds
pop dx
pop cx
pop bx
pop ax
clc ;indicate sucess
ret
;==============
; Failure Exit
;==============
db_fail movb need_dump, true ;still need to dump buffer
movb prn_status, off_line ;send bad status to caller
movw aux_status, not_clear
pop ds
pop dx
pop cx
pop bx
pop ax
stc ;indicate failure
ret
endp
;======================================================
; REAL_DOS
;
; This allows our resident routines to make calls directly
; to DOS, skipping our int 21H handler, and avoiding
; some pathological cases of infinite recursion.
;======================================================
real_dos proc near
pushf ;real INT pushes flags
cli ;real INT turns off interupts
callf cs:old_int21
sti ;re-enable interupts
ret
endp
list ;$$$
;=======================
; Resident data section
;=======================
verify db 'VPRINT 5.00' ;recognition string
verify_end
old_int14 dw 0000H, 0000H ;original async handler vector
old_int17 dw 0000H, 0000H ;original printer handler vector
old_int21 dw 0000H, 0000H ;original dos function vector
old_int28 dw 0000H, 0000H ;original dos idle handler vector
dos_c_ptr dw 0000H, 0000H ;pointer to dos critical byte
ps_status_ptr dw 0000H, 0050H ;pointer to print screen status byte
need_dump db false ;boolean flag - does buffer want dumping?
enable_flag db true ;boolean flag - are we active?
enabled_printers db 03H ;which printer(s) are we emulating?
enabled_aux db 00H ;which aux port(s) are we emulating?
trap_char db 00H ;character to trap (none, cr, or lf)
numchars dw 0000H ;number of chars now in buffer
prn_status db ready ;printer status to send to caller
aux_status dw clear2send ;aux status: assert clear to send
filename ds 73 ;length, 63 byte path, filename.ext 00
buffer_seg dw 0000H ;segment pointer for buffer
buf_size dw 0800H ;size of buffer
trigger dw 0400H ;if this many chars in buffer, try to dump
end_resident ;end of resident code and data
;buffer segment follows this point
nolist ;$$$
;================================
; Beginning of non-resident code
;================================
main proc near
call check_dos
call uc_command_line
call parse_options
cmpb param_found, true
jne exit_with_help
;print title message
mov ah, @write
mov bx, stdout
mov cx, short_end-short_hello
mov dx, offset(short_hello)
int 21H
call check_install
cmpb install_request, true
jne m1
jmp install_code ;program terminates in this routine
m1 call update_res
int 20H ;program halts here
;======================================================
; If no valid options were given, print a help screen,
; and exit without doing anything else.
;======================================================
exit_with_help mov ah, @write ;print help message
mov bx, stdout ;on standard output
mov cx, h_end-hello
mov dx, offset(hello)
int 21H
mov ax,4C00H ;exit with ERRORLEVEL 0
int 21H
endp
;=================================================
; CHECK_DOS
; Confirms that we're running under DOS 2 or later.
; Aborts program with error message if not.
;=================================================
check_dos proc near
mov ah, @dosver ;get DOS version
int 21H
cmp al, 2 ;DOS 2 or later?
jge cd_exit ;yes, skip
mov ah, @prnstr ;no, bitch
mov dx, offset(baddos)
int 21H
int 20H ;and exit
cd_exit ret
endp
;==============================================
; UC_COMMAND_LINE
; Converts the command line to all upper case.
;==============================================
uc_command_line proc near
push si
push di
mov cl, param_count ;length of command line
xor ch, ch ; " " " "
jcxz uc_exit ;abort if nothing to process
mov si, offset(param_area) ;pointers to command line
mov di, si ; " " " "
uc_1 lodsb
cmp al, 'a' ;too low?
jl uc_2 ;skip char if so
cmp al, 'z' ;too high?
jg uc_2 ;skip char if so
and al, 0DFH ;otherwise force upper case
uc_2 stosb
loop uc_1
uc_exit pop di
pop si
ret
endp
;==================================================
; PARSE_OPTIONS
; Parse the command line, and set flags accordingly
;==================================================
parse_options proc near
xor ch, ch ;CX <== # of parameter chars
mov cl, param_count ;ditto
mov di, offset(param_area)
mov al, ' ' ;search for 1st non-blank
cld
rep
scasb
jcxz po_exit_1 ;no parameters? just print hello screen
jmps po_cont_1 ;otherwise continue
po_exit_1 jmp po_exit ;need long jump, hence convolutions
po_cont_1 dec di ;back up to char found
inc cx ; ditto
cmpb [di], '?' ;help request?
je po_exit_1 ;exit by printing help
call get_filename ;read user's or use default
jcxz po_exit_1 ;out of param chars?
po1 mov al, '/' ;nope, scan for options
cld
repne
scasb
jcxz po1a
jmps po2 ;found, continue
po1a jmp po_exit ;none found, skip
po2 mov al, [di] ;get option char
cmp al, 'I' ;install request?
jne po3 ;nope, skip
movb install_request, true
movb param_found,true
jmps po1 ;and loop
po3 cmp al, 'P' ;printer number request?
jne po5 ;nope, skip
movb set_printers, true
movb param_found, true
inc di ;get next char
dec cx
mov al, [di]
cmp al, 'P' ;just PrtSc?
jne po3a
movb enabled_printers, 8
jmps po1
po3a cmp al, '0' ;no LPT output?
jne po3b
movb enabled_printers, 0
jmps po1
po3b cmp al, '1' ;LPT1: request
jne po3c
movb enabled_printers, 1
jmps po1
po3c cmp al, '2' ;LPT2 request?
jne po3d
movb enabled_printers, 2
jmps po1
po3d cmp al, '3' ;both ports request?
jne po4 ;nope, skip
movb enabled_printers, 3
po4 jmps po1
po5 cmp al, 'A' ;COM port number request?
jne po6 ;nope, skip
movb set_aux, true
movb param_found, true
inc di ;get next char
dec cx
mov al, [di]
cmp al, '0' ;no COM output
jne po5a
movb enabled_aux, 0
jmp po1
po5a cmp al, '1' ;COM1: request
jne po5b
movb enabled_aux, 1
jmp po1
po5b cmp al, '2' ;COM2 request?
jne po5c
movb enabled_aux, 2
jmp po1
po5c cmp al, '3' ;both ports request?
jne po5d ;nope, skip
movb enabled_aux, 3
po5d jmp po1
po6 cmp al, 'S' ;status request?
jne po7 ;nope, skip
movb status_request, true
movb param_found, true
jmp po1
po7 cmp al, 'E' ;enable request?
jne po8 ;nope, skip
movb enable_flag, true
movb set_enable_state, true
movb param_found,true
jmp po1
po8 cmp al, 'D' ;disable request?
jne po9 ;nope, skip
movb enable_flag, false
movb set_enable_state, true
movb param_found,true
jmp po1
po9 cmp al, 'N' ;neutral request?
jne po10 ;nope, skip
movb trap_char, 00H
movb set_trap_mode, true
movb param_found,true
jmp po1
po10 cmp al, 'C' ;trap CR request?
jne po11 ;nope, skip
movb trap_char, cr
movb set_trap_mode, true
movb param_found,true
jmp po1
po11 cmp al, 'L' ;trap LF request?
jne po12 ;nope, skip
movb trap_char, lf
movb set_trap_mode, true
movb param_found,true
jmp po1
po12 cmp al, 'F' ;flush buffer request?
jne po14 ;nope, skip
movb flush_request, true
movb param_found, true
po13 jmp po1
;=============================================
; Note below that we don't set the normal two flags,
; since /B is only valid during installation.
;=============================================
po14 cmp al, 'B' ;set buffer size request?
jne po15
xor ax, ax ;clear running total
mov dl, 10 ;frequently used constant
po14a cmpb 1[di], '0' ;next char valid digit?
jb po14b ;done if not
cmpb 1[di], '9'
ja po14b
mul dl ;multiply by 10 for next digit
inc di ;point to next digit
dec cx ;used up a char
mov bl, [di] ;get the next digit
sub bl, '0' ;convert from ASCII to binary
xor bh, bh ;and add to running total
add ax, bx
jmps po14a
;===================================
; We now have the buffer size in K.
; Validate, and convert to bytes for internal use.
;===================================
po14b cmp ax, 1 ;force outliers to min or max value
jge po14c
mov ax, 1
po14c cmp ax, 64
jle po14d
mov ax, 64
;convert to bytes
po14d push cx ;save number of param chars left
mov cl, 9 ;multiply by 2^9 for trigger amount
shl ax, cl ;(trigger is buf_size/2)
mov trigger, ax ;save trigger amount
shl ax ;multiply by 2 for buf_size
cmp ax, 0000H ;handle conversion overflow
jne po14e ;skip if ok
mov ax, 0FFFFH ;otherwise use MaxInt
po14e mov cs:buf_size, ax ;save buf_size
pop cx ;restore number of param chars left
po15 jmp po1
po_exit ret
endp
;==============================================================
; CHECK_INSTALL
; Checks to see if a copy of VPRINT is already resident.
; Sets the flag INSTALLED accordingly. ES is set to the segment
; of the current printer support routine, and the current
; printer support vector is saved for possible re-use.
;==============================================================
check_install proc near
;save the current bios printer vector
mov ax, 3517H ;get vector 17 (printer_io)
int 21H
mov old_int17, bx ;and save it
mov old_int17+2, es
;======================================================
; ES now points to the current routine's segment
; If the current routine is VPRINT already, variables in
; our segment are at same offsets as those in the resident
; routine. The only difference is that DS points to our
; stuff, while ES points to the resident routine.
;========================================================
; Compare our verifier string with the same location
; in the current printer support routine. If they match,
; a copy of us is already resident.
;========================================================
mov di, offset(verify)
mov si, di ;same offset in both segments
mov cx, verify_end-verify ;length of verifier string
cld
repe
cmpsb
jcxz ci_yes
movb installed, false ;not matched, so not installed
jmps ci_exit
ci_yes movb installed, true ;matched, so already installed
ci_exit ret
endp
;======================================================
; INSTALL_CODE
; If we're not already there, install resident code.
;======================================================
install_code proc near
cmpb installed, true ;are we aleady installed?
jne ic_1 ;no, continue
jmp ic_res_error ;yes, bitch (need long jump)
;create/open our file
ic_1 mov ah, @create ;create file
xor cx, cx ;normal file attribute
mov dx, offset(filename)
int 21H ;to make sure we can
jnc ic_2 ;if so, continue
jmp ic_open_error ;else bitch (need long jump)
ic_2 mov ah, @close ;close it until we need it
int 21H
push es ;summarize switch settings
push ds
pop es
call print_status
pop es
;print reassuring message
mov ah, @write
mov bx, stdout
mov cx, inst_end-inst_msg
mov dx, offset(inst_msg)
int 21H
;grab the BIOS printer vector for our own use.
;(we already saved it's current value)
mov dx, offset(new_int17)
mov ax, 2517H
int 21H
;save the current BIOS async vector
mov ax, 3514H ;get vector 14H (rs232_io)
int 21H
mov old_int14, bx ;and save it
mov old_int14+2, es
;and grab it for our own use.
mov dx, offset(new_int14)
mov ax, 2514H
int 21H
;save the current DOS function dispatcher vector
mov ax, 3521H ;get vector 21H
int 21H
mov old_int21, bx ;and save it
mov old_int21+2, es
;and grab it for our own use.
mov dx, offset(new_int21)
mov ax, 2521H
int 21H
;save the current dos idle vector
mov ax, 3528H ;get vector 28H (dos idle)
int 21H
mov old_int28, bx ;and save it
mov old_int28+2, es
;and grab it for our own use.
mov dx, offset(new_int28)
mov ax, 2528H
int 21H
;get and save pointer to dos critical byte
mov ah, 34H
int 21H
mov dos_c_ptr, bx
mov dos_c_ptr+2, es
;=========================================================================
;Set up our buffer
;
;I can't seem to find an elegant way to tell how much memory
;is available outside our segment. Instead of knowing how much is available
;and just grabbing it off when we return via int 31H, we'll be polite and
;let DOS handle memory allocation for us. To start with, we de-allocate
;all un-needed memory; we're given everything on entry. We then
;request a new segment for our buffer. We have to check after return
;whether we were able to get all that we asked for, and if not, bitch
;to user and abort.
;=========================================================================
;===========================
; Deallocate our environment
;===========================
mov ax, environ_ptr ;get pointer to environment block
mov es, ax
mov ah, @free ;free block
int 21H
;=================================================
;Figure out where we actually end,
;and shrink our block to its miminum size.
;Adding 15 guarantees we don't lop off anything
;when we convert to paragraphs via the divide by 16
;=================================================
mov ax, cs ;point to our segment
mov es, ax
mov bx, offset((end_nonres+15)/16) ;minimum size, paragraphs
mov ah, @setblock ;set block size
int 21H
;=======================
;Now allocate the buffer
;=======================
mov bx, buf_size ;buffer size in bytes
mov cl, 4 ;divide by 16 for paragraphs
shr bx, cl
inc bx ;takes care of anything lopped off
mov ah, @alloc ;allocate block
int 21H
jc ic_mem_error ;fatal error if not enough memory
mov buffer_seg, ax ;save pointer to new segment
;======================================================================
;Terminate and Stay Resident
;The DOS 2 TSR call asks for the number of PARAGRAPHs of memory we want.
;Paragraphs are 16 byte chunks, hence we divide the number of bytes by 16.
;We reserve memory here for our code and various small local variables.
;Our main buffer was reserved above by a DOS memory allocation call.
;======================================================================
;Usage note: Large amounts of memory are given in paragraphs because
;this is the smallest unit in which the total possible memory of the 8088
;can be specified in a word: FFFF paragraphs = 1 megabyte
;======================================================================
mov ax, 3100H ;TSR w/ ERRORLEVEL = 0
mov dx, offset((main+15)/16) ;total paragraphs
int 21H
;==============================
; program halts after TSR call.
;==============================
;==============================
; Fatal Error Messages
; Print message, then abort.
;==============================
ic_res_error mov cx, ic_res_end-ic_res_msg
mov dx, offset(ic_res_msg)
jmps fatal_error
ic_open_error mov cx, ic_open_end-ic_open_msg
mov dx, offset(ic_open_msg)
jmps fatal_error
ic_mem_error mov cx, ic_mem_end-ic_mem_msg
mov dx, offset(ic_mem_msg)
jmps fatal_error
fatal_error mov ah, @write ;write error message
mov bx, stdout ;on standard output
int 21H
mov ax, 4CFFH ;and exit w/ ERRORLEVEL = 255
int 21H
endp
;=======================================
; program halts upon exit w/ errorlevel
;=======================================
;===============================================
; GET_FILENAME
; Builds the proper d:\path\filename, from either
; the default or the user's specifications.
;===============================================
; Upon entry, ES:DI point to the first non-blank
; character on the command line, and CX has the
; number of characters left on the command line.
;===============================================
; Upon exit, FILENAME has an ASCIIZ string specifying
; the full filespec. ES:DI have been moved on the command
; line past the filename, and CX has the (updated) number
; of command line characters left.
;===============================================
get_filename proc near
cld ;8088 <== autoincrement mode
push ax
push bx
push cx
push dx
push di
push si
mov si, di
mov di, offset(filename)
;=====================================================
; DI now points to where we will build the filespec.
; SI points to the command line.
; Assumes ES and DS both point to our local segment
;=====================================================
cmpb [si], '/' ;are we looking at an option?
jne gf_0 ;no, skip
;===================================================
;if no filename was specified, use our default name
;on the current default drive.
;===================================================
use_default mov ah, 19H ;get default drive
int 21H
add al, 'A' ;convert code to drive letter
stosb ;move it to filespec
mov al, ':' ;followed by a colon
stosb
mov si, offset(default_file)
push cx
mov cx, default_end-default_file
rep
movsb
pop cx
jmps gf_exit
;==========================================================
; Build a complete filespec with info from the command line
;==========================================================
gf_0 movb file_request, true ;user specified a filename
movb param_found, true
call approve_file ;make sure its an allowed name
jnc gf_0a ;ok? continue
mov ah, @write ;illegal name? bitch and abort
mov bx, stdout
mov cx, illegal_end-illegal_msg
mov dx, offset(illegal_msg)
int 21H
int 20H
gf_0a cmpb 1[si], ':' ;was a drive specified?
jne gf_1 ;no, skip
mov al, [si] ;yes, get it
movsw ;then copy into filespec
sub cx, 2 ;used up two chars
mov dl, al ;will need drive later
and dl, 0DFH ;force upper case
sub dl, 'A'-1 ;convert to drive code
jmps gf_2 ;done
gf_1 mov ah, 19H ;otherwise get default drive
int 21H
add al, 'A' ;convert code to drive letter
stosb ;move it to filespec
mov al, ':' ;followed by a colon
stosb
sub dl, dl ;use code 0 (default) in next call
;====================================================
; Filespec now has D:, and DL has drive code (A: = 1)
;====================================================
gf_2 cmpb [si], '\' ;was a path specified?
je gf_3 ;skip if so
;=============================================
; if no path specified, use current directory
;=============================================
mov al, '\' ;start with \
stosb ; ditto
mov ah, 47H ;ask for current directory
push si ;need SI temporarily
mov si, di ;point to building filespec
int 21H
pop si ;restore SI
push cx ;need CX temporarily
mov al, 00H ;now search for end of path
mov cx, 64 ;maximum path length
repne ;will stop after terminator byte
scasb ; ditto
dec di ;now points to terminator byte
cmpb -1[di], '\' ;do we need the final \?
je gf_2a ;no, skip
mov al, '\' ;end with \
stosb
gf_2a pop cx ;restore CX
;============================================
; Now copy the filename, up to the delimiter
;============================================
gf_3 jcxz gf_exit
lodsb ;get a byte
cmp al, '.' ;in valid range?
jl gf_exit ;too low?
cmp al, 'z'
jg gf_exit ;too high?
stosb ;otherwise, add to filespec
jmps gf_3 ;and loop for another
gf_exit mov al, 00H ;terminate filespec
stosb
pop si ;and restore registers
pop di
pop dx
pop cx
pop bx
pop ax
ret
endp
;=========================================================
; APPROVE_FILE
; Checks to see if the user specified an illegal filename.
; If we allow devices LPT1: LPT2: or PRN, we could lock up
; the system. The carry flag is set if the name is illegal.
;==========================================================
approve_file proc near
push es
push ax
push cx
push di
push si
push bp
mov bp, sp
mov ax, ds ;establish addressability
mov es, ax ;of test strings
mov di, offset(lpt1_string)
mov cx, 5
repe
cmpsb
jcxz disapprove
mov si, 2[bp] ;restore si after last loop
mov di, offset(lpt2_string)
mov cx, 5
repe
cmpsb
jcxz disapprove
mov si, 2[bp] ;restore si after last loop
mov di, offset(prn_string)
mov cx, 3
repe
cmpsb
jcxz disapprove
clc ;no matches? file ok
jmps ap_exit
disapprove stc ;matched on, so bad name
ap_exit pop bp
pop si
pop di
pop cx
pop ax
pop es
ret
endp
;==========================================================
; UPDATE_RESIDENT
; Apply the specified options to the already resident code.
;
; ES contains the segment of the resident copy of us.
; Modify the resident copy's flags, etc, according
; to our parameters.
;==========================================================
update_res proc near
cmpb installed, true
je ur_cont
;====================================================
; We can't modify resident code if its not installed
;====================================================
not_inst_err mov dx, offset(not_inst_msg)
mov cx, not_inst_end-not_inst_msg
mov bx, stdout
mov ah, @write
int 21H
int 20H
;=====================================
; Request to change emulated printers?
;=====================================
ur_cont cmpb set_printers, true
jne ur_2
mov al, enabled_printers
mov es:enabled_printers, al
;========================================
; Request to change emulated async ports?
;========================================
ur_2 cmpb set_aux, true
jne ur_3
mov al, enabled_aux
mov es:enabled_aux, al
;===========================================
; Request to change character trapping mode?
;===========================================
ur_3 cmpb set_trap_mode, true
jne ur_4
mov al, trap_char
mov es:trap_char, al
;=========================
; enable/disable request?
;=========================
ur_4 cmpb set_enable_state, true
jne ur_5
mov al, enable_flag
mov es:enable_flag, al
cmp al, false ;did we just disable resident code?
jne ur_5 ;skip if not
call flush_resident ;if so, flush resident buffer
;==================================
; Request to flush resident buffer?
;==================================
ur_5 cmpb flush_request, true
jne ur_6
call flush_resident
;==========================
; Request to use new file?
;==========================
ur_6 cmpb file_request, true
jne ur_exit
mov ah, @create ;confirm the file's there
xor cx, cx
mov dx, offset(filename)
int 21H
jc ur_open_err
;close file for later use
mov bx, ax ;get handle
mov ah, @close
int 21H
ur_1a call flush_resident ;flush out buffer to old file
;===========================================
; copy the new filename to the resident code
;===========================================
mov si, offset(filename)
mov di, si
lodsb ;get a character
ur_1 stosb ;transfer to resident copy
or al, al ;was that the null terminator?
jz ur_exit ;if so, exit
lodsb ;otherwise, get another char
jmps ur_1 ;and continue
;====================================
; Print post-processing status, exit
;====================================
ur_exit call print_status
ret
;=========================================
; error handler if unable to open new file
;=========================================
ur_open_err call print_status
mov ah, @write ;unable to open file, so bitch
mov bx, stdout
mov cx, ic_open_end-ic_open_msg
mov dx, offset(ic_open_msg)
int 21H
ret
endp
;==============================================
; PRINT_STATUS
; print the current status of the resident copy
; Assumes that ES points to the resident copy.
;==============================================
print_status proc near
mov ah, @write ;write title block
mov bx, stdout
mov cx, cs_status_end-cs_status_hdr
mov dx, offset(cs_status_hdr)
int 21H
mov ah, @write ;are we enabled or disabled?
mov bx, stdout
cmpb es:enable_flag, true
jne cs_1a
mov cx, cs_enab_end-cs_enab_msg
mov dx, offset(cs_enab_msg)
jmps cs_1b
cs_1a mov cx, cs_disab_end-cs_disab_msg
mov dx, offset(cs_disab_msg)
cs_1b int 21H
mov ah, @write ;which printers are we emulating?
mov bx, stdout
cmpb es:enabled_printers, 1
jne cs_2a
mov cx, cs_prn1_end-cs_prn1_msg
mov dx, offset(cs_prn1_msg)
jmps cs_2c
cs_2a cmpb es:enabled_printers, 2
jne cs_2b
mov cx, cs_prn2_end-cs_prn2_msg
mov dx, offset(cs_prn2_msg)
jmps cs_2c
cs_2b cmpb es:enabled_printers, 3
jne cs_2d
mov cx, cs_prn3_end-cs_prn3_msg
mov dx, offset(cs_prn3_msg)
jmps cs_2c
cs_2d cmpb es:enabled_printers, 8
jne cs_2e
mov cx, cs_prsc_end-cs_prsc_msg
mov dx, offset(cs_prsc_msg)
jmps cs_2c
cs_2e cmpb es:enabled_printers, 0
jne cs_2f
mov cx, cs_prn0_end-cs_prn0_msg
mov dx, offset(cs_prn0_msg)
jmps cs_2c
cs_2c int 21H
cs_2f
mov ah, @write ;which async ports are we emulating?
mov bx, stdout
cmpb es:enabled_aux, 1
jne cs_4a
mov cx, cs_aux1_end-cs_aux1_msg
mov dx, offset(cs_aux1_msg)
jmps cs_4c
cs_4a cmpb es:enabled_aux, 2
jne cs_4b
mov cx, cs_aux2_end-cs_aux2_msg
mov dx, offset(cs_aux2_msg)
jmps cs_4c
cs_4b cmpb es:enabled_aux, 3
jne cs_4d
mov cx, cs_aux3_end-cs_aux3_msg
mov dx, offset(cs_aux3_msg)
jmps cs_4c
cs_4d cmpb es:enabled_aux, 0
jne cs_4e
mov cx, cs_aux0_end-cs_aux0_msg
mov dx, offset(cs_aux0_msg)
jmps cs_4c
cs_4c int 21H
cs_4e
mov ah, @write ;are we trapping any characters?
mov bx, stdout
cmpb es:trap_char, 00H
jne cs_3a
mov cx, cs_notrap_end-cs_notrap_msg
mov dx, offset(cs_notrap_msg)
jmps cs_3c
cs_3a cmpb es:trap_char, lf
jne cs_3b
mov cx, cs_traplf_end-cs_traplf_msg
mov dx, offset(cs_traplf_msg)
jmps cs_3c
cs_3b cmpb es:trap_char, cr
jne cs_3d
mov cx, cs_trapcr_end-cs_trapcr_msg
mov dx, offset(cs_trapcr_msg)
cs_3c int 21H
cs_3d
mov ah, @write ;buffer size message
mov bx, stdout
mov cx, cs_buf1_end-cs_buf1_msg
mov dx, offset(cs_buf1_msg)
int 21H
call write_bufsize ;print out buffer size in bytes
mov ah, @write ;2nd part buffer size message
mov bx, stdout
mov cx, cs_buf2_end-cs_buf2_msg
mov dx, offset(cs_buf2_msg)
int 21H
mov ah, @write ;filename message
mov bx, stdout
mov cx, cs_file_end-cs_file_msg
mov dx, offset(cs_file_msg)
int 21H
;calculate length of filename string
push ds
mov ax, es
mov ds, ax
mov al, 00H ;scan for terminator byte
mov cx, 72 ;maximum filename length
mov di, offset(filename)
repne
scasb
mov ax, cx
mov cx, 72
sub cx, ax
;and write filename
mov ah, @write
mov bx, stdout
mov dx, offset(filename)
int 21H
pop ds
mov ah, @write
mov bx, stdout
mov cx, 2
mov dx, offset(crlf)
int 21H
;buffer flushed?
cmpb buf_flushed, true
jne cs_exit
mov ah, @write
mov bx, stdout
mov cx, flush_end-flush_msg
mov dx, offset(flush_msg)
int 21H
cs_exit
ret
endp
;===========================================
; WRITE_BUFSIZE
; Converts the size of our buffer to ASCII,
; and writes it to the standard output.
;===========================================
write_bufsize proc near
push ax ;save machine state
push bx
push cx
push dx
push di
mov ax, es:buf_size ;number to convert
;the following code fragment was written
;by Bob Smith and published in PC Age
;Volume 3.1 (Jan. '84) p. 116
mov bx, 10 ;set up divisor
xor cx, cx ;clear counter
nxt_in xor dx, dx ;clear for division
div bx ;dl <== AX mod 10
or dl, '0' ;convert to ascii digit
push dx ;save digit
inc cx ;bump counter
and ax, ax ;are we done?
jnz nxt_in ;nope, keep going
;stack now has digits of number
;end of Bob Smith's code
mov ah, 06H ;write char function
nxt_out pop dx
int 21H
loop nxt_out
pop di ;restore state
pop dx
pop cx
pop bx
pop ax
ret ;and exit
endp ;(write_linenum)
;==============================================
; Flush_Resident
; Doesn't actually flush the buffer, just sets
; the resident flag indicating a flush request.
; Note: ES points to resident data section.
;==============================================
flush_resident proc near
movb es:need_dump, true
movb cs:buf_flushed, true
ret
endp
;======================
; Flags (non-resident)
;=====================
param_found db false ;was anything found to process?
file_request db false ;did user specify a file?
install_request db false ;is this an install request?
set_printers db false ;change printers to emulate?
set_aux db false ;change aux ports to emulate?
status_request db false ;should we print a status report?
set_enable_state db false ;request to enable or disable?
set_trap_mode db false ;request to change trap mode?
flush_request db false ;request to flush buffer?
buf_flushed db false ;did we flush the buffer?
installed db false ;are we already installed?
default_file db '\VIRTUAL.PRN'
default_end
baddos db cr lf 'This program requires DOS 2.0 or later!$' cr lf
inst_msg db cr lf 'Resident section has been installed.' cr lf
inst_end
lpt1_string db 'LPT1:'
lpt2_string db 'LPT2:'
prn_string db 'PRN'
illegal_msg db cr lf 'Invalid filename - redirecting to a printer'
db ' would cause a system crash.' cr lf
db 'Use VPRINT /D to send output back to a real printer.'
db cr lf
illegal_end
not_inst_msg db cr lf 'Unable to locate resident routine.' cr lf
not_inst_end
flush_msg db cr lf 'Internal buffer flushed to disk.' cr lf
flush_end
ic_res_msg db cr lf 'VPRINT is already resident!' cr lf
ic_res_end
ic_open_msg db cr lf 'Unable to open file.' cr lf
ic_open_end
ic_mem_msg db cr lf 'Insufficient memory to load VPRINT.' cr lf
db 'Specify a smaller buffer and try again.' cr lf
ic_mem_end
short_hello db cr lf 'VPRINT - virtual printer (version 5.00)' cr lf
db cr lf
db 'User-supported software by D. Whitman' cr lf
db 'For help/info, type VPRINT ?' cr lf
short_end
cs_status_hdr db cr lf 'Current Status: ' cr lf cr lf
cs_status_end
cs_enab_msg db ' Redirection: ENABLED' cr lf
cs_enab_end
cs_disab_msg db ' Redirection: DISABLED' cr lf
cs_disab_end
cs_prn0_msg db ' Emulated printer: None' cr lf
cs_prn0_end
cs_prsc_msg db ' Emulated printer: PrtSc ONLY' cr lf
cs_prsc_end
cs_prn1_msg db ' Emulated printer: LPT1:' cr lf
cs_prn1_end
cs_prn2_msg db ' Emulated printer: LPT2:' cr lf
cs_prn2_end
cs_prn3_msg db ' Emulated printers: LPT1: and LPT2:' cr lf
cs_prn3_end
cs_aux0_msg db ' Emulated async port: None' cr lf
cs_aux0_end
cs_aux1_msg db ' Emulated async port: COM1:' cr lf
cs_aux1_end
cs_aux2_msg db ' Emulated async port: COM2:' cr lf
cs_aux2_end
cs_aux3_msg db ' Emulated async port: COM1: and COM2:' cr lf
cs_aux3_end
cs_notrap_msg db ' Filter mode: VERBATIM' cr lf
cs_notrap_end
cs_traplf_msg db ' Filter mode: '
db 'drop LF, expand CR --> CR/LF' cr lf
cs_traplf_end
cs_trapcr_msg db ' Filter mode: '
db 'drop CR, expand LF --> CR/LF' cr lf
cs_trapcr_end
cs_buf1_msg db ' Buffer size: '
cs_buf1_end
cs_buf2_msg db ' bytes' cr lf
cs_buf2_end
cs_file_msg db ' Print file: '
cs_file_end
crlf db cr lf
hello
db cr lf
db 'VPRINT - Virtual Printer (version 5.00)' cr lf
db cr lf
db 'Purpose: Redirects printer output to a disk file.' cr lf
db cr lf
db 'Syntax: VPRINT [[d:][path]filename[.ext]] [options]' cr lf
db cr lf
db 'Options: /i install (required to load resident code)' cr lf
db ' /bnn buffer size, K (nn = 1-64, default is 2)' cr lf
db ' /p1,p2,p0,pp,p3 emulate LPT1, LPT2, none, PrtSc, or all (default)' cr lf
db ' /a1,a2,a3,a0 emulate COM1, COM2, both, or none (default)' cr lf
db ' /l,c,n filter mode: drop LF, drop CR, or none (default)' cr lf
db ' /d,e redirection: disabled, enabled (default)' cr lf
db ' /f flush - empty any buffered output to disk' cr lf
db ' /s report status' cr lf
db cr lf
db 'Once installed, you can change file or options by running VPRINT again.'
db cr lf cr lf
db 'VPRINT is user-supported software. If you find this program of value, '
db 'please' cr lf
db 'support its development with a payment of $20 to:' cr lf
db cr lf
db ' Whitman Software' cr lf
db ' P.O. Box 1157' cr lf
db ' North Wales, PA 19454'
h_end
end_nonres