home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Simtel MSDOS - Coast to Coast
/
simteldosarchivecoasttocoast.iso
/
screen
/
savscr.asm
< prev
next >
Wrap
Assembly Source File
|
1994-03-05
|
35KB
|
1,022 lines
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