;       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
