	PAGE	,132
	TITLE	SAVSCR

COMMENT	\
	Usage: "SAVSCR  filename".  SAVSCR  makes itself resident and  handles
               PrtSc interrupts (interrupt 5).   When the Shift-PrtSc keys are
               depressed,  whatever  is on the text screen will be saved in  a
               disk  file.   You  might do this in the middle of  any  program
               where  you want to save a copy of what you see on  the  screen.
               You can do this repeatedly, and each screen will be appended to
               the file and separated by a form feed.  Later you can edit this
               file and use it to document your programs.

	       If  there  is a problem writing to the file,  you will  hear  a
               "beep".

	Written  by  Ted  Shapin,  9/26/85 (Thanks to the  unknown  author  of
        SAVSC.COM).

	Edit History:

	Edit ID	 Who   Date	Description
	-------- --- --------	-----------------------------------------------
	[VAC001] VAC 12/31/85	(VAC=Vince Cuomo) - Added code to:

				     1) Properly  handle  EGA/ECD hardware  in
                                        "43 line" mode;
				     2) Provide extra pathname support in  the
                                        filespec;
				     3) Prevent  screen saves when in graphics
                                        video  modes (modes 4 through  6,  and
                                        greater than 7);
				     4) Prevent  system  hangs  when  a   user
                                        presses Shift-PrtSc while we're inside
                                        this code;
				     5) Support  screens  pages  1  through  7
                                        (CGA/PGA/EGA adapters only);
				     6) Initially  open  the  output  file  in
                                        "append" mode (instead of creating the
                                        output file from scratch), so that the
                                        user  doesn't accidentally lose useful
                                        screen images;
				     7) Make  sure that we don't  do  anything
                                        while  the INT 10H code in the BIOS is
                                        executing (because SAVSCR needs to use
                                        INT  10H  also,  and  the  BIOS  isn't
                                        reentrant);  instead,  set  a flag  so
                                        that our INT 10H "shell" will  perform
                                        an  INT 5 if SAVSCR was invoked  while
                                        INT 10H was executing;
				     8) Put some of the references to absolute
                                        memory  addresses in the ROM BIOS data
                                        area  back in the code to  reduce  the
                                        time it takes SAVSCR to execute and to
                                        reduce  the chances of system  "hangs"
                                        (absolute   memory  addresses   really
                                        aren't  much of a problem since  we're
                                        already  assuming that the  monochrome
                                        and    CGA/PGA/EGA   adapter    buffer
                                        addresses  are at B000 or  B800,  plus
                                        there doesn't appear to be a clean way
                                        to  get the video page address  offset
                                        except  to  look directly at the  BIOS
                                        data area in word 0000:044E);
				     9) Set  the  IBM-PC BIOS's "performing  a
                                        PrtSc  operation  now"  flag   (memory
                                        location    0050:0000h)   to   1    as
                                        documented  in  the BIOS code  and  in
                                        Peter Norton's book.

				Also  cleaned  up some of the SAVSCR  messages
                                and  .DOC  file,  and did  some  general  code
                                "cleanup"  (bet  ya' won't recognize the  code
                                anymore Ted...).

	\

;	$PAGE -----------------------------------------------------------------

;[VAC001]
;[VAC001] The  following equates are the absolute memory addresses that SAVSCR
;[VAC001] uses.   If  references to additional absolute memory  locations  are
;[VAC001] needed,  update  these equates so that the memory locations are  all
;[VAC001] defined in a single place in the SAVSCR code.
;[VAC001]

B$CVPAO	EQU	44EH		;[VAC001] BIOS Current Video Page Address
				;[VAC001] Offset
B$PSFLG	EQU	50H		;[VAC001] BIOS "Performing PrtSc" Flag Segment
				;[VAC001] Address
B$GABAO	EQU	0B800H		;[VAC001] BIOS Graphics Adapter Buffer Address
				;[VAC001] Offset
B$MABAO	EQU	0B000H		;[VAC001] BIOS Monochrome Adapter Buffer
				;[VAC001] Address Offset

;	$PAGE -----------------------------------------------------------------

BELL	EQU	 7		; ASCII code (decimal) for a bell
CR	EQU	13		; ASCII code (decimal) for a carriage-return
LF	EQU	10		; ASCII code (decimal) for a line-feed
SPACE	EQU	32		; ASCII code (decimal) for a space
TAB	EQU	 9		; ASCII code (decimal) for a tab

MAXCHRS	EQU	43d*132d	;[VAC001] The maximum number of	display	screen
				;[VAC001] characters that we'll need to write
				;[VAC001] out (25x80 isn't good enough because
				;[VAC001] EGA/ECD hardware can be set to "43
				;[VAC001] line" mode, and some of the newer
				;[VAC001] video cards finally support 132
				;[VAC001] columns)

;	$PAGE -----------------------------------------------------------------

S00000	SEGMENT	BYTE PUBLIC 'CODE'
	ASSUME	CS:S00000

	DB	100H DUP (0)	; Space	for the	INT 5 stack
I5STK	EQU	00FFH		;[VAC001] Pointer to the top-of-INT 5 stack

SAVSCR	PROC	FAR

BEGIN:	ORG	100H		; All PC/MS-DOS .COM programs start at 100H
	JMP	START		; Start	address when run from COMMAND.COM

;	$PAGE -----------------------------------------------------------------

