;	BROWSE.ASM -- Full Screen File Pager
;	====================================

CSEG		Segment
		Assume	CS:CSEG, DS:CSEG, ES:CSEG, SS:CSEG
		Org	0080h
Parameter	Label	Byte
		Org	0100h
Entry:		Jmp Begin

;	All Data
;	--------

		db	'ATTR='
Attribute	db	0		; Current screen attribute
		db	'SHIFT='
ShiftHoriz	db	8		; Horizontal shift screen default
DosVersionFail	db	'Requires DOS 2.0 or above$'
NoSpaceFail	db	'Not enough memory$'
FileFail	db	'File Not Found$'
ScreenFail	db	'Unsupported video mode$'
Delimiters	db	9,' ,;=/'	; Delimiters in parameter
FileHandle	dw	?		; Use for saving file handle
WSMode		db	0FFh		; AND value for non-WordStar mode
LineLength	db	?		; Length of line (from BIOS)
NumberLines	db	25,0		; Number of lines (check EGA BIOS)
ScreenSize	dw	?		; Size of screen in bytes
CheckRetrace	db	1		; Flag zero if EGA or MONO used
Addr6845	dw	?		; Could use for retrace check	
ScreenAddr	Label	DWord		; Address of screen
ScreenOff	dw	0		; Higher for non-page 0
ScreenSeg	dw	0B800h		; Set to B000h for Mono Mode 7
ScreenStart	dw	?		; Points within buffer 
EndOfFile	dw	?		; Points within buffer
FileOffset	dw	-1, -1		; Address within file of buffer data
HorizOffset	dw	0		; Horizontal offset for display	
RightMargin	dw	0		; Right margin for offset display
Dispatch	dw	Home, Up, PgUp, Dummy, Left
        dw  Dummy, Right, Dummy, Endx, Down, PgDn

;	Check DOS Version for 2.0 or above
;	----------------------------------

Begin:		Cld			; All string directions forward
		Mov	AH,30h
		Int	21h		; Get DOS Version Number
		Cmp	AL,2		; Check for 2.0 or later
		Jae	DOSVerOK
		Mov	DX,Offset DOSVersionFail
ErrorExit:	Mov	AH,9		; Write error message
		Int	21h
		Int	20h

;	Parse Command Line to get File Name and WordStar flag
;	-----------------------------------------------------

DOSVerOK:	Mov	SI,1 + Offset Parameter	; Points to parameter
NameSearch:	Lodsb				; Get byte
		Cmp	AL,13			; Check if carriage return
		Jz	NoFileFound		; If so, no file name
		Mov	DI,Offset Delimiters	; String of delimiters
		Mov	CX,5			; Number of delimiters (no /)
		Repne	Scasb			; See if a match
		Je	NameSearch		; If a delimiter, keep looking
		Mov	DX,SI			; Otherwise found file name
		Dec	DX			; Points to beginning of it
EndSearch:	Lodsb				; Get next byte
		Cmp	AL,13			; See if carriage return
		Je	GotFileEnd		; If so, we're all done
		Mov	DI,Offset Delimiters	; String of delimiters
		Mov	CX,6			; Number (including /)
		Repne	Scasb			; See if a match
		Jne	EndSearch		; If not, still in file name
		Mov	Byte Ptr [SI - 1],0	; If so, mark end of file name
		Jcxz	GotFlag			; If slash, check for W
		Jmp	EndSearch		; Or continue flag search
GotFlag:	Lodsb				; Get byte after / flag
		Or	AL,20h			; Uncapitalize
		Cmp	AL,'w'			; See if w for WordStar mode
		Jnz	GotFileEnd		; If not, just ignore it
		Mov	[WSMode],7Fh		; AND value for WordStar

;	Open the File
;	-------------

GotFileEnd:	Mov	Byte Ptr [SI - 1],0	; Mark end of file name
						; DX still points to name
		Mov	AX,3D00h		; Open file for reading
		Int	21h			;   by calling DOS
		Jnc	GotTheFile		; If no error, continue	
