; PROGRAM VPRINT version 1.0
;      by Dave Whitman
;
; Redirects printer output to a specified disk file,
; providing a virtual printer.  Invoked from DOS by
; the command: VPRINT filespec
;
; Changes vector to BIOS printer routine to point
; to VPRINT code.  The VPRINT code is grafted onto
; DOS, and will not be overwritten by other programs.
;
fcb            equ [5CH]   ;file control block
name           equ [5DH]   ;name field of fcb
name_length    equ 11      ;length of name field in fcb
cur_block      equ [68H]   ;current block field
current_rec    equ [7CH]   ;current record field
record_size    equ [6AH]   ;record size field
dta_length     equ 200H    ;length of internal disk buffer
param_count    equ [80H]   ;contains # of chars in param area
param_area     equ [81H]   ;PSP unformated param area
dummy_dta      equ [80H]   ;leave dta set here on return
cr             equ  0DH    ;ascii carriage return
lf             equ  0AH    ;ascii line feed
vector_offset  equ [005CH] ;offset part of interrupt vector 17H
vector_segment equ [005EH] ;segment part of interrupt vector 17H
printer_io     equ EFD2H   ;offset of BIOS routine
;
       jmp init          ;branch around redirection code
;
;------------------------------------------------------
; This section of code will be grafted onto DOS.
; Calls to PRINTER_IO are redirected here, where
; they are converted to DOS sequential file writes.
; 
convert 
         sti                    ;enable interupts
         or ah,ah               ;request to print char?
         jnz abort              ;abort if not
;
         push dx                ;save state
         push ds 
         push cs
         pop  ds                ;and establish addr.
         mov char,al            ;save the char
;
         cmp al,lf             ;is this a line feed?
         je  exit              ;exit if so
;
         call printchar        ;print the charactor
         cmp  al,cr            ;was that a carriage return?
         jne  exit             ;exit if not
         mov al,lf             ;otherwise generate a line feed
         call printchar        ;and send it
;
exit     mov al,char           ;restore state
         pop ds
         pop dx
;
;=========================================================
; We have to return a status byte in AH.  The way I read
; Tech. Ref., to signal all ok we should send value 50H.
; However, a long DEBUG session showed that Easywriter 1.1
; expects to get 90H as the OK value.  I would interpret this
; as "printer busy", but we'll humor Easywriter.  
;=========================================================
;
abort    mov ah,90H            ;move status = ok to AH
         iret                  ;and return
;
printchar proc near
          push di
          mov di,numchars      ;position in buffer for char

          mov offset(dta)[di],al ;mov char into buffer
;
          inc di               ;update number of chars
          mov numchars,di      ;and save it
;
          cmp di,dta_length    ;is the buffer full?
          jne pc-exit          ;exit if not
          call dumpdta         ;otherwise dump it
;
pc-exit   pop di
          ret
          endp
;
dumpdta  proc near
         push ax                       ;save state
         push dx
;
         mov ah,0FH                    ;open file
         mov dx,offset(fcb)
         int 21H
;
         movw record_size,dta_length   ;set record size
 
         mov  ax,block_save            ;set fcb current block
         mov  cur_block,ax
;
         mov dx,offset(dta)            ;set disk transfer addr.
         mov ah,1AH
         int 21H
;
         mov dx, offset(fcb)           ;write buffer to disk
         mov ah,15H
         int 21H
;
         mov  ax, cur_block            ;save current block
         mov  block_save,ax
;
         mov  ax,1000H                 ;close file
         mov  dx,offset(fcb)
         int  21H
;
         movw numchars,0000H           ;buffer is now empty
;
;======================================================
; Since we don't know where the DTA was set upon entry,
; we can't really restore the state of the machine.
; What we'll do is set up a dummy DTA for the caller to
; use, so it doesn't clobber our buffered data between calls.
; This is *NOT* foolproof, but seems to work OK with Easywriter.
;======================================================
;
         mov dx,offset(dummy_dta)   ;set DTA to dummy block
         mov ah,1AH
         int 21H
 
         pop dx                     ;restore state
         pop ax
         ret                        ;and return
         endp
;
;====================================================
; Data area for resident section
;====================================================
char       db 00H           ;to save the char. passed
numchars   db 00H,00H       ;word: # of chars in buffer
block_save db 00H,00H       ;to save current block between calls
dta        ds dta_length    ;internal data buffer
;
;===================================================
; Initialization section.  Does not become resident.
;===================================================
init   cmpb name,' '              ;was a file specified?
       jnz  a1                    ;skip if so
 
       cmpb param_count,00H       ;any param chars?
       jz   default               ;skip if not
       xor  ch,ch                 ;set up for scan
       mov  cl,param_count
       mov  di,offset(param_area)
       mov  al,'/'                ;look for '/'
       repnz                      ;scan to end or 1st match
       scasb
;
       jnz default                ;skip if not found
       xorb [di],20H              ;guarantees upper case
;
       cmpb [di],'N'              ;is it an N?
       je   p_handler             ;if so, jump
;
;if no parameters decoded, just fall through
;and set up print file with default name
;
default mov  si, offset(default_file_name)
        mov  di, offset(name)
        mov  cx, name_length
        rep
        movsb
;
a1     mov dx, offset(fcb)         ;check if file exists
       mov ah,11H
       int 21H
;
       test al,al                  ;file found?
       jnz  a2                     ;skip if not
;
       mov  dx, offset(err_msg_1)  ;otherwise print
       mov  ah,09H                 ;error message
       int 21H
       int 20H                     ;and return to DOS
;
a2     mov ah,16H                  ;create file
       int 21H
;
       test al,al                  ;file created?
       jz   a3                     ;skip if so
       mov  dx, offset(err_msg_2)  ;otherwise error
       mov  ah, 09H
       int  21H
       int  20H
;
a3     movw current_rec, 0         ;fill in fcb
;
       mov ah,10H                  ;close file
       mov dx, offset(fcb)
       int 21H
;
       mov ax,2517H                ;reset printer vector
       mov dx, offset(convert)
       int 21H
;
       mov dx,offset(init)         ;point dx past resident code
       int 27H                     ;end but stay resident
;===================================================================
p_handler
;
       mov ax,0000H              ;ext. addr. for vector
       mov ds,ax
;
       cmpw vector_offset,printer_io  ;already in BIOS?
       je   p_exit                    ;exit if so
;
       mov  ds,vector_segment    ;est. addr. in original vprint seg
       call dumpdta              ;operates on buffer in original vprint
;
       mov  ax,F000H             ;reset vector 17H to BIOS routine
       mov  ds,ax                ;bios segment
       mov  dx,printer_io        ;printer support routine
       mov  ax,2517H
       int 21H 
;
p_exit int  20H                  ;return to DOS
;
;===================================================================
err_msg_1  db 'File already exists!$'
err_msg_2  db 'No room for file in directory.$'
default_file_name db 'VIRTUAL PRN'