;
; The following are storage allocations for various SAVSCR control variables.
;
SIGNAT	DW	2234H		; Signature of this routine
OLD5V1	DW	?		; Storage for the old INT 5 (PrtSc) interrupt
OLD5V2	DW	?		; vector
ACTIVEF	DB	0		; Active flag (go to DOS INT 5 handler if 0)
FILEH	DW	?		; Storage for the output file handle
CRLF	DB	CR,LF,'$'	; DOS INT 21H function code 9 format CR/LF
FORMFD	DB	0CH		; ASCII form-feed character
EXITSP	DW	?		;[VAC001] Storage for the SP value for IEXIT
EGA	DB	0		;[VAC001] EGA hardware flag
I5LOCK	DB	0		;[VAC001] "Ignore INT 5" flag
I10LOCK	DB	0		;[VAC001] "INT 10H executing" flag
IN_I10	DB	0		;[VAC001] A "second level" INT 10H flag
DOINT5	DB	0		;[VAC001] "Do INT 5 after INT 10H" flag
OLD10V1	DW	?		;[VAC001] Storage for INT 10h vector
OLD10V2	DW	?		;[VAC001]    "     "   "   "	"
CURSMOD	DW	?		;[VAC001] Storage for the cursor mode at entry
CURSLOC	DW	?		;[VAC001] Storage for the cursor location
CURSCHR	DW	?		;[VAC001] Storage for the char at cursor loc.
SCRROWS	DW	?		;[VAC001] Storage for the number of screen rows
SCRCOLS	DW	?		;[VAC001] Storage for the number of screen cols
VIDSEGM	DW	?		;[VAC001] Storage for the video buffer segment
VIDEOM	DB	?		;[VAC001] Storage for the current video mode
ACTIVEP	DB	?		;[VAC001] Storage for the active video page
IN10_SS	DW	?		;[VAC001] Storage for the SS register
IN10_SP	DW	?		;[VAC001] Storage for the SP register
IN10_AX	DW	?		;[VAC001] Storage for the AX register
INT5_SS	DW	?		;[VAC001] Storage for the SS register
INT5_SP	DW	?		;[VAC001] Storage for the SP register
INT5_AX	DW	?		;[VAC001] Storage for the AX register
	DW	100 DUP (0)	;[VAC001] INT 10H handler code stack
INTSTK	DW	?		;[VAC001] Mark the start of the INT 10H stack

FILEN	DB	75D DUP	(0)	;[VAC001] Filename (paths OK)

;	$PAGE -----------------------------------------------------------------

;[VAC001]+ (Start of edit VAC001 code block)

	;
	; This  routine  will be called in place of INT 10H (the  normal  BIOS
	; interrupt code for video service).   If an interrupt is being issued
	; to set the screen to "43 line" mode (EGA/ECD hardware only), then we
	; need to "trap" the call so that we can reset the INT 5 vector to our
	; code  again  (for some reason,  the firmware in the EGA  resets  the
	; INT 5  vector,  which means that SAVSCR is permanently deactivated).
	; Also,  we  need  to  block our INT 5  handler when INT 10H  code  is
	; executing in order to help prevent system "hangs".
	;
INTTENH:CLI			; Disable interrupts for now...
	PUSHF			; Save the CPU flags
	TEST	BYTE PTR CS:IN_I10,1
				; Did INT 10H call itself (who knows...), or
				; did INT 5 call INT 10H?
	JZ	INT10OK		; If not, then continue
	POPF			; If so, then restore the CPU flags
	JMP	DWORD PTR CS:OLD10V1
				; And go do a normal INT 10H call

INT10OK:POPF			; Restore the CPU flags
	MOV	BYTE PTR CS:IN_I10,1
				; Set the "second level" lockout flag...
	MOV	BYTE PTR CS:I10LOCK,1
				; Set the "INT 10H executing" flag
	MOV	CS:IN10_SS,SS	; Save the SS register
	MOV	CS:IN10_SP,SP	; Save the SP register
	MOV	CS:IN10_AX,AX	; Save the AX register
	PUSH	CS		; Get our code segment address
	POP	SS		; Make the INT 10H stack be in the same	segment
	MOV	SP,CS:INTSTK	; Make SP point	to the beginning of the	stack
	PUSHF			; Store	the current CPU	flags on the stack
	PUSH	CS		; Store	our code segment on the	stack
	PUSH	DS		; Store the original DS value on the stack
	PUSH	CS		; Get our CS value
	POP	DS		; And set DS to be the same as CS
	MOV	AX,OFFSET INTTENR
				; Get the return address for INT 10H
	POP	DS		; Restore the original DS value
	PUSH	AX		; Save the return address in our INT 10H
				; handler on the stack (now INT	10H will IRET
				; back into our	code so	that we	can reset the
				; INT 5	vector to us, if needed)
	MOV	AX,CS:IN10_AX	; Restore the original contents	of AX

	JMP	DWORD PTR CS:OLD10V1
				; And go to the	BIOS's INT 10H code

	;
	; Get here after the real INT 10H code in the ROM BIOS is finished
	;
INTTENR:NOP			; Make sure that INT 10H really IRETs to the
	NOP			; right place...
	PUSH	DX		; Save DX for now
	PUSH	DS		; Save the current DS register contents
	PUSH	CS		; Get the current CS register contents
	POP	DS		; Set DS to be the same	as CS for function 25H
	MOV	DX,IN10_AX	; Get the INT 10H function code
	MOV	IN10_AX,AX	; Save the AX value that INT 10H returned
	TEST	BYTE PTR DS:EGA,1
				; Is there an EGA card on this machine?
	JZ	CHKINT5		; If not, go see if we got an INT 5 request
	CMP	DH,12H		; If so, then is this the 12H function code?
	JNE	CHKINT5		; If not, go see if we got an INT 5 request
	STI			; Enable interrupts again...
	MOV	AH,25H		; And make sure that the interrupt handler
	MOV	AL,5		; for INT 5
	MOV	DX,OFFSET INTVEC; is pointed at the code in SAVSCR
	INT	21H		; and is still active
	CLI			; Disable interrupts...