NoFileFound:	Mov	DX,Offset FileFail	; Otherwise print a message 
		Jmp	ErrorExit
GotTheFile:	Mov	[FileHandle],AX		; Save the file handle

;	Get Screen Mode Information from BIOS Data Area
;	-----------------------------------------------

		Push	ES			; Save register
		Sub	AX,AX
		Mov	ES,AX			; Set ES to 0 (BIOS Data)
		Mov	AL,ES:[0449h]		; Current Video Mode
		Cmp	AL,3			; Check if Color Alpha
		Jbe	DisplayOK		; Continue if so
		Cmp	AL,7			; Check if monochrome display
		Je	Monochrome		; If so, branch
		Mov	DX,Offset ScreenFail	; We can't handle graphics
		Jmp	ErrorExit		; So print an error message
Monochrome:	Mov	[ScreenSeg],0B000h	; Use Monochrome Segment
		Mov	[CheckRetrace],0	; Don't have to check retrace
DisplayOK:	Mov	AL,ES:[044Ah]		; Number of Columns
		Mov	[LineLength],AL		; Save it
		Mov	AX,ES:[044Eh]		; Offset into screen buffer
		Mov	[ScreenOff],AX		; Save it		 
		Mov	AX,ES:[0463h]		; Address of 6845 Regsiter
		Mov	[Addr6845],AX		; Save it
		Push	ES
		Sub	DL,DL			; Set Rows to zero first
		Sub	BH,BH
		Mov	AX,1130h		; EGA BIOS: Get Information
		Int	10h
		Pop	ES
		Or	DL,DL			; Check if DL is still zero
		Jz	NoEGA			; If so, skip rest of stuff
		Inc	DL
		Mov	[NumberLines],DL	; Save Number of Lines
		Test	Byte Ptr ES:[0487h],4	; Check if must check retrace
		Jnz	NoEGA
		Mov	[CheckRetrace],0	; EGA says we don't have to
NoEGA:		Mov	BH,ES:[0462h]		; Get Current Page (use later)
		Pop	ES
		Mov	AL,[LineLength]		; Length of each line
		Mul	[NumberLines]		; Total chars on screen
		Add	AX,AX			; Double for attributes
		Mov	[ScreenSize],AX		; And Save it

;	See if enough memory is left
;	----------------------------

		Add	AX,Offset ScreenHold	; Add ScreenSize to code end
		Add	AX,256			; Add a little stack room
		Cmp	AX,SP			; Check against stack pointer
		Jbe	GotEnufMemory		; Continue if OK
		Mov	DX,Offset NoSpaceFail	; Otherwise end program
		Jmp	ErrorExit		;    with error messae
		
;	Get Current Screen Attribute
;	---------------------------- 

GotEnufMemory:	Cmp	[Attribute],0		; Check if attribute pre-set
		Jnz	GotAttribute		; If so, move on
		Mov	DL,' '			; Write out a byte
		Mov	AH,2			;   using DOS
		Int	21h
		Mov	AL,8			; Now backspace
		Mov	AH,14			;   using BIOS call
		Int	10h
		Mov	AH,8			; Read character & attribute
		Int	10h			;   using BIOS call (BH = pg)
		Mov	[Attribute],AH		; And save attribute

;	Save Current Screen
;	-------------------

GotAttribute:	Mov	DX,Offset Terminate	; Set Ctrl-Break exit
		Mov	AX,2523h		;   to terminate that way
		Int	21h
		Mov	DI,Offset ScreenHold	; Destination of screen
		Mov	CX,[ScreenSize]		; Size of screen
		Push	DS			; Save Source Segment
		Lds	SI,[ScreenAddr]		; Get screen address
		Rep	Movsb			; Move in the bytes
		Pop	DS			; Restore Source Segment

