Here it is...
PAGE 46,132
TITLE PRTSCFF4.ASM - PRTSCFF4.SYS DEVICE DRIVER
;====================================================================
;
; PRTSCFF4.ASM
;
; Copyright (C) 1995 J.A. Keuter. All Rights Reserved.
;
; CONTAINS: All the code to create the PRTSCFF4.SYS device driver
;
; IMPLEMENTATION DETAILS:
;
; This code contains a complete DOS character device. It has a device
; header with attributes and pointers to the strategy and interrupt
; functions.
;
; This device driver functions as follows:
;
; * On initial load by ??DOS.SYS and on any call made to the device driver
; a request packet is passed to the strategy procedure. It stores the
; packet address for the interrupt procedure that will be called next.
;
; * The interrupt function checks the request.
;
; + If it's the initialize request the device driver initializes by
; installing an interrupt handler on INT 05h (the Print Screen
; interrupt) and INT 10h (the video interrupt). Then the commandline is
; scanned for a parameter containing a 'Q'. If it's not found a banner
; is printed. The start address of the initialization code is returned
; to the operating system so memory can be free'd from there on. This
; is possible because initialize is only requested upon loading of the
; device driver.
;
; + If it's the open request the device driver will check if it is still
; in possession of the INT 05h (Print Screen interrupt). If it's not
; (another program took over) it reinstalls itself on the interrupt.
; It also resets it's receive buffer so it can receive a new output
; request.
;
; + If it's the close request the device driver does nothing. It has to
; respond to it because it responds to open.
;
; + If it's the output status request the driver will fetch the current
; receive status and return this.
;
; + If it's the output or output and verify request a number of things
; get done:
;
; The referenced output buffer is copied into a receive buffer as
; long as it fits. After the copy the buffer is checked if it starts
; with a valid command, a 01h/02h byte. If not it's illegal access and
; a write protect error is returned. If it's oke though the receive
; buffer is scanned for a 00h byte which indicates the end of the
; command is received. If it is, the 00h byte is replaced by a space.
; Depending on the command byte the received data is either copied into
; the printer codes section, so it gets printed next time, or is used
; to set the printer port number to use. Finally the receive buffer and
; status are cleared for the next command.
;
; + if it's the non-destructive input request the driver will tell
; there's no data available to read. This is added because MSDOS
; irregularly ask if any input data is available.
;
; + If it's neither one of these requests an unknown command error is
; returned. This should only occur as a result of illegal access.
;
; * Besides device requests there are two other ways to activate the device
; driver:
;
; + INT 05h (Print Screen). This is the primary entry. Once this
; interrupt is called the device driver gets in full operation.
;
; + INT 10h (video). This one is used to check if something is changed
; about the system Print Screen routine. The device driver has to adapt
; to this, by retaking INT 05h.
;
; * The INT 05h handler gets the request to print the screen. It checks to
; see whether or not the current video mode is a text mode. Depending on
; this it will act or pass the request on to the original handler.
; First the PCL 3 printer commands to setup the printer are send using
; INT 17h. Then the screen characters are read line-by-line from video
; memory using cursor positioning and send to the printer. To avoid
; having the cursor racing about on screen we switch it off first. In
; between lines spaces are send to center the screen print on paper. A
; code is send to let the printer print the screen data transparently, so
; it won't get confused about funny characters. Finally a printer reset
; command is send to eject the page and restore the printer to its
; original state. The cursor is restored too.
;
; * The INT 10h handler checks the video interrupt for the set alternate
; print screen request. If it has found it, the handler passes it through
; to the video BIOS after which it retakes INT 05h (Print Screen
; interrupt)
;
;
; MODIFICATIONS
;
; January 8, 1995 0.01 : J.A. Keuter
; Created from PASSWRD5.ASM by David Kirschbaum, Toad Hall &
; John R. Petrocelli.
;
; January 10, 1995 1.00 : J.A. Keuter
; Many trial and error runs made me make several changes and improvements.
; It's ready for release.
;
; January 12, 1995 2.00 : J.A. Keuter
; Difficulty with the interception of the INT 05h print screen interrupt
; lead to another approach. Instead of INT 05h the printer interrupt
; INT 17h is used. Added PCL 3 support.
;
; January 15, 1995 3.00 : J.A. Keuter
; Fix for INT 05h print screen interrupt found. Returning to original
; design using knowledge of version 2.00, especially PCL 3 support.
;
; February 5, 1995 4.00 : J.A. Keuter
; Changed design to avoid video BIOS print screen routine all together.
; Added open and close request processing to make easy reinstall possible.
; Added output capability to be able to set username.
;
; February 9, 1995 4.01 : J.A. Keuter
; Changed effect of username setting. Now 'Username' string gets set
; by the configuration utility. Added printer data transparency.
;
; June 6, 1995 4.02 : J.A. Keuter
; Added write() check to configuration utility.
;
; June 20, 1995 4.03 : J.A. Keuter
; Added printer port selection code.
;
;====================================================================
;Useful symbol definitions and device request structure.
;Semaphores
SEMCLEAR EQU 0
SEMSET EQU 1
;ASCII characters
ESCAPE EQU 01Bh
LF EQU 0Ah
CR EQU 0Dh
TAB EQU 08h
SPACE EQU 020h
;Printer numbers
LPT1 EQU 00h
LPT2 EQU 01h
LPT3 EQU 02h
;Device requests
REQINIT EQU 000h
REQNDINPUT EQU 005h
REQOUTPUT EQU 008h
REQOUTVER EQU 009h
REQOUTSTAT EQU 00Ah
REQOPEN EQU 00Dh
REQCLOSE EQU 00Eh
;Device replies
REPERROR EQU 08000h
REPDONE EQU 00100h
REPBUSY EQU 00200h
REPUNKWN EQU 00003h
;Device request structure
DevReq STRUC
bLen DB ?
bUnitNo DB ?
bCmd DB ?
rStatus DW ?
res DB 8 DUP (?)
bData DB ?
pfBuff DD ?
wLen DW ?
DevReq ENDS
;Command codes
CMDSETNAME EQU 01h
CMDSETPORT EQU 02h
;Miscellaneous
RXSIZE EQU 30
BIOSDSEG EQU 00040h
ROWSMINUSONE EQU 00084h
;====================================================================
;The start of the device driver segment
DEV_SEG SEGMENT
ORG 0
;====================================================================
;The following lines are the device header, which must exist for
;every device. This file has only one device, and it works with
;character I/O. It can be opened and closed.
PrtScFF_Device PROC FAR
PrtScFF_dev_header LABEL BYTE ;start of the device driver
next_dev_ptr DD -1 ;only 1 device is defined in this file
dev_attribute DW 1000100000000000B ;character device, supports open/close
strategy_ptr DW Strategy ;the proc that receives the request
interrupt_ptr DW Interrupt ;the proc that handles all services
device_name DB 'PRTSCFF$' ;device name string, used to open device
;The Strategy proc stores es:bx request header pointer here
;The Interrupt proc retrieves it
request_ptr LABEL DWORD
request_off DW 0
request_seg DW 0
;Command receive buffer
RxStatus DB 0FFh
CharOff DW 0
CharReceive DB RXSIZE DUP (' ')
;Print screen handler address store
int05h_ptr LABEL DWORD
int05h_off DW 0
int05h_seg DW 0
;Video handler address store and semaphore
int10h_ptr LABEL DWORD
int10h_off DW 0
int10h_seg DW 0
int10h_sem DB SEMCLEAR
;Cursor location and size storage
cursorloc DW 0
cursorsize DW 0
;Printer port
Port DW LPT1
;PCL 3 printer codes: - First part to setup the printer: Reset, ISOA4,
; Portrait, Pitch 12, PC-8, 6 lines/inch, and 6
; linefeeds to outrun the company logo. Username
; gets tucked in between.
; - Last part is used before every print line: roll-
; over to next line, spaces for left margin and code
; for printer data transparency.
printerreset LABEL BYTE
printerset DB ESCAPE, 'E'
PRINTERRESETLEN EQU $ - printerreset
DB ESCAPE, '&l26A', ESCAPE, '&l0O'
DB ESCAPE, '(s12H', ESCAPE, '(10U', ESCAPE, '&l6D'
DB CR, LF, LF, ' '
username DB RXSIZE DUP (' ')
DB LF, LF, LF, LF
PRINTERSETLEN EQU $ - printerset
lineheader DB LF, CR, ' ', ESCAPE, '&p'
columnascii DB '@@@X' ;column count set by interrupt handler
LINEHEADERLEN EQU $ - lineheader
PrtScFF_Device ENDP
;====================================================================
;Strategy procedure
;Just saves the request packet pointer for the Interrupt procedure.
Strategy PROC FAR
ASSUME cs:DEV_SEG
mov cs:[request_off], bx ;store request offset
mov cs:[request_seg], es ;and segment
ret
Strategy ENDP
;====================================================================
;Interrupt procedure
;Processes the request indicated in the request packet.
Interrupt PROC FAR
ASSUME cs:DEV_SEG, ds:DEV_SEG, es:NOTHING
push ax ;save only what we need
push bx ;for first test
push es
push ds
mov ax, cs
mov ds, ax ;set data segment for general use
les bx, ds:[request_ptr] ;pointer to our request packet
mov al, es:[bx+bCmd] ;check the request
cmp al, REQINIT
jne maybeopen
jmp Init ;go initialize
maybeopen:
cmp al, REQOPEN
je Open ;open device
cmp al, REQCLOSE
je Close ;close device
cmp al, REQOUTSTAT
je OutputStatus ;output status check
cmp al, REQOUTPUT
je Output ;output to device
cmp al, REQOUTVER
je OutputVerify ;output with verify
cmp al, REQNDINPUT
je NonDestInput ;non-destructive input
mov word ptr es:[bx+rStatus], REPERROR+REPDONE+REPUNKWN
;indicate error & done &
;unknown request
pop ds ;restore those we disturbed
pop es
pop bx
pop ax
ret ;RET FAR from drivers
Interrupt ENDP
;====================================================================
;Open device checks Int 05h vector and retakes it if changed. Also
;resets the receive buffer.
Open PROC FAR
ASSUME cs:DEV_SEG, ds:DEV_SEG, es:NOTHING
mov word ptr es:[bx+rStatus], REPDONE
call RetakeInt05h
mov ds:[CharOff], 0 ;reset rx buffer
mov ds:[RxStatus], 0FFh ;reset receive error
pop ds ;restore those we disturbed
pop es
pop bx
pop ax
ret ;RET FAR from driver
Open ENDP
;====================================================================
;Close has to be handled because open is, thats all.
Close PROC FAR
ASSUME cs:DEV_SEG, ds:DEV_SEG, es:NOTHING
mov word ptr es:[bx+rStatus], REPDONE
pop ds ;restore those we disturbed
pop es
pop bx
pop ax
ret ;RET FAR from driver
Close ENDP
;====================================================================
;Non-destructive input request is answered here. It is irregularly
;asked by MSDOS.
NonDestInput PROC FAR
ASSUME cs:DEV_SEG, ds:DEV_SEG, es:NOTHING
mov word ptr es:[bx+rStatus], REPDONE+REPBUSY
pop ds ;restore those we disturbed
pop es
pop bx
pop ax
ret ;RET FAR from driver
NonDestInput ENDP
;====================================================================
;Output status check answered here.
OutputStatus PROC FAR
ASSUME cs:DEV_SEG, ds:DEV_SEG, es:NOTHING
mov ax, REPDONE
mov al, byte ptr ds:[RxStatus]
cmp al, 0FFh ;0FFh means no error
je retstatus
add ax, REPERROR ;set error flag
retstatus:
mov word ptr es:[bx+rStatus], ax
pop ds ;restore registers
pop es
pop bx
pop ax
ret ;RET FAR from driver
OutputStatus ENDP
;====================================================================
;Output is collected in a buffer and checked for the right header (to be
;command byte). If it's not right a write protect violation error is
;returned. Otherwise an ASCIIZ string is expected (possibly in parts) of
;26 characters, excluding the null-character. Once the null-character is
;received the string is copied into the printer codes area.
Output PROC FAR
ASSUME cs:DEV_SEG, ds:DEV_SEG, es:NOTHING
OutputVerify:
mov word ptr es:[bx+rStatus], REPDONE
push si
push di
push cx
push dx
mov ax, word ptr es:[bx+pfBuff+2] ;get buffer segment
push ax ;save for later
mov si, word ptr es:[bx+pfBuff] ;get buffer offset
mov di, OFFSET CharReceive
add di, word ptr ds:[CharOff] ;set buffer offset
mov ax, RXSIZE
sub ax, word ptr ds:[CharOff] ;get dest buffer size
jnz buffnotfull
mov word ptr es:[bx+wLen], 0 ;set # of processed chars
mov byte ptr ds:[RxStatus], 0Ah ;set write fault
pop ax ;clear stack
jmp cmddone
buffnotfull:
mov cx, word ptr es:[bx+wLen] ;get source buffer size
cmp ax, cx
jae noclip
mov cx, ax ;clip size to avail. space
mov word ptr es:[bx+wLen], cx ;set # of processed chars
mov byte ptr ds:[RxStatus], 0Ah ;set write fault
noclip:
add ds:[CharOff], cx ;calculate new end
pop ds ;ds:si -> source buffer
push cs ;store segments
push es
push cs ;get buffer segment
pop es ;es:di -> dest buffer
cld
rep movsb ;copy source to dest
pop es ;restore segment
pop ds ;restore data segment
mov al, ds:[CharReceive]
cmp al, CMDSETNAME ;check headerbyte
je checkEOS
cmp al, CMDSETPORT ;check headerbyte
je checkportnum
mov word ptr es:[bx+wLen], 0 ;set # of processed chars
mov byte ptr ds:[RxStatus], 00h ;set write protect error
jmp cmddone
checkEOS:
push es
mov ax, ds
mov es, ax ;buffer segment
mov ax, 0 ;search for null-character
mov cx, RXSIZE ;buffer size
mov di, OFFSET CharReceive ;buffer offset
cld
repne scasb ;scan buffer for null-character
pop es
cmp ax, cx ;count down to 0
je cmddone ;if not end of string found
dec di
mov byte ptr ds:[di], SPACE ;to avoid rewrite/recheck problems
mov ax, RXSIZE-1 ;-1 for header byte
sub ax, cx ;calc string length
mov cx, ax
mov si, OFFSET CharReceive
inc si ;string start offset
push es
push ds
pop es ;username segment
mov di, OFFSET username ;uername offset
cld
rep movsb ;copy string to printer codes area
pop es
mov ds:[CharOff], 0 ;reset rx buffer
mov ds:[RxStatus], 0FFh ;reset receive error
jmp cmddone
checkportnum:
push es
mov ax, ds
mov es, ax ;buffer segment
mov ax, 0 ;search for null-character
mov cx, RXSIZE ;buffer size
mov di, OFFSET CharReceive ;buffer offset
cld
repne scasb ;scan buffer for null-character
pop es
cmp ax, cx ;count down to 0
je cmddone ;if not end of string found
dec di
mov byte ptr ds:[di], SPACE ;to avoid rewrite/recheck problems
dec di
mov al, byte ptr ds:[di]
cmp al, '1'
jge checkporthigh
mov byte ptr ds:[RxStatus], 0Ah ;set write fault
jmp cmddone
checkporthigh:
cmp al, '3'
jle portoke
mov byte ptr ds:[RxStatus], 0Ah ;set write fault
jmp cmddone
portoke:
mov ah, 0
sub al, '1'
mov word ptr ds:[Port], ax ;store printer port
mov ds:[CharOff], 0 ;reset rx buffer
mov ds:[RxStatus], 0FFh ;reset receive error
cmddone:
mov ax, REPDONE
mov al, byte ptr ds:[RxStatus]
cmp al, 0FFh ;0FFh means no error
je exitout
add ax, REPERROR ;set error flag
exitout:
mov word ptr es:[bx+rStatus], ax
pop dx
pop cx
pop di
pop si
pop ds ;restore those we disturbed
pop es
pop bx
pop ax
ret ;RET FAR from driver
Output ENDP
;====================================================================
;Retake int 05h if it changed.
RetakeInt05h PROC NEAR
ASSUME cs:DEV_SEG, ds:DEV_SEG, es:NOTHING
push ax
push bx
push dx
push es
mov ax, 03505h ;get interrupt vector for
int 21h ;interrupt 05h
mov ax, es
cmp ds:[int05h_seg], ax ;check with stored segment
jne retake
cmp ds:[int05h_off], bx ;check with stored offset
je dontretake
retake:
mov ds:[int05h_seg], es ;store segment
mov ds:[int05h_off], bx
mov dx, OFFSET Int05h ;new vector for interrupt 05h
mov ax, 02505h
int 21h ;set new vector
dontretake:
pop es ;restore registers
pop dx
pop bx
pop ax
ret
RetakeInt05h ENDP
;====================================================================
;Convert column count to ASCII string and set printer escape sequence
ColToASCII PROC NEAR
ASSUME cs:DEV_SEG, ds:DEV_SEG, es:NOTHING
push ax ;save registers
push bx
push cx
mov cx, 2 ;# figures min 1
figureloop:
push cx
mov cx, 0
mov cl, ah ;setup counter
mov ax, 0
numberloop:
inc al
aaa ;ascii adjust after addition
loop numberloop
pop cx
add al, '0' ;make real ASCII figure
mov bx, cx
mov byte ptr ds:[bx+columnascii], al ;write in ESC seq.
loop figureloop
add ah, '0' ;make real ASCII figure
mov byte ptr ds:[columnascii], ah ;write in ESC seq.
pop cx ;restore registers
pop bx
pop ax
ret
ColToASCII ENDP
;====================================================================
;Print Screen handler
Int05h PROC FAR
ASSUME cs:DEV_SEG, ds:DEV_SEG, es:NOTHING
push ds ;save registers
push ax
push bx
push cx
push dx
push es
push si
push di
mov ax, cs
mov ds, ax ;get data segment
mov ah, 00Fh ;get current video mode
int 10h
and al, 07Fh ;mask bit 7
cmp al, 0 ;video mode 0
je supped
cmp al, 1 ;video mode 1
je supped
cmp al, 2 ;video mode 2
je supped
cmp al, 3 ;video mode 3
je supped
cmp al, 7 ;video mode 7
je supped
pop di ;restore registers
pop si
pop es
pop dx
pop cx
pop bx
pop ax
pop ds
jmp dword ptr cs:[int05h_ptr] ;chain for other video modes
supped:
call ColToASCII ;convert column count into ASCII
;and fill printer escape sequence
mov cl, ah ;store column count
mov al, SEMSET
mov ds:[int10h_sem], al ;set semaphore for speed
mov ax, BIOSDSEG ;BIOS data segment
mov es, ax
mov al, es:[ROWSMINUSONE] ;get rows on screen minus one
inc al
mov ch, al ;cx = row:column
push cx
mov ah, 003h ;get cursor position
int 10h
mov word ptr ds:[cursorloc], dx ;store cursor location
mov word ptr ds:[cursorsize], cx ;store cursor size
mov ah, 001h ;set text mode cursor shape
mov cx, 02000h ;invisible cursor
int 10h
mov di, OFFSET printerset
mov cx, PRINTERSETLEN
prtsetloop:
mov dx, word ptr ds:[Port]
mov ah, 000h ;print character
mov al, byte ptr ds:[di] ;character from printer set
int 17h
inc di
loop prtsetloop ;loop until all printed
pop cx
mov dh, 0 ;start with row 0
rowloop:
push dx
push cx
mov di, OFFSET lineheader
mov cx, LINEHEADERLEN
prtlhdrloop:
mov dx, word ptr ds:[Port]
mov ah, 000h ;print character
mov al, byte ptr ds:[di] ;character from line header
int 17h
inc di
loop prtlhdrloop ;loop until all printed
pop cx
pop dx
mov dl, 0 ;start with column 0
prtlineloop:
push dx
mov ah, 002h ;set cursor position
int 10h
mov ah, 008h ;read char and attrib from cursor loc
int 10h
mov dx, word ptr ds:[Port]
mov ah, 000h ;print character
int 17h
pop dx
inc dl ;count column
cmp dl, cl
jne prtlineloop
inc dh ;count row
cmp dh, ch
jne rowloop
mov di, OFFSET printerreset
mov cx, PRINTERRESETLEN
prtresetloop:
mov dx, word ptr ds:[Port]
mov ah, 000h ;print character
mov al, byte ptr ds:[di] ;character from printer reset
int 17h
inc di
loop prtresetloop ;loop until all printed
mov ah, 002h ;set cursor location
mov dx, word ptr ds:[cursorloc] ;get stored location
int 10h
mov ah, 001h ;set text mode cursor shape
mov cx, word ptr ds:[cursorsize] ;get cursor size
int 10h
mov al, SEMCLEAR
mov ds:[int10h_sem], al ;clear semaphore
done:
pop di ;restore registers
pop si
pop es
pop dx
pop cx
pop bx
pop ax
pop ds
iret ;done
Int05h ENDP
;====================================================================
;Video interrupt handler
Int10h PROC FAR
ASSUME cs:DEV_SEG, ds:DEV_SEG, es:NOTHING
push ds
push ax
mov ax, cs
mov ds, ax ;get data segment
mov al, ds:[int10h_sem]
cmp al, SEMCLEAR
je semclear10h ;semaphore clear
chain10h:
pop ax
pop ds
jmp dword ptr cs:[int10h_ptr] ;chain to original handler
semclear10h:
pop ax
push ax ;restore ax
cmp ah, 12h
jne chain10h ;special functions
cmp bl, 20h
jne chain10h ;alternate print screen
mov al, SEMSET
mov ds:[int10h_sem], al ;set semaphore
pop ax ;restore ax
push ax
int 10h ;call original video
mov al, SEMCLEAR
mov ds:[int10h_sem], al ;clear semaphore
call RetakeInt05h ;retake int 05h is changed
pop ax
pop ds
iret ;don't chain
Int10h ENDP
;====================================================================
;Initialize the device driver. From here on memory will be released
;to DOS. es:bx points to the request packet.
Init PROC FAR
ASSUME cs:DEV_SEG, ds:DEV_SEG, es:NOTHING
push cx ;already pushed ax, bx and es
push dx ;so gotta save the rest now
push si
push di
push ds
mov word ptr es:[bx+00Eh], OFFSET Init ;release from
mov word ptr es:[bx+010h], cs ;here on
mov word ptr es:[bx+003h], REPDONE ;flag done
push es
push bx
mov ax, 03510h ;get interrupt vector for
int 21h ;interrupt 10h
mov ds:[int10h_seg], es ;and store it for later use
mov ds:[int10h_off], bx
mov dx, OFFSET Int10h ;new vector for interrupt 10h
mov ax, 02510h
int 21h ;set new vector
mov ax, 03505h ;get interrupt vector for
int 21h ;interrupt 05h
mov ds:[int05h_seg], es ;and store it for later use
mov ds:[int05h_off], bx
mov dx, OFFSET Int05h ;new vector for interrupt 05h
mov ax, 02505h
int 21h ;set new vector
pop bx
pop es
les bx, dword ptr es:[bx+wLen] ;get end of DEVICE= line
nextchar:
cmp byte ptr es:[bx], CR
je prtbanner
cmp byte ptr es:[bx], LF
je prtbanner
cmp byte ptr es:[bx], SPACE ;find first parameter
je foundparm
inc bx
jmp nextchar
foundparm:
inc bx
cmp byte ptr es:[bx], CR
je prtbanner
cmp byte ptr es:[bx], LF
je prtbanner
cmp byte ptr es:[bx], 'Q' ;found parameter
je nobanner
cmp byte ptr es:[bx], 'q' ;found parameter
je nobanner
jmp foundparm
prtbanner:
mov dx, OFFSET banner
mov ah, 9 ;DOS display the banner
int 21h
nobanner:
pop ds ;clean up the stack
pop di
pop si
pop dx
pop cx
pop ds ;the ones we first pushed
pop es
pop bx
pop ax
ret ;RET FAR from drivers
Init ENDP
;====================================================================
;This is the installation message
banner DB 'ÕÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ͸', CR, LF
DB '³ Print Screen FormFeeder installed ³', CR, LF
DB '³ Ver 4.03 (C) 1995 by J.A. Keuter ³', CR, LF
DB 'ÔÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ;', CR, LF
DB '$'
DEV_SEG ENDS
END PrtScFF_Device
;====================================================================
Written by my own two hands and an ASCII editor.
Problems? write me
Last updated May 21, 1997
Page best viewed with