CHKINT5:TEST	BYTE PTR DS:DOINT5,1
				; Did the user request a "SAVSCR" while INT 10H
				; was executing?
	JZ	I10EXIT		; If not, then go exit from the INT 10H code
	MOV	BYTE PTR DS:DOINT5,0
				; Otherwise, clear the "Do an INT 5" flag
	MOV	BYTE PTR DS:I10LOCK,0
				; Reset the INT 10H lockout flag
	STI			; Enable interrupts again...
	INT	5		; And do an INT 5 to save the screen image
	MOV	BYTE PTR DS:I10LOCK,1
				; Set the INT 10H lockout flag again for a bit

I10EXIT:POP	DS		; Restore the original DS register contents
	POP	DX		; Restore the original DX register contents
	MOV	AX,CS:IN10_SS	; Get the original SS contents
	MOV	SS,AX		; Restore the user's SS register
	MOV	AX,CS:IN10_SP	; Get the original SP contents
	MOV	SP,AX		; Restore the user's SP register
	MOV	AX,CS:IN10_AX	; Restore the AX register
	MOV	BYTE PTR CS:I10LOCK,0
				; Clear the INT 10H lockout flag
	MOV	BYTE PTR CS:IN_I10,0
				; Clear the "second level" lockout flag too...
	IRET			; And return to	the original caller's code

;[VAC001]- (End	of edit	VAC001 code block)

;	$PAGE -----------------------------------------------------------------

INTVEC:	; The INT 5 interrupt handler code starts here...
	CLI			;[VAC001] Disable interrupts
	TEST	BYTE PTR CS:I5LOCK,1
				;[VAC001] Are we executing INT 5 right now?
	JZ	INT5OK		;[VAC001] If not, then continue
	IRET			;[VAC001] Otherwise, return right now...

INT5OK:	MOV	BYTE PTR CS:I5LOCK,1
				;[VAC001] Lock out any other INT 5's for now...
	TEST	BYTE PTR CS:I10LOCK,1
				;[VAC001] Is the INT 10H code executing now?
	JZ	I10CLEAR	;[VAC001] If not, then go do the INT 5 code
	MOV	BYTE PTR CS:DOINT5,1
				;[VAC001] Otherwise, tell our INT 10H "shell"
				;[VAC001] to activate the INT 5 code when thru
	MOV	BYTE PTR CS:I5LOCK,0
				;[VAC001] Reset the INT 5 lock out flag byte
	IRET			;[VAC001] And exit until called again...

I10CLEAR:			;[VAC001]
	MOV	CS:INT5_SS,SS	;[VAC001] Save caller's SS value
	MOV	CS:INT5_SP,SP	;[VAC001] Save caller's SP value
	PUSH	CS		;[VAC001]
	POP	SS		;[VAC001] Initialize our stack segment address
	MOV	SP,I5STK	;[VAC001] Initialize our stack pointer
	PUSH	DS		; Save the caller's DS contents
	PUSH	CS:INT5_AX	;[VAC001] Save the caller's AX contents
	TEST	BYTE PTR CS:ACTIVEF,1
				; Are we active?
	JNZ	DOIT		; Yes, we are
	POP	AX		;[VAC001] So restore AX
	POP	DS		; Restore DS
	MOV	SS,CS:INT5_SS	;[VAC001] Restore the original SS contents
	MOV	SP,CS:INT5_SP	;[VAC001] Restore the original SP contents
	MOV	BYTE PTR CS:I5LOCK,0
				;[VAC001] Reset the INT 5 lock out flag byte
	JMP	DWORD PTR CS:OLD5V1
				; And go to the old INT 5 handler