;	Get Keyboard Key and Decide on Action
;	-------------------------------------

		Call	Home			; Read file in
		Mov	[ScreenStart],SI	; Set buffer address
KeyLoop:	Call	UpDateScreen		; Write file to screen
GetKey:		Mov	AH,8			; Get key
		Int	21h			;   by calling DOS
		Cmp	AL,27			; Check if ESC
		Je	Terminate		; If so, terminate 
		Cmp	AL,0			; Check if extended
		Jnz	GetKey			; If not, try again
		Mov	AH,8			; Get extended code
		Int	21h			;   by calling DOS
		Sub	AL,71			; Subtract Home key value
		Jb	GetKey			; If below that, not valid
		Cmp	AL,(81 - 71)		; Check if above PgDn
		Ja	GetKey			; If so, ignore it
		Sub	AH,AH			; Zero out top byte
		Add	AX,AX			; Double for word access
		Mov	BX,AX			; Offset in dispatch table
		Mov	SI,[ScreenStart]	; Set current buffer pointer
		Call	[Dispatch + BX]		; Do the call
		Mov	[ScreenStart],SI	; Set new buffer pointer
		Jmp	KeyLoop			; And update the screen

;	Terminate -- Restore screen and close file
;	------------------------------------------

Terminate:	Mov	SI,Offset ScreenHold	; Address of Saved Screen
		Les	DI,[ScreenAddr]		; Address of Display
		Mov	CX,[ScreenSize]		; Number of characters
		Rep	Movsb			; Move them back 
		Mov	BX,[FileHandle]		; Get File Handle
		Mov	AH,3Eh			; Close File
		Int	21h
		Int	20h			; Terminate

;	Cursor Key Routines -- Home Key
;	-------------------------------

Home:		Sub	BX,BX			; For zeroing out values
		Mov	AX,[FileOffset]		; Check if read in file
		Or	AX,[FileOffset + 2]
		Mov	[FileOffset],BX		; Zero out file address
		Mov	[FileOffset + 2],BX
		Mov	[HorizOffset],BX	; Zero out horizontal offset	
		Mov	SI,Offset Buffer	; Reset buffer pointer	
		Jz	Dummy			; Skip file read if in already
		Mov	DX,Offset Buffer	; Area to read file in
		Mov	CX,32768		; Number of bytes to read
		Call	FileRead		; Read in file
Dummy:		Ret

;	Up and PgUp Keys
;	----------------

Up:		Call	GetPrevChar		; Get previous char in buffer
		Jc	UpDone			; If none available, finish
UpLoop:		Call	GetPrevChar		; Get previous char again
		Jc	UpDone			; if none, we're done
		Cmp	AL,10			; Check if line feed
		Jnz	UpLoop			; If not, try again 
		Call	GetNextChar		; Get char after line feed
UpDone:		Ret

PgUp:		Mov	CX,Word Ptr [NumberLines]	; Number of lines
PgUpLoop:	Call	Up			; Do UP that many times
		Loop	PgUpLoop
		Ret

;	Left and Right Keys
;	-------------------

Left:		Mov	[HorizOffset],0		; Reset Horizontal Offset
		Ret

Right:		Mov	AL,[ShiftHoriz]		; Get places to shift
		Sub	AH,AH
		Add	[HorizOffset],AX	; Move that many right
		Ret

;	End, Down, and PgDn Keys
;	------------------------

Endx:       Mov BX,SI           ; Save buffer pointer
		Call	PgDn			; Go page down
		Cmp	BX,SI			; Check if we did so
        Jnz Endx        ; If so, do it again
		Ret

Down:		Call	GetNextChar		; Get next character
		Jc	NoMoreDown		; If no more, we're done
DownLoop:	Call	GetNextChar		; Get one again
		Jc	UpLoop			; If no more, find prev LF
		Cmp	AL,10			; See if line feed
		Jnz	DownLoop		; If not, continue
NoMoreDown:	Ret

