home *** CD-ROM | disk | FTP | other *** search
/ Share Gallery 1 / share_gal_1.zip / share_gal_1 / UT / UT019.ZIP / VPRINT.ASM < prev    next >
Assembly Source File  |  1988-03-23  |  73KB  |  1,729 lines

  1. ;=======================================================
  2. ; VPRINT version 5.00
  3. ;
  4. ; Printer redirection utility.
  5. ; Memory resident software to capture
  6. ; printer output and save to disk.
  7. ;
  8. ; Revision History:
  9. ;
  10. ; Version    Comments
  11. ; 1.00       Very primative DOS 1 version, to upgrade EasyWriter
  12. ; 2.00       Updated DOS 2 version, not released
  13. ; 2.01       Released as U/S
  14. ; 3.00       Add int 28H buffer dump, watch dos critical flag before writes.
  15. ; 3.01       Correct bug in dos critical byte handling
  16. ; 3.02       Add selective trapping of PrtSc operation
  17. ; 4.00       Add int 21H buffer dump & function 40H handler, variable buf size
  18. ; 5.00       Add emulation of async output COM1: and COM2:, kill risky mode
  19. ;============================================================================
  20. ; Copyright Information
  21. ;
  22. ; This program is copyright (c) 1986, 1987, 1988 by D. Whitman
  23. ; The source code is provided to registered users of the program
  24. ; for educational purposes, and to allow them to customize for
  25. ; their OWN use.  UNDER NO CIRCUMSTANCES MAY YOU FURTHER DISTRIBUTE
  26. ; THIS FILE, MODIFIED VERSIONS OF VPRINT, OR TRANSLATIONS INTO
  27. ; INTO OTHER LANGUAGES.
  28. ;============================================================================
  29. ; References:
  30. ;
  31. ; The following articles provided insights into safely obtaining
  32. ; DOS services from within a TSR:
  33. ;
  34. ;   Dr. Dobb's Journal, Feb 87, p 103
  35. ;   Byte Magazine, Sept 86, p 399
  36. ;   PC Magazine, Apr 14, 1987, p 313
  37. ;
  38. ; (Thanks to Wayne B'Rells for pointing out these articles!)
  39. ;=============================================================================
  40.  
  41. ;==============
  42. ; Equates
  43. ;==============
  44. cr              equ 0DH          ;carrage return character
  45. lf              equ 0AH          ;line feed character
  46. true            equ 0FFH         ;boolean "TRUE"
  47. false           equ 00H          ;boolean "FALSE"
  48. stdin           equ 0000H        ;standard input handle
  49. stdout          equ 0001H        ;standard output handle
  50. off_line        equ 0C8H         ;bad printer status value (=printer turned off)
  51. clear2send      equ 0010H        ;aux port ready for output
  52. not_clear       equ 8000H        ;aux port has timed out
  53. ready           equ 90H          ;good printer status value
  54.  
  55. param_count     equ [80H]        ;# of param chars in command line
  56. param_area      equ [81H]        ;text of DOS command line
  57. environ_ptr     equ [2CH]        ;pointer to our copy of environment
  58.  
  59. @prnstr         equ 09H          ;DOS print screen function
  60. @create         equ 3CH          ;DOS create file function
  61. @open           equ 3DH          ;DOS open file function
  62. @write          equ 40H          ;DOS write block to file/device function
  63. @close          equ 3EH          ;DOS close file function
  64. @dosver         equ 30H          ;DOS get DOS version function
  65. @alloc          equ 48H          ;DOS allocate memory block
  66. @free           equ 49H          ;DOS free allocated memory
  67. @setblock       equ 4AH          ;DOS modify allocated memory block
  68.  
  69.  
  70. entry_point     jmp   main       ;jump over resident stuff when starting up
  71.  
  72. ;===========================================================
  73. ; NEW_INT14
  74. ; This section of code traps interrupt 14H, BIOS RS232_IO.
  75. ; We test for whether we're enabled, and whether this is an
  76. ; output call for of our emulated aux ports.  If so, handle the
  77. ; call, otherwise pass it along to the original service routine.
  78. ;===========================================================
  79. new_int14
  80.                 sti                          ;enable interupts
  81.                 push  ax                     ;save state
  82.                 push  bx                     ;  ditto
  83.                 push  dx                     ;  ditto
  84.                 push  ds                     ;  ditto
  85.                 push  es                     ;  ditto
  86.                 mov   bx, cs                 ;and establish addressability
  87.                 mov   ds, bx                 ;  ditto
  88.  
  89.                 cmpb  enable_flag, true      ;are we enabled?
  90.                 jne   pass_14                ;if not, pass call down the pipe
  91.  
  92.                 inc   dl                     ;convert printer code for AND
  93.                 test  dl, enabled_aux        ;request for one of ours?
  94.                 jz    pass_14                ;if not, pass call down the pipe
  95.  
  96.                 cmp   ah, 02H                ;receive character request?
  97.                 je    pass_14                ;if so, pass along to BIOS
  98.  
  99.                 cmp   ah, 01H                ;output char request?
  100.                 jne   ni14_status            ;if not, just return status
  101.                 call  output_char            ;else handle char output
  102.  
  103. ni14_short_status
  104.                 pop   es
  105.                 pop   ds
  106.                 pop   dx
  107.                 pop   bx
  108.                 pop   ax
  109.                 mov   ah, cs:aux_status
  110.                 iret
  111.  
  112. ni14_status
  113.                 pop   es
  114.                 pop   ds
  115.                 pop   dx
  116.                 pop   bx
  117.                 pop   ax
  118.                 mov   ax, cs:aux_status
  119.                 iret                         ;return to caller
  120.  
  121. pass_14         pop   es     ;restore state
  122.                 pop   ds
  123.                 pop   dx
  124.                 pop   bx
  125.                 pop   ax
  126.                 ;===================================================
  127.                 ;jump to the original vector.  Note CS: prefix,
  128.                 ;since we just lost our addressability by popping DS
  129.                 ;===================================================
  130.                 cli                          ;real INT turns off interupts
  131.                 jmpf  cs:old_int14           ;allow BIOS to do it
  132.  
  133. ;===========================================================
  134. ; NEW_INT17
  135. ; This section of code traps interrupt 17H, BIOS PRINTER_IO.
  136. ; We test for whether we're enabled, and whether this is a
  137. ; one of our emulated printers.  If so, handle the call,
  138. ; otherwise pass it along to the original service routine.
  139. ;===========================================================
  140. new_int17
  141.                 sti                  ;enable interupts
  142.                 push  ax             ;save state
  143.                 push  bx             ;  ditto
  144.                 push  dx             ;  ditto
  145.                 push  ds             ;  ditto
  146.                 push  es             ;  ditto
  147.                 mov   bx, cs         ;and establish addressability
  148.                 mov   ds, bx         ;  ditto
  149.  
  150.                 cmpb  enable_flag, true      ;are we enabled?
  151.                 jne   pass_17                ;if not, pass call down the pipe
  152.  
  153.                 cmpb  enabled_printers, 8    ;are we in PrtSc only mode?
  154.                 jne   r1                     ;skip if not
  155.                 les   bx, ps_status_ptr      ;else get pointer to PrtSc status
  156.                 testb es:[bx], 0FFH          ;is PrtSc active?
  157.                 jz    pass_17                ;if not, pass call down the pipe
  158.                 jmps  r2                     ;otherwise, handle print request
  159.  
  160. r1              inc   dl                     ;convert printer code for AND
  161.                 test  dl, enabled_printers   ;request for one of ours?
  162.                 jz    pass_17                ;if not, pass call down the pipe
  163.  
  164.                 or    ah, ah                 ;request to print?
  165.                 jnz   return_status          ;if not, either init or status
  166. r2              call  output_char            ;else handle char output
  167.  
  168. return_status
  169.                 pop   es
  170.                 pop   ds
  171.                 pop   dx
  172.                 pop   bx
  173.                 pop   ax
  174.                 mov   ah, cs:prn_status
  175.                 iret                         ;return to caller
  176.  
  177. pass_17         pop   es     ;restore state
  178.                 pop   ds
  179.                 pop   dx
  180.                 pop   bx
  181.                 pop   ax
  182.                 ;============================================
  183.                 ;Fake an interupt instruction and jump to the
  184.                 ;original vector.  Note CS: prefix, since we
  185.                 ;just lost our addressability by popping DS
  186.                 ;============================================
  187.                 cli                           ;real INT turns off interupts
  188.                 jmpf  cs:old_int17            ;allow BIOS to do it
  189.  
  190. ;========================================================
  191. ; NEW_INT21
  192. ;
  193. ; This section of code intercepts interupt 21H, the DOS function
  194. ; dispatcher.  We do this for two reasons:
  195. ;
  196. ;     1. It gives us a chance to empty our buffer every time
  197. ;        an application makes a DOS call, at a time when DOS
  198. ;        *has* to be idle.  (It's idle, because it hasn't
  199. ;        received the application's call yet - sneaky, eh?)
  200. ;        Note that to be strictly safe, we still check the
  201. ;        DOS_CRITICAL byte to rule out the situation where
  202. ;        DOS is calling itself.
  203. ;
  204. ;     2. We can trap function 40H and do it ourselves.  A debug
  205. ;        session revealed that DOS goes critical during function 40H
  206. ;        processing.  If the application requests output of a block
  207. ;        larger than our buffer size, with DOS critical the whole time,
  208. ;        our buffer will overflow.  If function 40H gets called with
  209. ;        a handle we're emulating, we'll do it ourselves, and INT 17H
  210. ;        can dump the buffer since DOS isn't critical yet.
  211. ;
  212. ; This allows us to support essentially all software.  The only
  213. ; hitch is if a program opens the printer or COM port as if it
  214. ; was a file, we won't be able to recognise the generated handle.
  215. ; As long as applications either use the pre-defined handles, or
  216. ; only write blocks smaller than our buffer, we're OK.
  217. ;============================================================
  218. new_int21       proc  far
  219.                 pushf                      ;save caller's flags
  220.                 sti                        ;allow interupts
  221.                 cmpb  cs:need_dump, true   ;do we need a buffer dump?
  222.                 jne   test_function        ;no, skip
  223.                                            ;try to dump the buffer
  224.                 push  es                   ;else get DOS critical byte
  225.                 push  bx
  226.                 les   bx, cs:dos_c_ptr     ;get pointer to dos critical byte
  227.                 cmpb  es:[bx], 01H         ;is dos critical?
  228.                 pop   bx
  229.                 pop   es
  230.                 jge   test_function        ;abort if so
  231.                 call  dump_buffer
  232.  
  233. test_function   cmp   ah, @write           ;is this a write block call?
  234.                 jne   ni21_pass            ;nope, pass along
  235.                 cmpb  cs:enable_flag, true     ;are we enabled?
  236.                 jne   ni21_pass            ;if not, pass along
  237. ;=============================================================
  238. ; Cut ourselves some extra insurance here.  If the requested
  239. ; block is bigger than the available room in our buffer, force
  240. ; a buffer flush, whether we recognize the handle or not.  
  241. ; This maximizes the probability we can take care of output
  242. ; when the user has opened his device as if it was a file.  
  243. ;=============================================================
  244.                 cmpw  cs:numchars, 0000H   ;anything at all in buffer?
  245.                 je    test_handles         ;skip if nothing there
  246.                 push  di
  247.                 mov   di, cs:buf_size      ;calculate room left in buffer
  248.                 sub   di, cs:numchars
  249.                 cmp   di, cx               ;is there enough room for this call?
  250.                 pop   di
  251.                 jae   test_handles         ;skip if enough room
  252.                 movb  need_dump, true      ;assert need_dump, in case we can't
  253.                 push  es                   ;else try to flush the buffer
  254.                 push  bx
  255.                 les   bx, cs:dos_c_ptr     ;get pointer to dos critical byte
  256.                 cmpb  es:[bx], 01H         ;is dos critical?
  257.                 pop   bx
  258.                 pop   es
  259.                 jge   test_handles         ;abort if so
  260.                 call  dump_buffer
  261.  
  262. ;=======================================================
  263. ; Now check for the handles we're specifically emulating
  264. ;=======================================================
  265. test_handles    cmp   bx, 4                ;check for std printer handle
  266.                 jne   ni21_aux             ;no, check for aux
  267.                 testb cs:enabled_printers, 1  ;are we emulating the std printer?
  268.                 jz    ni21_pass            ;if not, pass along
  269.                 call  handle_40_prn        ;else do the function 40 ourselves
  270.                 jmps  ni21_exit
  271.  
  272. ni21_aux        cmp   bx, 3                ;check for std aux handle
  273.                 jne   ni21_pass            ;pass on if not
  274.                 testb cs:enabled_aux, 1    ;are we emulating the std aux device?
  275.                 jz    ni21_pass            ;if not, pass along
  276.                 call  handle_40_aux        ;else do the function 40 ourselves
  277.                 jmps  ni21_exit
  278.  
  279. ;==========================================================
  280. ; DOS returns from interupts in a strange way, not via IRET
  281. ; We need to return the callers original flags, but with
  282. ; the carry flag cleared (it's set if DOS had an error).  
  283. ; We do this by popping our saved user flags, clearing the
  284. ; carry bit, and doing a RET 2 to discard the flag set
  285. ; pushed by the original INT 21H instruction.  
  286. ;===========================================================
  287. ni21_exit       popf                       ;restore saved user flags
  288.                 clc                        ;indicate no errors
  289.                 ret   2                    ;simulate DOS's return
  290. ni21_pass
  291.                 popf                       ;restore saved user flags
  292.                 cli                        ;real INT turns off interupts
  293.                 jmpf cs:old_int21          ;and pass call to DOS
  294.                 endp
  295.  
  296. ;======================================================
  297. ; NEW_INT28
  298. ;
  299. ; This section of code intercepts interrupt 28H.
  300. ; This is an undocumented DOS interrupt, which DOS
  301. ; seems to call periodically during idle periods.
  302. ; The DOS external command PRINT works by intercepting
  303. ; int 28H.   If our buffer is more than half full
  304. ; during an int 28H call, try to empty it to disk.
  305. ;
  306. ; Intercepting int 28H allows us to empty the buffer
  307. ; under conditions where the DOS critical byte is set
  308. ; during calls to VPRINT's BIOS interupts.
  309. ;
  310. ; According to the PC magazine article referenced in the
  311. ; prolog, it's safe to call DOS during int 28H processing,
  312. ; AS LONG AS WE ONLY USE DOS FUNCTIONS HIGHER THAN 0CH.
  313. ; Using the lower functions will clobber one of DOS's
  314. ; internal stacks.  Since we only use high functions,
  315. ; we don't need to check the DOS critical byte before
  316. ; requesting services.
  317. ;========================================================
  318. new_int28
  319.                 sti                      ;be polite, answer the phone
  320.                 push ax
  321.                 push bx
  322.                 push ds
  323.  
  324.                 mov  ax, cs              ;establish local addressability
  325.                 mov  ds, ax
  326.  
  327.                 mov  ax, numchars        ;how many chars in our buffer?
  328.                 cmp  ax, trigger         ;are we above the trigger amount?
  329.                 jb   idle_exit           ;abort if below
  330.  
  331.                 call  dump_buffer
  332.  
  333. idle_exit       pop  ds
  334.                 pop  bx
  335.                 pop  ax
  336.                 cli
  337.                 jmpf cs:old_int28        ;now service original int 28 routine.         
  338.  
  339. ;===================================================
  340. ; HANDLE_40_PRN
  341. ;
  342. ; This routine emulates a DOS "write block" function
  343. ; to the standard printer device.  We send the block
  344. ; one character at a time to our int 17H handler.
  345. ;
  346. ; By emulating this function, we prevent DOS from
  347. ; going critical during the int 17H calls, so
  348. ; they can safely empty our buffer as needed.
  349. ;===================================================
  350. handle_40_prn   proc near
  351.                 jcxz h4p_exit               ;if nothing to write, abort
  352.  
  353.                 push si                     ;else save state and do it
  354.                 push cx
  355.                 push dx
  356.  
  357.                 cld                         ;8088 <-- autoincrement mode
  358.                 mov  si, dx                 ;ds:si <-- pointer to string
  359.                 xor  dx, dx                 ;handle 4 is printer id = 0
  360.  
  361. h4p_loop        lodsb                       ;get a char
  362.                 xor  ah, ah                 ;print char function
  363.                 int  17H                    ;call our routine
  364.                 loop h4p_loop               ;loop til CX = 0
  365.  
  366.                 pop dx                      ;restore regs
  367.                 pop cx
  368.                 pop si
  369.  
  370. h4p_exit        mov ax, cx                  ;tell caller we wrote all his chars
  371.                 ret
  372.                 endp
  373.  
  374. ;===================================================
  375. ; HANDLE_40_AUX
  376. ;
  377. ; This routine emulates a DOS "write block" function
  378. ; to the standard auxiliary device.  Implementation
  379. ; and rationale are analogous to HANDLE_40_PRN.
  380. ;===================================================
  381. handle_40_aux   proc near
  382.                 jcxz h4a_exit               ;if nothing to write, abort
  383.  
  384.                 push si                     ;else save state and do it
  385.                 push cx
  386.                 push dx
  387.  
  388.                 cld                         ;8088 <-- autoincrement mode
  389.                 mov  si, dx                 ;ds:si <-- pointer to string
  390.                 xor  dx, dx                 ;handle 3 is async id = 0
  391.  
  392. h4a_loop        lodsb                       ;get a char
  393.                 mov  ah, 01H                ;print char function
  394.                 int  14H                    ;call our routine
  395.                 loop h4a_loop               ;loop til CX = 0
  396.  
  397.                 pop dx                      ;restore regs
  398.                 pop cx
  399.                 pop si
  400.  
  401. h4a_exit        mov ax, cx                  ;tell caller we wrote all his chars
  402.                 ret
  403.                 endp
  404.  
  405. ;=================================================
  406. ; OUTPUT_CHAR
  407. ;
  408. ; We implement 3 printing modes: verbatim, and 2
  409. ; "trapping" modes to force proper CR/LF terminated
  410. ; lines, since some programs don't bother to send
  411. ; both CR and LF to a printer.  This routine
  412. ; handles the various trapping modes, and then
  413. ; calls PRINT_CHAR for the actual output.
  414. ;=================================================
  415. output_char     proc  near
  416.                 push  ax
  417.                 push  bx
  418.                 push  ds
  419.                 mov   bx, cs                 ;establish local addressability
  420.                 mov   ds, bx
  421.  
  422.                 cmpb  trap_char, lf          ;are we in trap linefeed mode?
  423.                 jne   try_cr                 ;nope, skip
  424.                 cmp   al, lf                 ;is this a line feed?
  425.                 je    oc_exit                ;exit if so
  426.                 call  print_char             ;otherwise print it
  427.                 cmp   al, cr                 ;was that a carriage return?
  428.                 jne   oc_exit                ;nope, we're done
  429.                 mov   al, lf                 ;if so, add line feed
  430.                 call  print_char
  431.                 jmps  oc_exit                ;and exit
  432.  
  433. try_cr          cmpb  trap_char, cr          ;trap carrage return mode?
  434.                 jne   no_traps               ;nope, skip
  435.                 cmp   al, cr                 ;is this a CR?
  436.                 je    oc_exit                ;exit if so
  437.                 cmp   al, lf                 ;line feed?
  438.                 jne   no_traps               ;nope, skip
  439.                 mov   al, cr                 ;yes, send CR first
  440.                 call  print_char
  441.                 mov   al, lf                 ;& THEN send line feed
  442.  
  443. no_traps        call  print_char
  444.  
  445. oc_exit         pop   ds
  446.                 pop   bx
  447.                 pop   ax
  448.                 ret
  449.                 endp
  450.  
  451. ;============================================
  452. ; PRINT_CHAR
  453. ;
  454. ; Add the character in AL to the buffer.
  455. ; If the buffer is full enough, dump it to disk.
  456. ;============================================
  457. print_char      proc  near
  458.                 push  ax
  459.                 push  bx
  460.                 push  di
  461.                 push  ds
  462.                 push  es
  463.  
  464.                 mov   bx, cs                 ;establish local addressability
  465.                 mov   ds, bx
  466.  
  467.                 mov   di, numchars           ;position in buffer for char
  468.                 cmp   di, buf_size           ;is buffer overflowing?
  469.                 jae   pc_0                   ;drop char & try to dump buffer
  470.                 mov   bx, buffer_seg         ;else char to buffer
  471.                 mov   es, bx                 ;    establish addr. of buffer
  472.                 mov   es:[di], al            ; and move char into buffer
  473.  
  474.                 inc   di                     ;bump number of characters
  475.                 mov   numchars, di           ;and save
  476.                 cmp   di, trigger            ;is buffer full enough to dump?
  477.                 jb    pc_exit                ;if not, return
  478.  
  479. ;===============================================================
  480. ; If we call for DOS services when DOS is in a re-entrant condition,
  481. ; we can mung up the system by trashing one of DOS's internal stacks.
  482. ; We can tell if it's safe by examining the DOS_CRITICAL byte,
  483. ; an undocumented flag maintained by DOS.  If this byte is zero,
  484. ; it is safe to call DOS for i/o.
  485. ;=================================================================
  486. ; During installation, we determined the location of this byte by
  487. ; using undocumented DOS function 34H.  Function 34H returns a
  488. ; pointer to DOS_CRITICAL in ES:BX.  Note that we *had* to do this
  489. ; during installation, since if DOS is critical now, we're in a
  490. ; catch-22 situation, where we could crash the system by calling
  491. ; DOS for the pointer.  Fortunately, the byte doesn't seem to move.
  492. ;=================================================================
  493. pc_0            les   bx, dos_c_ptr          ;get pointer to DOS_CRITICAL
  494.                 cmpb  es:[bx], 01H           ;is dos critical?
  495.                 jge   pc_fail                ;abort if so
  496.  
  497. pc_1            call  dump_buffer
  498.                 jnc   pc_exit                ;if successful, exit
  499.  
  500. pc_fail         movb  need_dump, true        ;couldn't empty, so set flag
  501.                 mov   ax, numchars
  502.                 cmp   ax, buf_size           ;is the buffer completely full?
  503.                 jb    pc_exit                ;if not, hope we can dump later
  504.                 movb  prn_status, off_line   ;else send bad status to caller
  505.                 movw  aux_status, not_clear  ;ditto
  506. pc_exit
  507.                 pop   es
  508.                 pop   ds
  509.                 pop   di
  510.                 pop   bx
  511.                 pop   ax
  512.                 ret
  513.                 endp
  514.  
  515. ;======================================================
  516. ; DUMP_BUFFER
  517. ;
  518. ; Attempts to dump the print buffer to disk.
  519. ; Clears carry flag if sucessful, sets it if not.
  520. ;
  521. ; No safety checking is performed.  DUMP_BUFFER
  522. ; should only be called if the caller knows it's
  523. ; safe to call DOS for services.
  524. ;
  525. ; We don't use any of the old DOS 1.x  "traditional
  526. ; character device" calls, so it should be safe to call
  527. ; DUMP_BUFFER from an INT 28H handler, regardless of the
  528. ; state of DOS_CRITICAL.
  529. ;
  530. ; Preserves all registers.
  531. ;=======================================================
  532. dump_buffer     proc  near
  533.                 movb  cs:need_dump, false    ;don't let other routines call again
  534.  
  535.                 push  ax                     ;save state
  536.                 push  bx
  537.                 push  cx
  538.                 push  dx
  539.                 push  ds
  540.  
  541.                 mov   ax, cs                 ;establish addressability
  542.                 mov   ds, ax                 ;  ditto
  543.  
  544.                 mov   dx, offset(filename)   ;point to filename
  545.                 mov   ax, 3D01H              ;open file for write
  546.                 call  real_DOS               ;skip our int 21H trap routine
  547.                 jnc   db_cont                ;continue if ok
  548.  
  549. ;===================================================================
  550. ; if we can't find our file, maybe the user changed the disk on us.
  551. ; Try making a new file.
  552. ;===================================================================
  553.                 mov   ah, @create
  554.                 xor   cx, cx                 ;normal file attribute
  555.                 mov   dx, offset(filename)
  556.                 call  real_DOS               ;skip our int 21H trap routine
  557.                 jnc   db_fail
  558.  
  559. db_cont         mov   bx, ax                 ;get handle from last call
  560.                 mov   ax, 4202H              ;seek EOF
  561.                 xor   cx, cx
  562.                 xor   dx, dx
  563.                 call  real_DOS               ;skip our int 21H trap routine
  564.  
  565.                 push  ds                     ;establish addr. of buffer
  566.                 mov   ax, buffer_seg         ;ditto
  567.                 mov   ds, ax                 ;ditto
  568.                 mov   ah, @write             ;request file write
  569.                 mov   cx, cs:numchars        ;number of bytes in buffer
  570.                 xor   dx, dx                 ;from offset 0 in buffer
  571.                 call  real_DOS               ;skip our int 21H trap routine
  572.                 pop   ds                     ;reestablish local addressability
  573.  
  574.                 mov   ah, @close             ;close file
  575.                 call  real_DOS               ;skip our int 21H trap routine
  576.  
  577. ;=================
  578. ; Exit Sucessfully
  579. ;=================
  580.                 movw  numchars, 0000H        ;buffer is now empty
  581.                 movb  prn_status, ready      ;send normal status back to caller
  582.                 movw  aux_status, clear2send ;ditto
  583.                 pop   ds
  584.                 pop   dx
  585.                 pop   cx
  586.                 pop   bx
  587.                 pop   ax
  588.                 clc                          ;indicate sucess
  589.                 ret
  590. ;==============
  591. ; Failure Exit
  592. ;==============
  593. db_fail         movb  need_dump, true            ;still need to dump buffer
  594.                 movb  prn_status, off_line       ;send bad status to caller
  595.                 movw  aux_status, not_clear
  596.                 pop   ds
  597.                 pop   dx
  598.                 pop   cx
  599.                 pop   bx
  600.                 pop   ax
  601.                 stc                          ;indicate failure
  602.                 ret
  603.                 endp
  604.  
  605.  
  606. ;======================================================
  607. ; REAL_DOS
  608. ;
  609. ; This allows our resident routines to make calls directly
  610. ; to DOS, skipping our int 21H handler, and avoiding
  611. ; some pathological cases of infinite recursion.
  612. ;======================================================
  613. real_dos        proc near
  614.                 pushf                      ;real INT pushes flags
  615.                 cli                        ;real INT turns off interupts
  616.                 callf cs:old_int21
  617.                 sti                        ;re-enable interupts
  618.                 ret
  619.                 endp
  620.  
  621.                list                        ;$$$
  622. ;=======================
  623. ; Resident data section
  624. ;=======================
  625. verify          db 'VPRINT 5.00'     ;recognition string
  626. verify_end
  627.  
  628. old_int14        dw 0000H, 0000H     ;original async handler vector
  629. old_int17        dw 0000H, 0000H     ;original printer handler vector
  630. old_int21        dw 0000H, 0000H     ;original dos function vector
  631. old_int28        dw 0000H, 0000H     ;original dos idle handler vector
  632. dos_c_ptr        dw 0000H, 0000H     ;pointer to dos critical byte
  633. ps_status_ptr    dw 0000H, 0050H     ;pointer to print screen status byte
  634. need_dump        db false            ;boolean flag - does buffer want dumping?
  635. enable_flag      db true             ;boolean flag - are we active?
  636. enabled_printers db 03H              ;which printer(s) are we emulating?
  637. enabled_aux      db 00H              ;which aux port(s) are we emulating?
  638. trap_char        db 00H              ;character to trap (none, cr, or lf)
  639. numchars         dw 0000H            ;number of chars now in buffer
  640. prn_status       db ready            ;printer status to send to caller
  641. aux_status       dw clear2send       ;aux status: assert clear to send
  642. filename         ds 73               ;length, 63 byte path, filename.ext 00
  643. buffer_seg       dw 0000H            ;segment pointer for buffer
  644. buf_size         dw 0800H            ;size of buffer
  645. trigger          dw 0400H            ;if this many chars in buffer, try to dump
  646. end_resident                         ;end of resident code and data
  647.                                      ;buffer segment follows this point
  648.  
  649.                nolist                        ;$$$
  650. ;================================
  651. ; Beginning of non-resident code
  652. ;================================
  653.  
  654. main            proc  near
  655.                 call  check_dos
  656.                 call  uc_command_line
  657.                 call  parse_options
  658.                 cmpb  param_found, true
  659.                 jne   exit_with_help
  660.  
  661.                 ;print title message
  662.                 mov   ah, @write
  663.                 mov   bx, stdout
  664.                 mov   cx, short_end-short_hello
  665.                 mov   dx, offset(short_hello)
  666.                 int   21H
  667.  
  668.                 call  check_install
  669.                 cmpb  install_request, true
  670.                 jne   m1
  671.                 jmp   install_code     ;program terminates in this routine
  672. m1              call  update_res
  673.                 int   20H              ;program halts here
  674. ;======================================================
  675. ; If no valid options were given, print a help screen,
  676. ; and exit without doing anything else.
  677. ;======================================================
  678. exit_with_help  mov   ah, @write       ;print help message
  679.                 mov   bx, stdout       ;on standard output
  680.                 mov   cx, h_end-hello
  681.                 mov   dx, offset(hello)
  682.                 int   21H
  683.                 mov   ax,4C00H         ;exit with ERRORLEVEL 0
  684.                 int   21H
  685.                 endp
  686.  
  687. ;=================================================
  688. ; CHECK_DOS
  689. ; Confirms that we're running under DOS 2 or later.
  690. ; Aborts program with error message if not.
  691. ;=================================================
  692. check_dos       proc  near
  693.                 mov   ah, @dosver      ;get DOS version
  694.                 int   21H
  695.                 cmp   al, 2            ;DOS 2 or later?
  696.                 jge   cd_exit          ;yes, skip
  697.                 mov   ah, @prnstr      ;no, bitch
  698.                 mov   dx, offset(baddos)
  699.                 int   21H
  700.                 int   20H              ;and exit
  701. cd_exit         ret
  702.                 endp
  703.  
  704. ;==============================================
  705. ; UC_COMMAND_LINE
  706. ; Converts the command line to all upper case.
  707. ;==============================================
  708. uc_command_line proc  near
  709.                 push  si
  710.                 push  di
  711.                 mov   cl, param_count         ;length of command line
  712.                 xor   ch, ch                  ;  "    "     "      "
  713.                 jcxz  uc_exit                 ;abort if nothing to process
  714.                 mov   si, offset(param_area)  ;pointers to command line
  715.                 mov   di, si                  ;   "      "    "      "
  716. uc_1            lodsb
  717.                 cmp   al, 'a'     ;too low?
  718.                 jl    uc_2        ;skip char if so
  719.                 cmp   al, 'z'     ;too high?
  720.                 jg    uc_2        ;skip char if so
  721.                 and   al, 0DFH    ;otherwise force upper case
  722. uc_2            stosb
  723.                 loop  uc_1
  724.  
  725. uc_exit         pop   di
  726.                 pop   si
  727.                 ret
  728.                 endp
  729.  
  730. ;==================================================
  731. ; PARSE_OPTIONS
  732. ; Parse the command line, and set flags accordingly
  733. ;==================================================
  734. parse_options   proc  near
  735.                 xor   ch, ch           ;CX <== # of parameter chars
  736.                 mov   cl, param_count  ;ditto
  737.                 mov   di, offset(param_area)
  738.                 mov   al, ' '          ;search for 1st non-blank
  739.                 cld
  740.                 rep
  741.                 scasb
  742.                 jcxz  po_exit_1        ;no parameters? just print hello screen
  743.                 jmps  po_cont_1        ;otherwise continue
  744. po_exit_1       jmp   po_exit          ;need long jump, hence convolutions
  745.  
  746. po_cont_1       dec   di               ;back up to char found
  747.                 inc   cx               ;  ditto
  748.  
  749.                 cmpb  [di], '?'        ;help request?
  750.                 je    po_exit_1        ;exit by printing help
  751.  
  752.                 call  get_filename     ;read user's or use default
  753.                 jcxz  po_exit_1        ;out of param chars?
  754.  
  755. po1             mov   al, '/'          ;nope, scan for options
  756.                 cld
  757.                 repne
  758.                 scasb
  759.                 jcxz  po1a
  760.                 jmps  po2              ;found, continue
  761. po1a            jmp   po_exit          ;none found, skip
  762.  
  763. po2             mov   al, [di]         ;get option char
  764.  
  765.                 cmp   al, 'I'          ;install request?
  766.                 jne   po3              ;nope, skip
  767.                 movb  install_request, true
  768.                 movb  param_found,true
  769.                 jmps  po1              ;and loop
  770.  
  771. po3             cmp   al, 'P'          ;printer number request?
  772.                 jne   po5              ;nope, skip
  773.                 movb  set_printers, true
  774.                 movb  param_found, true
  775.                 inc   di               ;get next char
  776.                 dec   cx
  777.                 mov   al, [di]
  778.                 cmp   al, 'P'          ;just PrtSc?
  779.                 jne   po3a
  780.                 movb  enabled_printers, 8
  781.                 jmps  po1
  782. po3a            cmp   al, '0'          ;no LPT output?
  783.                 jne   po3b
  784.                 movb  enabled_printers, 0
  785.                 jmps  po1
  786. po3b            cmp   al, '1'          ;LPT1: request
  787.                 jne   po3c
  788.                 movb  enabled_printers, 1
  789.                 jmps  po1
  790. po3c            cmp   al, '2'          ;LPT2 request?
  791.                 jne   po3d
  792.                 movb  enabled_printers, 2
  793.                 jmps  po1
  794. po3d            cmp   al, '3'          ;both ports request?
  795.                 jne   po4              ;nope, skip
  796.                 movb  enabled_printers, 3
  797. po4             jmps  po1
  798.  
  799. po5             cmp   al, 'A'          ;COM port number request?
  800.                 jne   po6              ;nope, skip
  801.                 movb  set_aux, true
  802.                 movb  param_found, true
  803.                 inc   di               ;get next char
  804.                 dec   cx
  805.                 mov   al, [di]
  806.                 cmp   al, '0'          ;no COM output
  807.                 jne   po5a
  808.                 movb  enabled_aux, 0
  809.                 jmp   po1
  810. po5a            cmp   al, '1'          ;COM1: request
  811.                 jne   po5b
  812.                 movb  enabled_aux, 1
  813.                 jmp   po1
  814. po5b            cmp   al, '2'          ;COM2 request?
  815.                 jne   po5c
  816.                 movb  enabled_aux, 2
  817.                 jmp   po1
  818. po5c            cmp   al, '3'          ;both ports request?
  819.                 jne   po5d             ;nope, skip
  820.                 movb  enabled_aux, 3
  821. po5d            jmp   po1
  822.  
  823. po6             cmp   al, 'S'          ;status request?
  824.                 jne   po7              ;nope, skip
  825.                 movb  status_request, true
  826.                 movb  param_found, true
  827.                 jmp   po1
  828.  
  829. po7             cmp   al, 'E'          ;enable request?
  830.                 jne   po8              ;nope, skip
  831.                 movb  enable_flag, true
  832.                 movb  set_enable_state, true
  833.                 movb  param_found,true
  834.                 jmp   po1
  835.  
  836. po8             cmp   al, 'D'          ;disable request?
  837.                 jne   po9              ;nope, skip
  838.                 movb  enable_flag, false
  839.                 movb  set_enable_state, true
  840.                 movb  param_found,true
  841.                 jmp   po1
  842.  
  843. po9             cmp   al, 'N'          ;neutral request?
  844.                 jne   po10             ;nope, skip
  845.                 movb  trap_char, 00H
  846.                 movb  set_trap_mode, true
  847.                 movb  param_found,true
  848.                 jmp   po1
  849.  
  850. po10            cmp   al, 'C'          ;trap CR request?
  851.                 jne   po11             ;nope, skip
  852.                 movb  trap_char, cr
  853.                 movb  set_trap_mode, true
  854.                 movb  param_found,true
  855.                 jmp   po1
  856.  
  857. po11            cmp   al, 'L'          ;trap LF request?
  858.                 jne   po12             ;nope, skip
  859.                 movb  trap_char, lf
  860.                 movb  set_trap_mode, true
  861.                 movb  param_found,true
  862.                 jmp   po1
  863.  
  864. po12            cmp   al, 'F'          ;flush buffer request?
  865.                 jne   po14             ;nope, skip
  866.                 movb  flush_request, true
  867.                 movb  param_found, true
  868. po13            jmp   po1
  869.  
  870. ;=============================================
  871. ; Note below that we don't set the normal two flags,
  872. ; since /B is only valid during installation.                                    
  873. ;=============================================
  874. po14            cmp   al, 'B'          ;set buffer size request?
  875.                 jne   po15
  876.                 xor   ax, ax           ;clear running total
  877.                 mov   dl, 10           ;frequently used constant
  878. po14a           cmpb  1[di], '0'       ;next char valid digit?
  879.                 jb    po14b            ;done if not
  880.                 cmpb  1[di], '9'
  881.                 ja    po14b
  882.                 mul   dl               ;multiply by 10 for next digit
  883.                 inc   di               ;point to next digit
  884.                 dec   cx               ;used up a char
  885.                 mov   bl, [di]         ;get the next digit
  886.                 sub   bl, '0'          ;convert from ASCII to binary
  887.                 xor   bh, bh           ;and add to running total
  888.                 add   ax, bx
  889.                 jmps  po14a
  890. ;===================================
  891. ; We now have the buffer size in K.                                              
  892. ; Validate, and convert to bytes for internal use.                              
  893. ;===================================
  894. po14b           cmp   ax, 1            ;force outliers to min or max value
  895.                 jge   po14c
  896.                 mov   ax, 1
  897. po14c           cmp   ax, 64
  898.                 jle   po14d
  899.                 mov   ax, 64
  900.                                        ;convert to bytes
  901. po14d           push  cx               ;save number of param chars left
  902.                 mov   cl, 9            ;multiply by 2^9 for trigger amount
  903.                 shl   ax, cl           ;(trigger is buf_size/2)
  904.                 mov   trigger, ax      ;save trigger amount
  905.                 shl   ax               ;multiply by 2 for buf_size
  906.                 cmp   ax, 0000H        ;handle conversion overflow
  907.                 jne   po14e            ;skip if ok
  908.                 mov   ax, 0FFFFH       ;otherwise use MaxInt
  909. po14e           mov   cs:buf_size, ax  ;save buf_size
  910.                 pop   cx               ;restore number of param chars left
  911.  
  912. po15            jmp   po1
  913.  
  914. po_exit         ret
  915.                 endp
  916.  
  917. ;==============================================================
  918. ; CHECK_INSTALL
  919. ; Checks to see if a copy of VPRINT is already resident.
  920. ; Sets the flag INSTALLED accordingly.  ES is set to the segment
  921. ; of the current printer support routine, and the current
  922. ; printer support vector is saved for possible re-use.
  923. ;==============================================================
  924. check_install   proc  near
  925.  
  926.                 ;save the current bios printer vector
  927.                 mov   ax, 3517H           ;get vector 17 (printer_io)
  928.                 int   21H
  929.                 mov   old_int17, bx       ;and save it
  930.                 mov   old_int17+2, es
  931.  
  932. ;======================================================
  933. ; ES now points to the current routine's segment
  934. ; If the current routine is VPRINT already, variables in
  935. ; our segment are at same offsets as those in the resident
  936. ; routine.  The only difference is that DS points to our
  937. ; stuff, while ES points to the resident routine.
  938. ;========================================================
  939. ; Compare our verifier string with the same location
  940. ; in the current printer support routine.  If they match,
  941. ; a copy of us is already resident.
  942. ;========================================================
  943.                 mov   di, offset(verify)
  944.                 mov   si, di                   ;same offset in both segments
  945.                 mov   cx, verify_end-verify    ;length of verifier string
  946.                 cld
  947.                 repe
  948.                 cmpsb
  949.                 jcxz  ci_yes
  950.                 movb  installed, false    ;not matched, so not installed
  951.                 jmps  ci_exit
  952. ci_yes          movb  installed, true     ;matched, so already installed
  953. ci_exit         ret
  954.                 endp
  955.  
  956. ;======================================================
  957. ; INSTALL_CODE
  958. ; If we're not already there, install resident code.
  959. ;======================================================
  960. install_code    proc  near
  961.                 cmpb  installed, true     ;are we aleady installed?
  962.                 jne   ic_1                ;no, continue
  963.                 jmp   ic_res_error        ;yes, bitch (need long jump)
  964.  
  965.                 ;create/open our file
  966. ic_1            mov   ah, @create         ;create file
  967.                 xor   cx, cx              ;normal file attribute
  968.                 mov   dx, offset(filename)
  969.                 int   21H                 ;to make sure we can
  970.                 jnc   ic_2                ;if so, continue
  971.                 jmp   ic_open_error       ;else bitch (need long jump)
  972.  
  973. ic_2            mov   ah, @close          ;close it until we need it
  974.                 int   21H
  975.  
  976.                 push  es                  ;summarize switch settings
  977.                 push  ds
  978.                 pop   es
  979.                 call  print_status
  980.                 pop   es
  981.  
  982.                 ;print reassuring message
  983.                 mov   ah, @write
  984.                 mov   bx, stdout
  985.                 mov   cx, inst_end-inst_msg
  986.                 mov   dx, offset(inst_msg)
  987.                 int   21H
  988.  
  989.                 ;grab the BIOS printer vector for our own use.
  990.                 ;(we already saved it's current value)
  991.                 mov   dx, offset(new_int17)
  992.                 mov   ax, 2517H
  993.                 int   21H
  994.  
  995.                 ;save the current BIOS async vector
  996.                 mov   ax, 3514H            ;get vector 14H (rs232_io)
  997.                 int   21H
  998.                 mov   old_int14, bx        ;and save it
  999.                 mov   old_int14+2, es
  1000.  
  1001.                 ;and grab it for our own use.
  1002.                 mov   dx, offset(new_int14)
  1003.                 mov   ax, 2514H
  1004.                 int   21H
  1005.  
  1006.                 ;save the current DOS function dispatcher vector
  1007.                 mov   ax, 3521H             ;get vector 21H
  1008.                 int   21H
  1009.                 mov   old_int21, bx         ;and save it
  1010.                 mov   old_int21+2, es
  1011.  
  1012.                 ;and grab it for our own use.
  1013.                 mov   dx, offset(new_int21)
  1014.                 mov   ax, 2521H
  1015.                 int   21H
  1016.  
  1017.                 ;save the current dos idle vector
  1018.                 mov   ax, 3528H             ;get vector 28H (dos idle)
  1019.                 int   21H
  1020.                 mov   old_int28, bx        ;and save it
  1021.                 mov   old_int28+2, es
  1022.  
  1023.                 ;and grab it for our own use.
  1024.                 mov   dx, offset(new_int28)
  1025.                 mov   ax, 2528H
  1026.                 int   21H
  1027.  
  1028.                 ;get and save pointer to dos critical byte
  1029.                 mov   ah, 34H
  1030.                 int   21H
  1031.                 mov   dos_c_ptr, bx
  1032.                 mov   dos_c_ptr+2, es
  1033.  
  1034. ;=========================================================================
  1035. ;Set up our buffer
  1036. ;
  1037. ;I can't seem to find an elegant way to tell how much memory
  1038. ;is available outside our segment.  Instead of knowing how much is available
  1039. ;and just grabbing it off when we return via int 31H, we'll be polite and
  1040. ;let DOS handle memory allocation for us.  To start with, we de-allocate
  1041. ;all un-needed memory; we're given everything on entry.  We then
  1042. ;request a new segment for our buffer.  We have to check after return
  1043. ;whether we were able to get all that we asked for, and if not, bitch
  1044. ;to user and abort.
  1045. ;=========================================================================
  1046.                 ;===========================
  1047.                 ; Deallocate our environment
  1048.                 ;===========================
  1049.                 mov   ax, environ_ptr       ;get pointer to environment block
  1050.                 mov   es, ax
  1051.                 mov   ah, @free             ;free block
  1052.                 int   21H
  1053.                 ;=================================================
  1054.                 ;Figure out where we actually end,
  1055.                 ;and shrink our block to its miminum size.  
  1056.                 ;Adding 15 guarantees we don't lop off anything
  1057.                 ;when we convert to paragraphs via the divide by 16
  1058.                 ;=================================================
  1059.                 mov   ax, cs               ;point to our segment
  1060.                 mov   es, ax
  1061.                 mov   bx, offset((end_nonres+15)/16)  ;minimum size, paragraphs
  1062.                 mov   ah, @setblock        ;set block size
  1063.                 int   21H
  1064.                 ;=======================
  1065.                 ;Now allocate the buffer
  1066.                 ;=======================
  1067.                 mov   bx, buf_size         ;buffer size in bytes
  1068.                 mov   cl, 4                ;divide by 16 for paragraphs
  1069.                 shr   bx, cl
  1070.                 inc   bx                   ;takes care of anything lopped off
  1071.                 mov   ah, @alloc           ;allocate block
  1072.                 int   21H
  1073.                 jc    ic_mem_error         ;fatal error if not enough memory
  1074.                 mov   buffer_seg, ax       ;save pointer to new segment
  1075. ;======================================================================
  1076. ;Terminate and Stay Resident
  1077. ;The DOS 2 TSR call asks for the number of PARAGRAPHs of memory we want.
  1078. ;Paragraphs are 16 byte chunks, hence we divide the number of bytes by 16.
  1079. ;We reserve memory here for our code and various small local variables.  
  1080. ;Our main buffer was reserved above by a DOS memory allocation call.            
  1081. ;======================================================================
  1082. ;Usage note: Large amounts of memory are given in paragraphs because
  1083. ;this is the smallest unit in which the total possible memory of the 8088
  1084. ;can be specified in a word: FFFF paragraphs = 1 megabyte
  1085. ;======================================================================
  1086.                 mov   ax, 3100H                       ;TSR w/ ERRORLEVEL = 0
  1087.                 mov   dx, offset((main+15)/16)  ;total paragraphs
  1088.                 int   21H
  1089. ;==============================
  1090. ; program halts after TSR call.
  1091. ;==============================
  1092.  
  1093. ;==============================
  1094. ; Fatal Error Messages
  1095. ; Print message, then abort.
  1096. ;==============================
  1097. ic_res_error    mov   cx, ic_res_end-ic_res_msg
  1098.                 mov   dx, offset(ic_res_msg)
  1099.                 jmps  fatal_error
  1100.  
  1101.  
  1102. ic_open_error   mov   cx, ic_open_end-ic_open_msg
  1103.                 mov   dx, offset(ic_open_msg)
  1104.                 jmps  fatal_error
  1105.  
  1106. ic_mem_error    mov   cx, ic_mem_end-ic_mem_msg
  1107.                 mov   dx, offset(ic_mem_msg)
  1108.                 jmps  fatal_error
  1109.  
  1110. fatal_error     mov   ah, @write          ;write error message
  1111.                 mov   bx, stdout          ;on standard output
  1112.                 int   21H
  1113.                 mov   ax, 4CFFH           ;and exit w/ ERRORLEVEL = 255
  1114.                 int   21H
  1115.                 endp
  1116. ;=======================================
  1117. ; program halts upon exit w/ errorlevel
  1118. ;=======================================
  1119.  
  1120. ;===============================================
  1121. ; GET_FILENAME
  1122. ; Builds the proper d:\path\filename, from either
  1123. ; the default or the user's specifications.
  1124. ;===============================================
  1125. ; Upon entry, ES:DI point to the first non-blank
  1126. ; character on the command line, and CX has the
  1127. ; number of characters left on the command line.
  1128. ;===============================================
  1129. ; Upon exit, FILENAME has an ASCIIZ string specifying
  1130. ; the full filespec.  ES:DI have been moved on the command
  1131. ; line past the filename, and CX has the (updated) number
  1132. ; of command line characters left.
  1133. ;===============================================
  1134. get_filename    proc  near
  1135.                 cld                          ;8088 <== autoincrement mode
  1136.                 push  ax
  1137.                 push  bx
  1138.                 push  cx
  1139.                 push  dx
  1140.                 push  di
  1141.                 push  si
  1142.                 mov   si, di
  1143.                 mov   di, offset(filename)
  1144. ;=====================================================
  1145. ; DI now points to where we will build the filespec.
  1146. ; SI points to the command line.
  1147. ; Assumes ES and DS both point to our local segment
  1148. ;=====================================================
  1149.                 cmpb  [si], '/'              ;are we looking at an option?
  1150.                 jne   gf_0                   ;no, skip
  1151. ;===================================================
  1152. ;if no filename was specified, use our default name
  1153. ;on the current default drive.
  1154. ;===================================================
  1155. use_default     mov   ah, 19H                ;get default drive
  1156.                 int   21H
  1157.                 add   al, 'A'                ;convert code to drive letter
  1158.                 stosb                        ;move it to filespec
  1159.                 mov   al, ':'                ;followed by a colon
  1160.                 stosb
  1161.                 mov   si, offset(default_file)
  1162.                 push  cx
  1163.                 mov   cx, default_end-default_file
  1164.                 rep
  1165.                 movsb
  1166.                 pop   cx
  1167.                 jmps  gf_exit
  1168.  
  1169. ;==========================================================
  1170. ; Build a complete filespec with info from the command line
  1171. ;==========================================================
  1172. gf_0            movb  file_request, true     ;user specified a filename
  1173.                 movb  param_found, true
  1174.                 call  approve_file           ;make sure its an allowed name
  1175.                 jnc   gf_0a                  ;ok? continue
  1176.                 mov   ah, @write             ;illegal name? bitch and abort
  1177.                 mov   bx, stdout
  1178.                 mov   cx, illegal_end-illegal_msg
  1179.                 mov   dx, offset(illegal_msg)
  1180.                 int   21H
  1181.                 int   20H
  1182.  
  1183. gf_0a           cmpb  1[si], ':'             ;was a drive specified?
  1184.                 jne   gf_1                   ;no, skip
  1185.                 mov   al, [si]               ;yes, get it
  1186.                 movsw                        ;then copy into filespec
  1187.                 sub   cx, 2                  ;used up two chars
  1188.                 mov   dl, al                 ;will need drive later
  1189.                 and   dl, 0DFH               ;force upper case
  1190.                 sub   dl, 'A'-1              ;convert to drive code
  1191.                 jmps  gf_2                   ;done
  1192.  
  1193. gf_1            mov   ah, 19H                ;otherwise get default drive
  1194.                 int   21H
  1195.                 add   al, 'A'                ;convert code to drive letter
  1196.                 stosb                        ;move it to filespec
  1197.                 mov   al, ':'                ;followed by a colon
  1198.                 stosb
  1199.                 sub   dl, dl                 ;use code 0 (default) in next call
  1200. ;====================================================
  1201. ; Filespec now has D:, and DL has drive code (A: = 1)
  1202. ;====================================================
  1203. gf_2            cmpb  [si], '\'              ;was a path specified?
  1204.                 je    gf_3                   ;skip if so
  1205. ;=============================================
  1206. ; if no path specified, use current directory
  1207. ;=============================================
  1208.                 mov   al, '\'                ;start with \
  1209.                 stosb                        ;  ditto
  1210.                 mov   ah, 47H                ;ask for current directory
  1211.                 push  si                     ;need SI temporarily
  1212.                 mov   si, di                 ;point to building filespec
  1213.                 int   21H
  1214.                 pop   si                     ;restore SI
  1215.                 push  cx                     ;need CX temporarily
  1216.                 mov   al, 00H                ;now search for end of path
  1217.                 mov   cx, 64                 ;maximum path length
  1218.                 repne                        ;will stop after terminator byte
  1219.                 scasb                        ;  ditto
  1220.                 dec   di                     ;now points to terminator byte
  1221.                 cmpb  -1[di], '\'            ;do we need the final \?
  1222.                 je    gf_2a                  ;no, skip
  1223.                 mov   al, '\'                ;end with \
  1224.                 stosb
  1225. gf_2a           pop   cx                     ;restore CX
  1226.  
  1227. ;============================================
  1228. ; Now copy the filename, up to the delimiter
  1229. ;============================================
  1230. gf_3           jcxz  gf_exit
  1231.                lodsb                         ;get a byte
  1232.                cmp   al, '.'                 ;in valid range?
  1233.                jl    gf_exit                 ;too low?
  1234.                cmp   al, 'z'
  1235.                jg    gf_exit                 ;too high?
  1236.                stosb                         ;otherwise, add to filespec
  1237.                jmps  gf_3                    ;and loop for another
  1238.  
  1239. gf_exit        mov   al, 00H                 ;terminate filespec
  1240.                stosb
  1241.                pop   si                      ;and restore registers
  1242.                pop   di
  1243.                pop   dx
  1244.                pop   cx
  1245.                pop   bx
  1246.                pop   ax
  1247.                ret
  1248.                endp
  1249.  
  1250. ;=========================================================
  1251. ; APPROVE_FILE
  1252. ; Checks to see if the user specified an illegal filename.
  1253. ; If we allow devices LPT1: LPT2: or PRN, we could lock up
  1254. ; the system.  The carry flag is set if the name is illegal.
  1255. ;==========================================================
  1256. approve_file  proc near
  1257.               push es
  1258.               push ax
  1259.               push cx
  1260.               push di
  1261.               push si
  1262.               push bp
  1263.               mov  bp, sp
  1264.  
  1265.               mov  ax, ds         ;establish addressability
  1266.               mov  es, ax         ;of test strings
  1267.  
  1268.               mov  di, offset(lpt1_string)
  1269.               mov  cx, 5
  1270.               repe
  1271.               cmpsb
  1272.               jcxz disapprove
  1273.               mov  si, 2[bp]                ;restore si after last loop
  1274.               mov  di, offset(lpt2_string)
  1275.               mov  cx, 5
  1276.               repe
  1277.               cmpsb
  1278.               jcxz disapprove
  1279.               mov  si, 2[bp]                ;restore si after last loop
  1280.               mov  di, offset(prn_string)
  1281.               mov  cx, 3
  1282.               repe
  1283.               cmpsb
  1284.               jcxz disapprove
  1285.               clc                         ;no matches? file ok
  1286.               jmps ap_exit
  1287. disapprove    stc                         ;matched on, so bad name
  1288. ap_exit       pop  bp
  1289.               pop  si
  1290.               pop  di
  1291.               pop  cx
  1292.               pop  ax
  1293.               pop  es
  1294.               ret
  1295.               endp
  1296. ;==========================================================
  1297. ; UPDATE_RESIDENT
  1298. ; Apply the specified options to the already resident code.
  1299. ;
  1300. ; ES contains the segment of the resident copy of us.
  1301. ; Modify the resident copy's flags, etc, according
  1302. ; to our parameters.
  1303. ;==========================================================
  1304. update_res     proc  near
  1305.                cmpb  installed, true
  1306.                je    ur_cont
  1307. ;====================================================
  1308. ; We can't modify resident code if its not installed
  1309. ;====================================================
  1310. not_inst_err   mov   dx, offset(not_inst_msg)
  1311.                mov   cx, not_inst_end-not_inst_msg
  1312.                mov   bx, stdout
  1313.                mov   ah, @write
  1314.                int   21H
  1315.                int   20H
  1316.  
  1317. ;=====================================
  1318. ; Request to change emulated printers?
  1319. ;=====================================
  1320. ur_cont        cmpb set_printers, true
  1321.                jne  ur_2
  1322.                mov  al, enabled_printers
  1323.                mov  es:enabled_printers, al
  1324.  
  1325. ;========================================
  1326. ; Request to change emulated async ports?
  1327. ;========================================
  1328. ur_2           cmpb set_aux, true
  1329.                jne  ur_3
  1330.                mov  al, enabled_aux
  1331.                mov  es:enabled_aux, al
  1332.  
  1333. ;===========================================
  1334. ; Request to change character trapping mode?
  1335. ;===========================================
  1336. ur_3           cmpb set_trap_mode, true
  1337.                jne  ur_4
  1338.                mov  al, trap_char
  1339.                mov  es:trap_char, al
  1340.  
  1341. ;=========================
  1342. ; enable/disable request?
  1343. ;=========================
  1344. ur_4           cmpb set_enable_state, true
  1345.                jne  ur_5
  1346.                mov  al, enable_flag
  1347.                mov  es:enable_flag, al
  1348.                cmp  al, false            ;did we just disable resident code?
  1349.                jne  ur_5                 ;skip if not
  1350.                call flush_resident       ;if so, flush resident buffer
  1351.  
  1352. ;==================================
  1353. ; Request to flush resident buffer?
  1354. ;==================================
  1355. ur_5           cmpb flush_request, true
  1356.                jne  ur_6
  1357.                call flush_resident
  1358.  
  1359. ;==========================
  1360. ; Request to use new file?
  1361. ;==========================
  1362. ur_6           cmpb file_request, true
  1363.                jne  ur_exit
  1364.                mov  ah, @create       ;confirm the file's there
  1365.                xor  cx, cx
  1366.                mov  dx, offset(filename)
  1367.                int  21H
  1368.                jc   ur_open_err
  1369.  
  1370.                ;close file for later use
  1371.                mov  bx, ax            ;get handle
  1372.                mov  ah, @close
  1373.                int  21H
  1374.  
  1375. ur_1a          call flush_resident    ;flush out buffer to old file
  1376.  
  1377. ;===========================================
  1378. ; copy the new filename to the resident code
  1379. ;===========================================
  1380.                mov  si, offset(filename)
  1381.                mov  di, si
  1382.                lodsb                  ;get a character
  1383. ur_1           stosb                  ;transfer to resident copy
  1384.                or   al, al            ;was that the null terminator?
  1385.                jz   ur_exit           ;if so, exit
  1386.                lodsb                  ;otherwise, get another char
  1387.                jmps ur_1              ;and continue
  1388.  
  1389. ;====================================
  1390. ; Print post-processing status, exit
  1391. ;====================================
  1392. ur_exit        call print_status
  1393.                ret
  1394.  
  1395. ;=========================================
  1396. ; error handler if unable to open new file
  1397. ;=========================================
  1398.  
  1399. ur_open_err    call print_status
  1400.                mov  ah, @write        ;unable to open file, so bitch
  1401.                mov  bx, stdout
  1402.                mov  cx, ic_open_end-ic_open_msg
  1403.                mov  dx, offset(ic_open_msg)
  1404.                int  21H
  1405.                ret
  1406.                endp
  1407.  
  1408. ;==============================================
  1409. ; PRINT_STATUS
  1410. ; print the current status of the resident copy
  1411. ; Assumes that ES points to the resident copy.
  1412. ;==============================================
  1413. print_status   proc near
  1414.                mov  ah, @write      ;write title block
  1415.                mov  bx, stdout
  1416.                mov  cx, cs_status_end-cs_status_hdr
  1417.                mov  dx, offset(cs_status_hdr)
  1418.                int  21H
  1419.  
  1420.                mov  ah, @write            ;are we enabled or disabled?
  1421.                mov  bx, stdout
  1422.                cmpb es:enable_flag, true
  1423.                jne  cs_1a
  1424.                mov  cx, cs_enab_end-cs_enab_msg
  1425.                mov  dx, offset(cs_enab_msg)
  1426.                jmps cs_1b
  1427. cs_1a          mov  cx, cs_disab_end-cs_disab_msg
  1428.                mov  dx, offset(cs_disab_msg)
  1429. cs_1b          int  21H
  1430.  
  1431.                mov  ah, @write             ;which printers are we emulating?
  1432.                mov  bx, stdout
  1433.                cmpb es:enabled_printers, 1
  1434.                jne  cs_2a
  1435.                mov  cx, cs_prn1_end-cs_prn1_msg
  1436.                mov  dx, offset(cs_prn1_msg)
  1437.                jmps cs_2c
  1438. cs_2a          cmpb es:enabled_printers, 2
  1439.                jne  cs_2b
  1440.                mov  cx, cs_prn2_end-cs_prn2_msg
  1441.                mov  dx, offset(cs_prn2_msg)
  1442.                jmps cs_2c
  1443. cs_2b          cmpb es:enabled_printers, 3
  1444.                jne  cs_2d
  1445.                mov  cx, cs_prn3_end-cs_prn3_msg
  1446.                mov  dx, offset(cs_prn3_msg)
  1447.                jmps cs_2c
  1448. cs_2d          cmpb es:enabled_printers, 8
  1449.                jne  cs_2e
  1450.                mov  cx, cs_prsc_end-cs_prsc_msg
  1451.                mov  dx, offset(cs_prsc_msg)
  1452.                jmps cs_2c
  1453. cs_2e          cmpb es:enabled_printers, 0
  1454.                jne  cs_2f
  1455.                mov  cx, cs_prn0_end-cs_prn0_msg
  1456.                mov  dx, offset(cs_prn0_msg)
  1457.                jmps cs_2c
  1458. cs_2c          int  21H
  1459. cs_2f
  1460.  
  1461.                mov  ah, @write             ;which async ports are we emulating?
  1462.                mov  bx, stdout
  1463.                cmpb es:enabled_aux, 1
  1464.                jne  cs_4a
  1465.                mov  cx, cs_aux1_end-cs_aux1_msg
  1466.                mov  dx, offset(cs_aux1_msg)
  1467.                jmps cs_4c
  1468. cs_4a          cmpb es:enabled_aux, 2
  1469.                jne  cs_4b
  1470.                mov  cx, cs_aux2_end-cs_aux2_msg
  1471.                mov  dx, offset(cs_aux2_msg)
  1472.                jmps cs_4c
  1473. cs_4b          cmpb es:enabled_aux, 3
  1474.                jne  cs_4d
  1475.                mov  cx, cs_aux3_end-cs_aux3_msg
  1476.                mov  dx, offset(cs_aux3_msg)
  1477.                jmps cs_4c
  1478. cs_4d          cmpb es:enabled_aux, 0
  1479.                jne  cs_4e
  1480.                mov  cx, cs_aux0_end-cs_aux0_msg
  1481.                mov  dx, offset(cs_aux0_msg)
  1482.                jmps cs_4c
  1483. cs_4c          int  21H
  1484. cs_4e
  1485.                mov  ah, @write             ;are we trapping any characters?
  1486.                mov  bx, stdout
  1487.                cmpb es:trap_char, 00H
  1488.                jne  cs_3a
  1489.                mov  cx, cs_notrap_end-cs_notrap_msg
  1490.                mov  dx, offset(cs_notrap_msg)
  1491.                jmps cs_3c
  1492. cs_3a          cmpb es:trap_char, lf
  1493.                jne  cs_3b
  1494.                mov  cx, cs_traplf_end-cs_traplf_msg
  1495.                mov  dx, offset(cs_traplf_msg)
  1496.                jmps cs_3c
  1497. cs_3b          cmpb es:trap_char, cr
  1498.                jne  cs_3d
  1499.                mov  cx, cs_trapcr_end-cs_trapcr_msg
  1500.                mov  dx, offset(cs_trapcr_msg)
  1501. cs_3c          int  21H
  1502.  
  1503. cs_3d
  1504.                mov  ah, @write           ;buffer size message
  1505.                mov  bx, stdout
  1506.                mov  cx, cs_buf1_end-cs_buf1_msg
  1507.                mov  dx, offset(cs_buf1_msg)
  1508.                int  21H
  1509.  
  1510.                call write_bufsize        ;print out buffer size in bytes
  1511.  
  1512.                mov  ah, @write           ;2nd part buffer size message
  1513.                mov  bx, stdout
  1514.                mov  cx, cs_buf2_end-cs_buf2_msg
  1515.                mov  dx, offset(cs_buf2_msg)
  1516.                int  21H
  1517.  
  1518.                mov  ah, @write           ;filename message
  1519.                mov  bx, stdout
  1520.                mov  cx, cs_file_end-cs_file_msg
  1521.                mov  dx, offset(cs_file_msg)
  1522.                int  21H
  1523.  
  1524.                ;calculate length of filename string
  1525.                push ds
  1526.                mov  ax, es
  1527.                mov  ds, ax
  1528.                mov  al, 00H               ;scan for terminator byte
  1529.                mov  cx, 72                ;maximum filename length
  1530.                mov  di, offset(filename)
  1531.                repne
  1532.                scasb
  1533.                mov  ax, cx
  1534.                mov  cx, 72
  1535.                sub  cx, ax
  1536.                ;and write filename
  1537.                mov  ah, @write
  1538.                mov  bx, stdout
  1539.                mov  dx, offset(filename)
  1540.                int  21H
  1541.                pop  ds
  1542.                mov  ah, @write
  1543.                mov  bx, stdout
  1544.                mov  cx, 2
  1545.                mov  dx, offset(crlf)
  1546.                int  21H
  1547.  
  1548.                ;buffer flushed?
  1549.                cmpb buf_flushed, true
  1550.                jne  cs_exit
  1551.                mov  ah, @write
  1552.                mov  bx, stdout
  1553.                mov  cx, flush_end-flush_msg
  1554.                mov  dx, offset(flush_msg)
  1555.                int  21H
  1556. cs_exit
  1557.                ret
  1558.                endp
  1559.  
  1560.  
  1561. ;===========================================
  1562. ; WRITE_BUFSIZE
  1563. ; Converts the size of our buffer to ASCII,
  1564. ; and writes it to the standard output.  
  1565. ;===========================================
  1566. write_bufsize  proc    near
  1567.                push    ax              ;save machine state
  1568.                push    bx
  1569.                push    cx
  1570.                push    dx
  1571.                push    di
  1572.  
  1573.                mov    ax, es:buf_size  ;number to convert
  1574.  
  1575.                                        ;the following code fragment was written
  1576.                                        ;by Bob Smith and published in PC Age
  1577.                                        ;Volume 3.1 (Jan. '84) p. 116
  1578.  
  1579.                mov     bx, 10          ;set up divisor
  1580.                xor     cx, cx          ;clear counter
  1581. nxt_in         xor     dx, dx          ;clear for division
  1582.                div     bx              ;dl <== AX mod 10
  1583.                or      dl, '0'         ;convert to ascii digit
  1584.                push    dx              ;save digit
  1585.                inc     cx              ;bump counter
  1586.                and     ax, ax          ;are we done?
  1587.                jnz     nxt_in          ;nope, keep going
  1588.                                        ;stack now has digits of number
  1589.                                        ;end of Bob Smith's code
  1590.  
  1591.                mov     ah, 06H         ;write char function
  1592. nxt_out        pop     dx
  1593.                int     21H
  1594.                loop    nxt_out
  1595.  
  1596.                pop     di              ;restore state
  1597.                pop     dx
  1598.                pop     cx
  1599.                pop     bx
  1600.                pop     ax
  1601.                ret                     ;and exit
  1602.                endp                    ;(write_linenum)
  1603.  
  1604. ;==============================================
  1605. ; Flush_Resident
  1606. ; Doesn't actually flush the buffer, just sets
  1607. ; the resident flag indicating a flush request.
  1608. ; Note: ES points to resident data section.
  1609. ;==============================================
  1610. flush_resident proc   near
  1611.                movb   es:need_dump, true
  1612.                movb   cs:buf_flushed, true
  1613.                ret
  1614.                endp
  1615.  
  1616. ;======================
  1617. ; Flags (non-resident)
  1618. ;=====================
  1619. param_found     db    false               ;was anything found to process?
  1620. file_request    db    false               ;did user specify a file?
  1621. install_request db    false               ;is this an install request?
  1622. set_printers    db    false               ;change printers to emulate?
  1623. set_aux         db    false               ;change aux ports to emulate?
  1624. status_request  db    false               ;should we print a status report?
  1625. set_enable_state db   false               ;request to enable or disable?
  1626. set_trap_mode   db    false               ;request to change trap mode?
  1627. flush_request   db    false               ;request to flush buffer?
  1628. buf_flushed     db    false               ;did we flush the buffer?
  1629. installed       db    false               ;are we already installed?
  1630.  
  1631. default_file    db    '\VIRTUAL.PRN'
  1632. default_end
  1633.  
  1634. baddos          db cr lf 'This program requires DOS 2.0 or later!$' cr lf
  1635. inst_msg        db cr lf 'Resident section has been installed.' cr lf
  1636. inst_end
  1637. lpt1_string     db 'LPT1:'
  1638. lpt2_string     db 'LPT2:'
  1639. prn_string      db 'PRN'
  1640. illegal_msg     db cr lf 'Invalid filename - redirecting to a printer'
  1641.                 db       ' would cause a system crash.' cr lf
  1642.                 db       'Use VPRINT /D to send output back to a real printer.'
  1643.                 db cr lf
  1644. illegal_end
  1645. not_inst_msg    db cr lf 'Unable to locate resident routine.' cr lf
  1646. not_inst_end
  1647. flush_msg       db cr lf 'Internal buffer flushed to disk.' cr lf
  1648. flush_end
  1649. ic_res_msg      db cr lf 'VPRINT is already resident!' cr lf
  1650. ic_res_end
  1651. ic_open_msg     db cr lf 'Unable to open file.' cr lf
  1652. ic_open_end
  1653. ic_mem_msg      db cr lf 'Insufficient memory to load VPRINT.' cr lf
  1654.                 db       'Specify a smaller buffer and try again.' cr lf
  1655. ic_mem_end
  1656. short_hello     db cr lf 'VPRINT - virtual printer (version 5.00)' cr lf
  1657.                 db cr lf
  1658.                 db       'User-supported software by D. Whitman' cr lf
  1659.                 db       'For help/info, type VPRINT ?' cr lf
  1660. short_end
  1661. cs_status_hdr   db     cr lf  'Current Status: '  cr lf cr lf
  1662. cs_status_end
  1663. cs_enab_msg     db '   Redirection:         ENABLED' cr lf
  1664. cs_enab_end
  1665. cs_disab_msg    db '   Redirection:         DISABLED' cr lf
  1666. cs_disab_end
  1667. cs_prn0_msg     db '   Emulated printer:    None' cr lf
  1668. cs_prn0_end
  1669. cs_prsc_msg     db '   Emulated printer:    PrtSc ONLY' cr lf
  1670. cs_prsc_end
  1671. cs_prn1_msg     db '   Emulated printer:    LPT1:' cr lf
  1672. cs_prn1_end
  1673. cs_prn2_msg     db '   Emulated printer:    LPT2:' cr lf
  1674. cs_prn2_end
  1675. cs_prn3_msg     db '   Emulated printers:   LPT1: and LPT2:' cr lf
  1676. cs_prn3_end
  1677. cs_aux0_msg     db '   Emulated async port: None' cr lf
  1678. cs_aux0_end
  1679. cs_aux1_msg     db '   Emulated async port: COM1:' cr lf
  1680. cs_aux1_end
  1681. cs_aux2_msg     db '   Emulated async port: COM2:' cr lf
  1682. cs_aux2_end
  1683. cs_aux3_msg     db '   Emulated async port: COM1: and COM2:' cr lf
  1684. cs_aux3_end
  1685. cs_notrap_msg   db '   Filter mode:         VERBATIM' cr lf
  1686. cs_notrap_end
  1687. cs_traplf_msg   db '   Filter mode:         '
  1688.                 db 'drop LF, expand CR --> CR/LF' cr lf
  1689. cs_traplf_end
  1690. cs_trapcr_msg   db '   Filter mode:         '
  1691.                 db 'drop CR, expand LF --> CR/LF' cr lf
  1692. cs_trapcr_end
  1693. cs_buf1_msg     db '   Buffer size:         '
  1694. cs_buf1_end
  1695. cs_buf2_msg     db ' bytes' cr lf
  1696. cs_buf2_end
  1697.  
  1698. cs_file_msg     db '   Print file:          '
  1699. cs_file_end
  1700. crlf            db    cr lf
  1701. hello
  1702.   db cr lf
  1703.   db 'VPRINT - Virtual Printer (version 5.00)' cr lf
  1704.   db cr lf
  1705.   db 'Purpose: Redirects printer output to a disk file.' cr lf
  1706.   db cr lf
  1707.   db 'Syntax:  VPRINT [[d:][path]filename[.ext]] [options]' cr lf
  1708.   db cr lf
  1709.   db 'Options: /i               install (required to load resident code)' cr lf
  1710.   db '         /bnn             buffer size, K (nn = 1-64, default is 2)' cr lf
  1711.   db '         /p1,p2,p0,pp,p3  emulate LPT1, LPT2, none, PrtSc, or all (default)' cr lf
  1712.   db '         /a1,a2,a3,a0     emulate COM1, COM2, both, or none (default)' cr lf
  1713.   db '         /l,c,n           filter mode: drop LF, drop CR, or none (default)' cr lf
  1714.   db '         /d,e             redirection: disabled, enabled (default)' cr lf
  1715.   db '         /f               flush - empty any buffered output to disk' cr lf
  1716.   db '         /s               report status' cr lf
  1717.   db cr lf
  1718.   db 'Once installed, you can change file or options by running VPRINT again.'
  1719.   db cr lf cr lf
  1720.   db 'VPRINT is user-supported software.  If you find this program of value, '
  1721.   db 'please' cr lf
  1722.   db 'support its development with a payment of $20 to:' cr lf
  1723.   db cr lf
  1724.   db '            Whitman Software' cr lf
  1725.   db '            P.O. Box 1157' cr lf
  1726.   db '            North Wales, PA  19454'
  1727. h_end
  1728. end_nonres
  1729.