DOIT:	PUSH	BX
	PUSH	CX
	PUSH	DX
	PUSH	SI
	PUSH	DI
	PUSH	ES
	PUSH	BP		;[VAC001] (INT 10H can destroy the BP register)
	MOV	CS:EXITSP,SP	;[VAC001] Remember the current stack pointer
	STI			; Allow	interrupts
	CALL	SCRINFO		;[VAC001] Get needed display screen information
	MOV	SI,B$PSFLG	;[VAC001] Get the "performing PrtSc" flag's
				;[VAC001] segment address
	MOV	ES,SI		;[VAC001] Set ES
	XOR	SI,SI		;[VAC001] Set SI to zero
	MOV	BYTE PTR ES:[SI],1
				;[VAC001] Set the "performing PrtSc" flag to 1
				;[VAC001] (as documented in the BIOS code and
				;[VAC001] in Peter Norton's book)
	MOV	ES,SI		;[VAC001] Point ES at the ROM BIOS data segment
	MOV	AX,B$GABAO	;[VAC001] Assume we're using a CGA/PGA/EGA card
	CMP	BYTE PTR CS:VIDEOM,4
				;[VAC001] Are we in modes 0-3 (text modes)?
	JL	TXTMODE		;[VAC001] If so, then continue
	CMP	BYTE PTR CS:VIDEOM,7
				;[VAC001] If not, are we in monochrome mode?
	JNE	IEXITNC		;[VAC001] If not, then go exit now (graphics
				;[VAC001] mode screens look like junk when
				;[VAC001] saved	because	the video fields are
				;[VAC001] different)
	MOV	AX,B$MABAO	;[VAC001] Set the buffer address for mono cards

TXTMODE:MOV	CS:VIDSEGM,AX	;[VAC001] Save the video RAM segment address
	MOV	AH,1		;[VAC001] "Set cursor type"
	MOV	CX,2010H	;[VAC001] Make the cursor invisible
	INT	10H		;[VAC001]
	MOV	AH,8		;[VAC001] "Read attr/char at curr. curs. loc."
	MOV	BH,CS:ACTIVEP	;[VAC001] Get the current active video page no.
	INT	10H		;[VAC001] Get the attibute/character
	MOV	CS:CURSCHR,AX	;[VAC001] Save them...
	MOV	SI,B$CVPAO	;[VAC001] Get the offset of the current video
				;[VAC001] page address offset
	MOV	SI,ES:[SI]	;[VAC001] Get the current video page address
				;[VAC001] offset (this provides support for
				;[VAC001] screen pages 1 through 7 on
				;[VAC001] CGA/PGA/EGA cards) for MLOOP below...
	PUSH	CS		;[VAC001] Get our CS value
	POP	DS		;[VAC001] And set DS to our code segment value
	MOV	DI,OFFSET IEND	; Our buffer is	at the end of the INT 5 service
				; routine in our code segment
	MOV	CX,CS:SCRCOLS	;[VAC001] Get the number of columns to save
	MOV	AX,CS:SCRROWS	;[VAC001] Get the number of rows to save
	MOV	DS,CS:VIDSEGM	;[VAC001] Set DS to the current video RAM seg.
	MUL	CX		;[VAC001] Put number of chars to save in AX
	XCHG	AX,CX		;[VAC001] Put the number of chars in CX	for the
				;[VAC001] LOOP instruction
	CLD			;[VAC001] We want SI to be incremented

MLOOP:	LODSW			; Load a word from the video RAM segment
	MOV	BYTE PTR CS:[DI],AL
				;[VAC001] Put the byte in our buffer
	INC	DI		;[VAC001] Increment the buffer pointer
	LOOP	MLOOP		; Save display screen bytes until CX=0

	PUSH	CS		; Get our CS contents
	POP	DS		; And put it in DS
	CALL	OPENEND		; Open the file	and position to	the end
	MOV	SI,OFFSET IEND	; Point	to our buffer
	MOV	CX,CS:SCRCOLS	;[VAC001] Put number of screen columns in CX
	MOV	DI,CS:SCRROWS	;[VAC001] Put number of screen rows in DI
	CALL	WRITESC		;[VAC001] Write	out the	display	screen lines
	CALL	CLOSEF		; and close the	output file

IEXIT:	MOV	AX,CS:CURSCHR	;[VAC001] Get the char/attrib. to write
	MOV	BL,AH		;[VAC001] Put the attribute where needed
	MOV	AH,9		;[VAC001] "Write attr/char at cursor location"
	MOV	BH,CS:ACTIVEP	;[VAC001] Get the active video page
	MOV	CX,1		;[VAC001] Only write one character
	INT	10H		;[VAC001] Do it...
	MOV	AH,1		;[VAC001] "Set cursor type"
	MOV	CX,CS:CURSMOD	;[VAC001] Get the original cursor mode
	INT	10H		;[VAC001] Restore the cursor mode to its value
				;[VAC001] at entry

IEXITNC:MOV	SI,B$PSFLG	;[VAC001] Get the "performing PrtSc" flag's
				;[VAC001] segment address
	MOV	ES,SI		;[VAC001] Set ES
	XOR	SI,SI		;[VAC001] Set SI to zero
	MOV	BYTE PTR ES:[SI],0
				;[VAC001] Set "performing PrtSc" flag back to 0
	POP	BP		;[VAC001]
	POP	ES
	POP	DI
	POP	SI
	POP	DX
	POP	CX
	POP	BX
	POP	AX
	POP	DS
	MOV	SS,CS:INT5_SS	;[VAC001]
	MOV	SP,CS:INT5_SP	;[VAC001]
	MOV	BYTE PTR CS:I5LOCK,0
				;[VAC001] Tell the world that we're inactive
	IRET

;	$PAGE -----------------------------------------------------------------

;[VAC001]+ (Beginning of edit VAC001 code block)

SCRINFO	PROC NEAR
	;
	; Determine the following screen information:
	;
	;	EGA     (byte)	If 1, then we're running on an active EGA card
	;	VIDEOM  (byte)	The current video mode
	;	ACTIVEP (byte)	The current active video page
	;	SCRROWS (word)	The number of display screem rows
	;	SCRCOLS (word)	The number of display screen columns
	;	CURSMOD (word)	The cursor mode
	;	CURSLOC (word)	The cursor location
	;
	MOV	AX,1130H	; EGA function code, "information" subfunction
				; code
	PUSH	BP		; Save the current BP contents
	PUSH	ES		; Save the current ES contents
	XOR	BH,BH		; The pointer value to be returned in ES:BP (we
				; don't care about it...)
	MOV	DX,-1		; Put a	ridiculous value in DX as the number of
				; screen rows (on PC's which don't have	the EGA
				; installed, this value	won't be changed when
				; INT 10h returns to us; otherwise, DL will
				; contain the last row number on the display
				; screen)
	INT	10H		; Do it...
	POP	ES		; Restore ES
	POP	BP		; Restore BP
	MOV	BYTE PTR CS:EGA,0
				; Assume that we don't have an active EGA card
	XOR	DH,DH		; Clear	DX's high byte
	CMP	DL,23D		; Are there at least 24	lines on the display
				; screen?
	JL	USE_25R		; If not, then assume 25 rows
	CMP	DL,68D		; Are we set to	a believable number?
	JG	USE_25R		; If not, then assume that function 11h	isn't
				; implemented in this machine's BIOS (i.e.,
				; the EGA card is not installed	or is not
				; active), and default to a 25 row display
				; screen
	MOV	BYTE PTR CS:EGA,1
				; Otherwise, set "active EGA card" flag byte
	JMP	GOT_NR		; And continue

USE_25R:MOV	DL,24D		; Assume row 24	is the last screen row...

GOT_NR:	INC	DL		; Convert from offset zero to offset 1
	MOV	CS:SCRROWS,DX	; Remember the number of screen rows
	MOV	AH,15D		; "Current video state"
	INT	10H		; Get the current video	state (places the
				; current display page number in BH, the number
				; of display screen columns in AH, and the
				; current video mode in AL)
	MOV	BYTE PTR CS:VIDEOM,AL
				; Remember the current video mode
	MOV	BYTE PTR CS:ACTIVEP,BH
				; Remember the current active display page no.
	MOV	DL,AH		; Put the last column number in	DL
	XOR	DH,DH		; Clear	DH
	MOV	CS:SCRCOLS,DX	; Remember the number of display screen columns
	MOV	AH,3		; "Read cursor position" (BH is set above...)
	INT	10H		; Get the current cursor mode and position
	MOV	CS:CURSMOD,CX	; Save the current cursor mode
	MOV	CS:CURSLOC,DX	; Save the current cursor location
	RET			; And return to	our caller

SCRINFO	ENDP

;[VAC001]- (End	of edit	VAC001 code block)

;	$PAGE -----------------------------------------------------------------

OPENEND	PROC	NEAR
	MOV	DX,OFFSET FILEN
	MOV	AL,1		; Write	access
	MOV	AH,3DH
	INT	21H
	JNC	TOEOF
	CMP	AX,2
	JE	OPEN2
	CALL	BEEP		; An open problem
	MOV	SP,CS:EXITSP	;[VAC001] Fix up the stack pointer
	JMP	IEXIT		; Go exit from SAVSCR

OPEN2:	CALL	CREATEF
	JNC	TOEOF		;[VAC001] If all is well, then position to EOF
	CALL	BEEP		; Some kind of OPEN problem
	MOV	SP,CS:EXITSP	;[VAC001] Fix up the stack pointer
	JMP	IEXIT		;[VAC001] Go exit from SAVSCR

TOEOF:	MOV	CS:FILEH,AX	; Save file handle
	MOV	BX,AX
	MOV	AL,2		; Move to end of file
	XOR	CX,CX		;[VAC001] (faster than MOV CX,0)
	XOR	DX,DX
	MOV	AH,42H
	INT	21H
	JC	BADEOF		;[VAC001]
	RET

BADEOF:	CALL	BEEP		;[VAC001] Some kind of problem
	MOV	SP,CS:EXITSP	;[VAC001] Fix up the stack pointer
	JMP	IEXIT		;[VAC001] Go exit from SAVSCR

OPENEND	ENDP

;	$PAGE -----------------------------------------------------------------

CREATEF	PROC	NEAR
	XOR	CX,CX		; Attribute bits zero
	MOV	AH,3CH		; Create a file	or if it exists, set the
	INT	21H		; length to zero
	RET

CREATEF	ENDP

;	$PAGE -----------------------------------------------------------------

WRITESC	PROC	NEAR		;[VAC001] Enter	with DI	containing the number
				;[VAC001] of display screen rows to be saved
				;[VAC001] and CX containing the	number of
				;[VAC001] display screen columns to be saved,
				;[VAC001] SI pointing to the start of the file
				;[VAC001] buffer's area.
	PUSH	DS		; Save the DS value at entry
	PUSH	CS		;[VAC001] Get our CS value
	POP	DS		;[VAC001] And set DS to be the same as CS

WRITELP:PUSH	CX		;[VAC001] Save the number of screen columns
	PUSH	DI		;[VAC001] Save the number of screen rows
	MOV	BX,CS:FILEH	;[VAC001] Put the output file handle in BX

	;[VAC001]
	;[VAC001] Now strip off	trailing spaces	from the line image that we're
	;[VAC001] going	to write out to	the output file.
	;[VAC001]
WL2:	MOV	DI,CX		;[VAC001] Get the current char count
	ADD	DI,SI		;[VAC001] Add it to the	line image's video
				;[VAC001] buffer address
	DEC	DI		;[VAC001] Correct for offset by	1 (needs to be
				;[VAC001] offset by 0)
	MOV	DL,BYTE PTR CS:[DI]
				;[VAC001] Get that byte
	CMP	DL,SPACE	;[VAC001] Is it	a SPACE?
	JNE	WL3		;[VAC001] If not, then we've stripped off any
				;[VAC001] trailing spaces
	LOOP	WL2		;[VAC001] Otherwise, it's a SPACE, so backup
				;[VAC001] another character
	POP	DI		;[VAC001] We can only get here when the	entire
				;[VAC001] line is spaces, so fix up the	stack
	JMP	WRITEOK		;[VAC001] And just go write out	a CR/LF

WL3:	MOV	AH,40H
	MOV	DX,SI
	INT	21H		; Do the write
	POP	DI		;[VAC001] Restore DI
	JC	BADWRT		;[VAC001] If there's a problem, then go "beep"

WRITEOK:MOV	AH,40H
	MOV	CX,2
	MOV	DX,OFFSET CRLF
	INT	21H		; Write	a CRLF at the end of the line
	JNC	NXTADDR		; If all is well, then continue

BADWRT:	POP	CX		;[VAC001] Restore CX
	POP	DS		;[VAC001] Restore DS
	JMP	BEEP		;[VAC001] And go "beep" at the user

NXTADDR:POP	CX		;[VAC001] Restore the number of screen columns
	ADD	SI,CX		; Advance to the next line
	DEC	DI
	JNE	WRITELP
	MOV	DX,OFFSET FORMFD
	MOV	CX,1
	MOV	AH,40H
	INT	21H		; Write	a FORM FEED
	POP	DS
	JC	BEEP		;[VAC001] If something is wrong, then go "beep"
	RET

;	$PAGE -----------------------------------------------------------------

CLOSEF	PROC	NEAR
	MOV	BX,CS:FILEH
	MOV	AH,3EH
	INT	21H		; Close	the output file
	JNC	CLOSEOK		;[VAC001]

BEEP:	MOV	DL,7		; Some kind of file error occured so
	MOV	AH,2		; just send a "beep" and return
	INT	21H

CLOSEOK:RET

CLOSEF	ENDP

WRITESC	ENDP			;[VAC001]

;	$PAGE -----------------------------------------------------------------

IEND:	;
	; End of the resident code. File buffer	area overlays the following.
	;

;	$PAGE -----------------------------------------------------------------

NOFILEM	DB	'You must specify an output filename such as "C:\PATH\SCREEN.OUT".',CR,LF,'$'

BADDOS	DB	'Sorry, SAVSCR must run under PC/MS-DOS V2.0 or greater.',CR,LF,'$'

PROBM	DB	'I am unable to create the output file "$'

FEXISTS	DB	CR,LF
	DB	'Warning, that output file already exists.  Screen output will be',CR,LF
	DB	"appended to the output file's current contents.",CR,LF,'$'

ALREADY	DB	CR,LF
	DB	'SAVSCR is already resident in memory and is active.',CR,LF,LF
	DB	'Would you like to disable SAVSCR and return to the original',CR,LF
	DB	'DOS PrtSc interrupt handler (Y/N)? $'

INACTIV	DB	CR,LF
	DB	'SAVSCR is already resident in memory but is inactive.',CR,LF,LF
	DB	'Would you like to enable SAVSCR (Y/N)? $'

OKINACM	DB	CR,LF
	DB	'OK, SAVSCR is now inactive.  To re-activate SAVSCR, just',CR,LF
	DB	'type "SAVSCR" and answer the questions.',CR,LF,'$'

OKACTM	DB	CR,LF
	DB	'OK, SAVSCR is now active again.  Press Shift-PrtSc to save',CR,LF
	DB	'the display screen to the output file.',CR,LF,'$'

INITMSG	DB	CR,LF
	DB	'The SAVSCR V1.1 (save display screen) program is now resident',CR,LF
	DB	'in memory.  Press Shift-PrtSc to save the display screen in',CR,LF
	DB	'the output file "$'

QUOTEND	DB	'".',CR,LF,'$'

WOULDM	DB	'Would you like instructions (Y/N)? $'

OPNMSG	DB	'Attempting to create screen image output file $'

DOCM	DB	CR,LF
	DB	'        Usage:  "SAVSCR [[d:]\path\]filename.ext"',CR,LF,LF
	DB	'        SAVSCR  makes itself resident and handles PrtSc interrupts (BIOS',CR,LF
	DB	'        interrupt 5).  When the Shift and PrtSc keys are pressed  AT THE',CR,LF
	DB	'        SAME TIME, whatever is  on the display screen will be written to',CR,LF
	DB	'        a disk  file.  You might do this in the middle of a program when',CR,LF
	DB	'        you  want to save a copy of what you see on the screen.  You may',CR,LF
	DB	'        do this  repeatedly, and each screen will be appended at the end',CR,LF
	DB	'        of the file and will be separated by a form feed.  Later you can',CR,LF
	DB	'        edit or print this file and use it to document your programs  or',CR,LF
	DB	'        save  useful information.  If there is a problem  writing to the',CR,LF
	DB	'        output file, you will hear a "beep".',CR,LF,LF
	DB	'                     SAVSCR V1.0  9/25/85 by Ted Shapin',CR,LF
	DB	'                     SAVSCR V1.1 12/31/85 by Vince Cuomo',CR,LF
	DB	'$'

;	$PAGE -----------------------------------------------------------------

START:	MOV	AX,CS		;[VAC001] Initial installation starts here
	MOV	DS,AX		;[VAC001]
	MOV	ES,AX		;[VAC001]
	MOV	SS,AX		;[VAC001]
	MOV	SP,I5STK	;[VAC001]
	MOV	AH,30H		; Check	DOS version
	INT	21H
	CMP	AL,2
	JGE	DOSOK
	MOV	DX,OFFSET BADDOS
	CALL	WRTMSG
	MOV	AL,-1		;[VAC001]
	JMP	LEAVE

DOSOK:	MOV	AH,35H		; Get interrupt	vector
	MOV	AL,5		; for interrupt	5
	INT	21H
	CMP	WORD PTR ES:SIGNAT,2234H
	JNE	NOTLOADED
	TEST	BYTE PTR ES:ACTIVEF,1
	JZ	NOTACTIV
	MOV	DX,OFFSET ALREADY
				; Already loaded and active
	CALL	SHORT WRTMSG
	CALL	ASK		; Do you want to make us inactive?
	JNE	ASKDOC
	MOV	BYTE PTR ES:ACTIVEF,0
				; Make us inactive
	MOV	DX,OFFSET OKINACM
	CALL	SHORT WRTMSG
	XOR	AL,AL		;[VAC001]
	JMP	LEAVE

NOTACTIV:
	MOV	DX,OFFSET INACTIV
				; Already loaded and inactive
	CALL	SHORT WRTMSG	; Do you want to make us active?
	CALL	ASK
	JNE	ASKDOC
	MOV	BYTE PTR ES:ACTIVEF,1
				; Make us active
	MOV	DX,OFFSET OKACTM
	CALL	SHORT WRTMSG
	XOR	AL,AL		;[VAC001]
	JMP	LEAVE

NOTLOADED:
	MOV	BX,OFFSET INTSTK;[VAC001] Get the INT 10H stack location
	MOV	INTSTK,BX	;[VAC001] Remember it for out INT 10H handler
	PUSH	CS
	POP	ES
	XOR	BH,BH
	MOV	BL,DS:[0080H]	; Look for a file name in the FCB1 location
	OR	BL,BL		; (BL has the length of	the string)
	JNZ	SHORT GOTNAME
	MOV	DX,OFFSET NOFILEM
	CALL	SHORT WRTMSG

ASKDOC:	MOV	DX,OFFSET WOULDM
	CALL	SHORT WRTMSG
	CALL	SHORT ASK
	JE	JINSTR		;[VAC001]
	XOR	AL,AL		;[VAC001]
	JMP	LEAVE

JINSTR:	JMP	INSTR		;[VAC001]

GOTNAME:MOV	CX,BX
	ADD	CX,2		; Save length for later
	ADD	BX,81H		; Put null byte	at end to make ASCIIZ string
	MOV	BYTE PTR [BX],0
	MOV	BX,80H

	; Find actual start of filename
FINDNM:	DEC	CX
	INC	BX
	CMP	BYTE PTR [BX],SPACE
				; by skipping leading spaces
	JE	FINDNM
	CMP	BYTE PTR [BX],TAB
				; and tabs
	JE	FINDNM

	PUSH	BX		; Save the FCB1 filename pointer
	MOV	DI,OFFSET FILEN	; Where	to put the filename

	;[VAC001]
	;[VAC001] Now see if the user passed us a pathname or device spec; if
	;[VAC001] not, then we'll default one for him.
	;[VAC001]
	INC	BX		;[VAC001] Move to the second filespec byte
	CMP	BYTE PTR [BX],":"
				;[VAC001] Is it a colon (a disk spec)?
	JE	GOTPATH		;[VAC001] If so, then user gave us a legit path

	; User didn't give us a device to use, so we'll use the current disk
	MOV	AH,19H		;[VAC001] "Get current disk"
	INT	21H		;[VAC001]
	MOV	DL,AL		;[VAC001] Put a copy of it where 47H needs it
	ADD	AL,"A"		;[VAC001]
	MOV	BYTE PTR DS:[DI],AL
				;[VAC001] Put current disk drive in the buffer
	INC	DI		;[VAC001] Increment the buffer pointer
	MOV	BYTE PTR DS:[DI],":"
				;[VAC001] Put a colon in the buffer
	INC	DI		;[VAC001] Increment the buffer pointer
	POP	BX		;[VAC001] Restore the original byte pointer
	PUSH	BX		;[VAC001] Put the byte pointer on the stack

	CMP	BYTE PTR DS:[BX],"\"
				;[VAC001] Is the first filespec byte a path
				;[VAC001] designator?
	JE	GOTPATH		;[VAC001] If so, then proceed
	MOV	BYTE PTR DS:[DI],"\"
				;[VAC001] Otherwise, put a leading pathname
				;[VAC001] designator in the buffer (DOS
				;[VAC001] function 47H won't do it for us)
	INC	DI		;[VAC001] Increment the buffer pointer
	MOV	AH,47H		;[VAC001] Specify DOS function 47H
	MOV	SI,DI		;[VAC001] Put DI into SI for functin 47H
	INC	DL		;[VAC001] Increment current disk (saved above)
	INT	21H		;[VAC001] Put our current directory in the file
				;[VAC001] name buffer
	MOV	DI,OFFSET FILEN	;[VAC001] Get the original buffer pointer back
	ADD	DI,2		;[VAC001] Skip past the device spec

EPLOOP:	MOV	DL,DS:[DI]	;[VAC001] Get a byte from the filename buffer
	OR	DL,DL		;[VAC001] Is this a NUL byte (the end)?
	JZ	ENDPATH		;[VAC001] If so, then proceed
	INC	DI		;[VAC001] Otherwise, increment to the next byte
	MOV	DH,DL		;[VAC001] Save this byte as "last byte seen"
	JMP	EPLOOP		;[VAC001] And go check the next byte

ENDPATH:CMP	DH,"\"		;[VAC001] Was the "last byte seen" a pathname
				;[VAC001] terminator?
	JE	GOTPATH		;[VAC001] If so, then proceed
	MOV	BYTE PTR DS:[DI],"\"
				;[VAC001] Otherwise, put a pathname designator
				;[VAC001] at the end of the filename buffer
	INC	DI		;[VAC001] And increment the buffer pointer

	;
	; Now put the filespec passed by the user into our filename buffer, and
	; convert it to uppercase.
	;
	; Enter with DI pointing to the buffer, and the pointer to the FCB
	; filespec on the top of the stack.
	;
GOTPATH:POP	BX		;[VAC001] Get the FCB filename pointer

GPLOOP:	MOV	DL,BYTE PTR DS:[BX]
				;[VAC001] Get a filespec byte
	CMP	DL,"a"		;[VAC001] Is it above the range of a lowercase
				;[VAC001] character?
	JL	NOTLC		;[VAC001] If not, then proceed
	CMP	DL,"z"		;[VAC001] Is it a lowercase character?
	JG	NOTLC		;[VAC001] If not, then proceed
	XOR	DL,32D		;[VAC001] Otherwise, convert it to uppercase

NOTLC:	MOV	BYTE PTR DS:[DI],DL
				;[VAC001] Put the character in our buffer
	INC	BX		;[VAC001] Increment the FCB filename byte ptr.
	INC	DI		;[VAC001] Increment our buffer pointer
	OR	DL,DL		;[VAC001] Set the CPU flags
	JNZ	GPLOOP		;[VAC001] Loop if the character isn't a NUL

	INC	DI		;[VAC001] Otherwise, increment past the NUL
	MOV	BYTE PTR DS:[DI],"$"
				;[VAC001] And insert a DOS string terminator
	CALL	APPENDF		;[VAC001] Open the output file in "append" mode
	JNC	CREATEOK
	MOV	DX,OFFSET PROBM
	CALL	SHORT WRTMSG
	MOV	DI,OFFSET FILEN	;[VAC001] Couldn't open the output file, so
	CALL	SHORT PRTSTR	;[VAC001] tell the user
	MOV	DX,OFFSET QUOTEND
				;[VAC001] Finish up the message
	CALL	SHORT WRTMSG	;[VAC001]
	MOV	AL,-1		;[VAC001]
	JMP	LEAVE		; And go exit back to the DOS CLI

CREATEOK:
	CALL	CLOSEF
	CALL	SCRINFO		;[VAC001] Set the CS:EGA flag word
	POP	DX		;[VAC001] Clean up the stack (we don't care
	POP	DX		;[VAC001] about the screen size)

	; Now install our interrupt handler in place of	PrtSc

	;[VAC001] And install our INT 10H handler too...
	MOV	AH,35h
	MOV	AL,5
	INT	21H		; Get old interrupt vector
	MOV	OLD5V1,BX
	MOV	OLD5V2,ES	; and save them
	MOV	AL,10h		;[VAC001]
	INT	21H		;[VAC001]
	MOV	OLD10V1,BX	;[VAC001]
	MOV	OLD10V2,ES	;[VAC001]
	MOV	AH,25H		; Set our interrupt handler entry
	MOV	AL,5		;[VAC001]
	MOV	DX,OFFSET INTVEC; DS already has CS
	INT	21H
	MOV	AL,10H		;[VAC001]
	MOV	DX,OFFSET INTTENH
				;[VAC001]
	INT	21H		;[VAC001]
	MOV	BYTE PTR CS:ACTIVEF,1
				; Mark our handler active
	MOV	DX,OFFSET INITMSG
				; and say we are loaded
	MOV	AH,9
	INT	21H
	MOV	DI,OFFSET FILEN	;[VAC001]
	CALL	PRTSTR		;[VAC001]
	MOV	DX,OFFSET QUOTEND
				;[VAC001]
	MOV	AH,9		;[VAC001]
	INT	21H		;[VAC001]

	; Now exit
	MOV	DX,OFFSET IEND	; Figure out how many resident SAVSCR code
				; bytes there are
	ADD	DX,MAXCHRS	;[VAC001] Leave room for a screen full of chars
	ADD	DX,0FH		;[VAC001] Round up to the next paragraph
	MOV	CL,4
	SHR	DX,CL		; Convert to the number of paragraphs to stay
				; resident in memory by shifting the byte count
				; in DX by 4 bits to the right (which is the
				; same as dividing it by 16 since 16=2^4)
	MOV	AH,31H
	XOR	AL,AL		;[VAC001]
	INT	21H		; Terminate and	stay resident

INSTR:	MOV	DX,OFFSET DOCM
	CALL	WRTMSG
	XOR	AL,AL		;[VAC001]
	JMP	LEAVE

;	$PAGE -----------------------------------------------------------------

ASK	PROC
	MOV	AH,0CH		; Clear	keyboard buffer
	MOV	AL,1		; keyboard input
	INT	21H
	PUSH	AX
	MOV	DX,OFFSET CRLF	; Go to	new line after answer
	CALL	WRTMSG
	POP	AX
	AND	AL,7FH-20H	; Make sure that the answer is upper-case
	CMP	AL,'Y'
	RET
ASK	ENDP

;	$PAGE -----------------------------------------------------------------

	; Procedure to display a message on the	display	screen
WRTMSG	PROC
	MOV	AX,CS
	MOV	DS,AX
	MOV	AH,9H
	INT	21H
	RET
WRTMSG	ENDP

;	$PAGE -----------------------------------------------------------------

;[VAC001]+

APPENDF	PROC	NEAR
	MOV	DX,OFFSET FILEN
	MOV	AL,1		; Write	access
	MOV	AH,3DH
	INT	21H
	JNC	APPMSG
	PUSHF
	CMP	AX,2
	JE	APPND2
	POPF
	RET

APPND2:	POPF
	CALL	CREATEF
	RET

APPMSG:	PUSH	AX
	MOV	DX,OFFSET FEXISTS
	CALL	SHORT WRTMSG
	POP	AX

APPEOF:	MOV	CS:FILEH,AX	; Save file handle
	MOV	BX,AX
	MOV	AL,2		; Move to end of file
	XOR	CX,CX		; (faster than MOV rX,0)
	XOR	DX,DX
	MOV	AH,42H
	INT	21H
	RET

APPENDF	ENDP

;	$PAGE -----------------------------------------------------------------

PRTSTR	PROC NEAR
	;
	; Routine to print an ASCIIZ string
	;
	; Enter with DS:DI pointing to the string to be printed.  All registers
	; are preserved.
	;
	PUSH	AX
	PUSH	DX
	PUSH	DI

	MOV	AH,2

L00:	MOV	DL,DS:[DI]
	OR	DL,DL
	JZ	L01
	INT	21H
	INC	DI
	JMP	L00

L01:	POP	DI
	POP	DX
	POP	AX
	RET

PRTSTR	ENDP

;[VAC001]-

;	$PAGE -----------------------------------------------------------------

	; Enter with AL containing a return code (may be used with DOS "IF" and
	; "ERRORLEVEL" batch subcommands)
LEAVE:	MOV	AH,4CH		;[VAC001] "Terminate a Process"
	INT	21H		;[VAC001]

;	$PAGE -----------------------------------------------------------------

SAVSCR	ENDP
S00000	ENDS

	END	BEGIN