PgDn:		Mov	CX,Word Ptr [NumberLines]	; Number of lines
PgDnLoop:	Call	Down			; Do DOWN that many times
		Loop	PgDnLoop
		Ret

;	Update Screen
;	-------------

UpdateScreen:	Push	ES
		Mov	SI,[ScreenStart]	; Address of data in buffer
		Les	DI,[ScreenAddr]		; Address of display
		Mov	CX,ScreenSize		; Number of bytes in screen
		Shr	CX,1			; Half for number of chars
		Mov	AL,' '			; Will blank screen
		Mov	AH,[Attribute]		; With screen attribute
		Rep	Stosw			; Blank it
		Mov	AL,[LineLength]		; Length of display line
		Sub	AH,AH
		Add	AX,[HorizOffset]	; Add Horizontal Offset
		Mov	[RightMargin],AX	; That's right display margin
		Sub	DL,DL			; Line Number
LineLoop:	Sub	BX,BX			; Column Number
		Mov	AL,[LineLength]		; Use Line Length
		Mul	DL			;   and Line Number
		Add	AX,AX			;     to recalculate
		Mov	DI,AX			;       display destination
		Add	DI,[ScreenOff]		; Add beginning address	
CharLoop:	Call	GetNextChar		; Get next character
		Jc	EndOfScreen		; If no more, we're done
		And	AL,[WSMode]		; Will be 7Fh for WordStar
		Cmp	AL,13			; Check for carriage return
		Je	CharLoop		; Do nothing if so
		Cmp	AL,10			; Check for line feed
		Je	LineFeed		; Do routine if so
		Cmp	AL,9			; Check for tab
		Je	Tab			; Do routine if so
		Mov	CX,1			; Just 1 char to display
PrintChar:	Cmp	BX,[HorizOffset]	; See if we can print it
		Jb	NoPrint		
		Cmp	BX,[RightMargin]	; See if within margin
		Jae	NoPrint
		Mov	AH,[Attribute]		; Attribute for display
		Cmp	[CheckRetrace],0	; See if must stop snow
		Jz	WriteIt			; If not, skip retrace wait
		Push	BX
		Push	DX
		Mov	BX,AX			; Save character and attribute
		Mov	DX,[Addr6845]		; Set up I/O address
		Add	DX,6 
RetraceWait1:	In	AL,DX			; Check until
		Shr	AL,1			;   vertical retrace
		Jc	RetraceWait1		;     ends
		Cli				; Clear interrupts
RetraceWait2:	In	AL,DX			; Check until
		Shr	AL,1			;   vertical retrace
		Jnc	RetraceWait2		;     begins
		Mov	AX,BX			; Get back character & attr
		Stosw				; Write to display
		Sti				; Enable interrupts again
		Pop	DX
		Pop	BX
		Jmp	Short NoPrint		; Skip around "no snow" write
WriteIt:	Stosw				; Write without retrace wait
NoPrint:	Inc	BX			; Bump up line counter
		Loop	PrintChar		; Do it CX times
		Jmp	CharLoop		; Then go back to top
Tab:		Mov	AX,BX			; Current column number
		And	AX,07h			; Take lower three bits
		Mov	CX,8
		Sub	CX,AX			; Subtract from 8
		Mov	AL,' '			; Will print CX blanks
		Jmp	PrintChar
LineFeed:	Inc	DL			; Next line
		Cmp	DL,[NumberLines]	; See if down at bottom
		Jb	LineLoop		; If not, continue
EndOfScreen:	Pop	ES			; All done -- leave
		Ret

;	Get Next Character from buffer
;	------------------------------
;		(Input is SI pointing to buffer, Returns AL, CY if no more)

GetNextChar:	Cmp	SI,[EndOfFile]		; See if at end of file
		Jae	NoMoreNext		; If so, no more chars
		Cmp	SI,Offset BufferEnd	; See if at end of buffer
		Jb	CanGetNext		; If not, just get character
		Push	CX			; Otherwise save registers
		Push	DX
		Push	DI
		Push	ES
		Push	DS			; Set ES to DS
		Pop	ES			;   (could be different)
		Mov	SI,Offset BufferMid	; Move 2nd buffer half
		Mov	DI,Offset Buffer	;   to 1st buffer half	
		Mov	CX,16384	   
		Sub	[ScreenStart],CX	; New buffer pointer
		Rep	Movsb			; Move them
		Mov	SI,DI			; SI also buffer pointer
		Add	[FileOffset],32768 	; Adjust file addr to read
		Adc	[FileOffset + 2],0 
		Mov	DX,Offset BufferMid	; Place to read file
		Mov	CX,16384		; Number of bytes
		Call	FileRead		; Read the file
		Sub	[FileOffset],16384	; Now adjust so reflects
		Sbb	[FileOffset + 2],0	;   1st half of buffer
		Pop	ES			; Get back registers
		Pop	DI
		Pop	DX
		Pop	CX
		Jmp	GetNextChar		; And try again to get char
CanGetNext:	Lodsb				; Get the character
NoMoreNext:	Cmc				; So CY set if no more
		Ret				

;	Get Previous Character from buffer
;	----------------------------------

GetPrevChar:	Cmp	SI,Offset Buffer	; See if at top of buffer
		Ja	CanGetPrev		; If not, just get character
		Mov	AX,[FileOffset]		; See if at top of file
		Or	AX,[FileOffset + 2]
		Jz	AtTopAlready		; If so, can't get anymore
		Push	CX			; Save some registers
		Push	DX
		Mov	SI,Offset Buffer	; Move 1st half of buffer
		Mov	DI,Offset BufferMid	;   to 2nd half of buffer
		Mov	CX,16384
		Add	[ScreenStart],CX	; New buffer pointer
		Rep	Movsb			; Do the move
		Sub	[FileOffset],16384	; Adjust file addr for read
		Sbb	[FileOffset + 2],0
		Mov	DX,Offset Buffer	; Area to read file into
		Mov	CX,16384		; Number of bytes
		Call	FileRead		; Read the file
		Pop	DX			; Get back registers
		Pop	CX
		Jmp	Short CanGetPrev	; Now get character
AtTopAlready:	Stc				; CY flag set for no more
		Ret
CanGetPrev:	Dec	SI			; Move pointer back
		Mov	AL,[SI]			; Get the character
		Clc				; CY flag reset for success
		Ret

;	Read CX bytes from the file into DX buffer
;	------------------------------------------ 	

FileRead:	Push	AX			; Save some registers
		Push	BX
		Push	CX
		Push	DX
		Mov	[EndOfFile],-1		; Initialize this
		Mov	DX,[FileOffset]		; Get file address to read
		Mov	CX,[FileOffset + 2]
		Mov	BX,[FileHandle]		; Get file Handle
		Sub	AL,AL			; Do LSEEK from beginning
		Mov	AH,42h			; LSEEK call
		Int	21h
		Pop	DX			; Get back destination
		Pop	CX			; Get back count
		Mov	AH,3Fh			; Read file function call
		Int	21h
		Jnc	NoReadError		; If no error, continue
		Sub	AX,AX			; Otherwise read zero bytes
NoReadError:	Cmp	AX,CX			; See if 32K has been read
		Je	GotItAll		; If so, we're home free
		Add	AX,DX			; Otherwise add to buffer addr
		Mov	[EndOfFile],AX		; And save as end of file
GotItAll:	Pop	BX
		Pop	AX
		Ret

;	File Buffer and Screen Hold Areas
;	---------------------------------

Buffer		Label	Byte			; Area for file reads
BufferMid	equ	Buffer + 16384		; Halfway through it
BufferEnd	equ	BufferMid + 16384	; At end of it		
ScreenHold	equ	BufferEnd		; Area for holding screen
CSEG		EndS				; End of segment
		End	Entry			; Denotes entry point

