;********************************************************************
;*                                                                  *
;* PROT - A 386 protected mode DOS extender                         *
;* Copyright (C) 1989, by Al Williams                               *
;* All rights reserved.                                             *
;*                                                                  *
;* Permission is granted for non-commercial use of this software    *
;* subject to certain conditions (see PROT.ASM).                    *
;*                                                                  *
;* This file is: FBROWSE.PM, a file browser.                        *
;* To assemble: PMASM FBROWSE                                       *
;* or: MASM /DPROGRAM=FBROWSE PROT.ASM,FBROWSE.OBJ,FBROWSE.LST;     *
;*                                                                  *
;********************************************************************

; If this equate is 0, use direct screen output else use DOS
DOSOUT       EQU      0

; This program displays a file. The file is stored as strings
; starting at the top of memory (the heap) and growing downward.
; At the start of memory is an array of pointers to lines in the
; heap.


             PROT_DATA
BASE         DD       0            ; top screen line
PTRPTR       DD       0            ; pointer to end of line pointers
HEAPPTR      DD       ?            ; pointer to empty heap entry
FHANDLE      DW       ?            ; file handle
LBUF         DB       81 DUP(?)    ; line buffer
FBUFP        DD       0            ; pointer to character in fbuf
FBUFL        DW       0            ; # of char's in fbuf
FBUF         DB       512 DUP(?)   ; file buffer
; help message
UMSG         DB       'Usage: FBROWSE filename',13,10,'$'
; error messages
NFMSG        DB       'Can''t open file',13,10,'$'
FEMSG        DB       'File error!',13,10,'$'
OMMSG        DB       'Out of memory',13,10,'$'
; copyright message
CPYMSG       DB       'FBROWSE - Protected Mode File Browser (C) 1989'
             DB       ' by Al Williams'
IF           DOSOUT
             DB       '$'          ; end of string for DOS function 9
ELSE
             DB       0            ; end of string for PROT
ENDIF

; bios call frame
BINTFRAME    VM86BLK  <>

             PROT_DATA_END

             PROT_CODE
; This routine catches ^BREAK's and forces program termination
ONBREAK      PROC     FAR
             MOV      AX,SEL_UDATA
             MOV      DS,AX
             JMP32S   USTOP0
ONBREAK      ENDP

; Main program
USER         PROC     NEAR
; Enable Break handling
             MOV      AX,SEL_UCODE
             MOV      BRK_SEG,AX
             MOV      EAX,OFFSET ONBREAK
             MOV      BRK_OFF,EAX
; clear the screen
             CALL32F  SEL_CODE32,CLS
; Set up CALL86 parameter blocks (pintframe & bintframe)
             MOV      AX,SEG SSINT1
             MOV      PINTFRAME.VMSS,EAX
             MOV      BINTFRAME.VMSS,EAX
             MOV      AX,SEG USERDATA
             MOV      PINTFRAME.VMDS,EAX
             MOV      EAX, OFFSET SSINT1
             MOV      PINTFRAME.VMESP,EAX
             MOV      BINTFRAME.VMESP,EAX
; Pintframe will be used for INT 21
             MOV      EAX,21H
             MOV      PINTFRAME.VMINT,EAX
; get command line
             MOV      CX,SEL_PSP
             MOV      FS,CX
             ASSUME   FS:NOTHING
             XOR      EBX,EBX
             MOV      BL,FS:[80H]
             OR       BL,BL
             JNZ      SHORT OPENFILE
; No command line - Print usage message
             MOV      AH,9
             MOV      EDX,OFFSET UMSG
             MOV      EBX,OFFSET PINTFRAME
             VM86CALL
             MOV      AL,1
             JMP32S   USTOP        ; back to DOS
; Open file for reading
OPENFILE:
             XOR      AX,AX        ; null terminate file name
             MOV      FS:[81H+BX],AX
             MOV      AX,_PSP
             MOV      PINTFRAME.VMDS,EAX
             MOV      EDX,80H
; skip spaces
SPACSKIP:
             INC      DX
             CMP      BYTE PTR FS:[EDX],' '
             JZ       SHORT SPACSKIP
             MOV      AX,3D00H
             MOV      EBX,OFFSET PINTFRAME
             VM86CALL              ; open file
             MOV      BX,SEG USERDATA
             MOV      PINTFRAME.VMDS,EBX
             JNC      SHORT FOPENED
; Couldn't open file -- print error
             MOV      EDX,OFFSET NFMSG
             MOV      AH,9
             MOV      EBX,OFFSET PINTFRAME
             VM86CALL              ; can't open file
             MOV      AL,1
             JMP32S   USTOP        ; back to DOS
; File opened OK
FOPENED:
             MOV      FHANDLE,AX   ; save file handle
; See if extended memory present
             MOV      AX,SEL_EXT
             LSL      EBX,EAX
             OR       EBX,EBX
             JNZ      SHORT USE_EXTMEM
; No extended memory present -- use DOS memory
             MOV      AX,SEL_FREE
USE_EXTMEM:
             MOV      GS,AX        ; memory segment --> GS
; Set heap pointer to top of availble memory
             LSL      EAX,EAX
             MOV      HEAPPTR,EAX
             MOV      EDX,EAX
             XOR      EBX,EBX
REREAD:
             MOV      EAX,EBX      ; check for heap overflow
             SHL      EAX,2
             CMP      EAX,EDX
             JB       SHORT HEAPOK
; No more memory -- Print error message
             MOV      AH,9
             MOV      EDX,OFFSET OMMSG
             MOV      EBX,OFFSET PINTFRAME
             VM86CALL              ; out of memory
             MOV      AL,1
             JMP32S   USTOP        ; back to DOS
HEAPOK:
; Put heap pointer in lines array
             MOV      GS:[EBX*4],EDX
; read line to heap in reverse order. Each line ends in CR
             CALL     SHORT READLINE
             PUSHFD
; point to next line in lines array
             INC      EBX
             POPFD
             JNZ      SHORT REREAD ; not end of file?
; here at end of file
             MOV      PTRPTR,EBX   ; end of lines array
             MOV      BX,FHANDLE
             MOV      PINTFRAME.VMEBX,EBX
             MOV      AH,3EH
             MOV      EBX,OFFSET PINTFRAME
             VM86CALL              ; close file
             MOV      DX,1800H
             CALL     SHORT SETCP  ; set cursor position
; write copyright message
IF           DOSOUT
             MOV      AH,9
             MOV      EDX,OFFSET CPYMSG
             MOV      EBX,OFFSET PINTFRAME
             VM86CALL              ; print copyright
ELSE
; If not BIOS output then get rid of BIOS cursor
             MOV      DX,0FFFFH
             CALL32S  DOSSETCP     ; hide cursor
             MOV      EBX,OFFSET CPYMSG
             CALL32F  SEL_CODE32,MESSOUT
ENDIF


; inverse video the bottom line
             PUSH     DS
             MOV      AX,SEL_VIDEO
             MOV      DS,AX
             MOV      CL,80
             MOV      EDX,24*160+1 ; bottom line
INVLOOP:
             MOV      BYTE PTR [EDX],70H
             INC      EDX
             INC      EDX
             DEC      CL
             JNZ      SHORT INVLOOP
             POP      DS
             JMP      SHORT HK0    ; skip over home key processing
; Home key processing
HOMEKEY:     OR       BASE,0       ; don't do home if already there
             JZ       SHORT KEYLOOP
HK0:
             XOR      EAX,EAX      ; Set screen start to 0
             MOV      BASE,EAX
; Come here to display a page starting at base
PAGEDISP:
             CALL     SHORT DISPLAY
; read a key and act on it
KEYLOOP:     MOV      AX,16H
             MOV      BINTFRAME.VMINT,EAX
             XOR      AH,AH
             PUSH     ES
             MOV      BX,SEL_UDATA
             MOV      ES,BX
             MOV      EBX,OFFSET BINTFRAME
             VM86CALL              ; read a key
             POP      ES
             CMP      AL,27        ; escape key?
             JZ       NEAR PTR USTOP0
             CMP      AL,3         ; break key
             JZ       NEAR PTR USTOP0
             OR       AL,AL
             JNZ      SHORT KEYERR ; special key?
; test for function keys
             CMP      AH,48H       ; up arrow
             JCC32    Z, UPARROW
             CMP      AH,47H       ; home key
             JZ       SHORT HOMEKEY
             CMP      AH,4FH       ; end key
             JCC32    Z,ENDKEY
             CMP      AH,49H       ; page up
             JCC32    Z,PGUP
             CMP      AH,51H       ; page down
             JCC32    Z,PGDN
             CMP      AH,50H       ; down arrow
             JZ       SHORT DNARROW
; Come here if you don't know what that key was!
KEYERR:
             PUSH     EDX
             MOV      DL,7         ; bell character
             MOV      AH,2
             MOV      EBX,OFFSET PINTFRAME
             VM86CALL
             POP      EDX
             JMP      SHORT KEYLOOP

; down arrow processing
DNARROW:
             MOV      EAX,BASE     ; don't go down if at end
             ADD      EAX,24
             CMP      EAX,PTRPTR
             JCC32    AE,KEYLOOP
             INC      BASE         ; top of screen + 1
             MOV      AX,0601H     ; scroll screen
             CALL     SHORT SCROLL
             MOV      DX,1700H     ; position cursor
             CALL     SHORT SETCP
             MOV      EDX,BASE     ; print bottom line
             ADD      EDX,23
             CALL     SHORT PLINE
             JMP32S   KEYLOOP

; up arrow processing
UPARROW:
             OR       BASE,0       ; don't go up if at top
             JCC32    Z,KEYLOOP
             DEC      BASE         ; adjust top of screen
             MOV      AX,0701H     ; scroll screen
             CALL     SHORT SCROLL
             XOR      DX,DX        ; position cursor
             CALL     SHORT SETCP
             MOV      EDX,BASE     ; print top line
             CALL     SHORT PLINE
             JMP32S   KEYLOOP

; End key processing
ENDKEY:      MOV      EAX,BASE     ; Don't do end if already there
             ADD      EAX,24
             CMP      EAX,PTRPTR
             JCC32    AE,KEYLOOP
             MOV      EAX,PTRPTR   ; get last line
             SUB      EAX,24       ; find top of screen
             MOV      BASE,EAX
             JMP32S   PAGEDISP     ; display page


; Page Up processing
PGUP:        MOV      EAX,BASE     ; Don't do pgup if at top
             OR       EAX,EAX
             JCC32    Z,KEYLOOP
             CMP      EAX,24
             JBE      SHORT PGUP0  ; if near the top.. go pgup0
             SUB      EAX,24       ; find new line
             JMP      SHORT PGUP1
PGUP0:       XOR      EAX,EAX      ; 1st line
PGUP1:       MOV      BASE,EAX
             JMP32S   PAGEDISP

; Page down processing
PGDN:        MOV      EAX,BASE     ; If at bottom, skip
             ADD      EAX,24
             CMP      EAX,PTRPTR
             JCC32    AE,KEYLOOP
             MOV      BASE,EAX     ; find new line
             JMP32S   PAGEDISP



; Exit FBROWSE
USTOP0:
; clear screen
             CALL32F  SEL_CODE32,CLS
             XOR      AL,AL        ; zero return code
             PUSH     EAX
             XOR      DX,DX        ; home cursor
             CALL     SHORT DOSSETCP
             POP      EAX
USTOP:
             BACK2DOS

USER         ENDP

; Read a line from the file
; lines end in 0AH unless they are long in which case they
; end in 0DH
READLINE     PROC     NEAR
             PUSH     ECX
             MOV      CX,80
RLINE0:
             CALL     SHORT GFCHAR ; get character
             MOV      GS:[EDX],AL  ; store it
             PUSHFD
             DEC      EDX
             POPFD                 ; end of line?
             JZ       SHORT RLINEXIT
             CMP      AL,0AH       ; end of line?
             JZ       SHORT RLINEEOL
             DEC      CX
             JNZ      SHORT RLINE0 ; 80 char line?
; compare last character with 0ah  -- if true pass it
             CALL     SHORT GFCHAR
             CMP      AL,0AH
; if so, line exactly 80 chars
             JZ       SHORT LONGLINE
             DEC      FBUFP        ; unget character
LONGLINE:
             MOV      AL,0DH       ; mark long line
             MOV      GS:[EDX],AL  ; save end marker
             DEC      EDX
RLINEEOL:    CMP      AL,0FFH      ; set NZ flag
RLINEXIT:
             POP      ECX
             RET
READLINE     ENDP

; scroll screen using BIOS
SCROLL       PROC     NEAR
             PUSH     ES
             MOV      BX,SEL_UDATA
             MOV      ES,BX
             MOV      BX,0700H
             MOV      BINTFRAME.VMEBX,EBX
             MOV      BX,10H
             MOV      BINTFRAME.VMINT,EBX
             XOR      ECX,ECX
             MOV      DX,174FH
             MOV      EBX,OFFSET BINTFRAME
             VM86CALL
             POP      ES
             RET
SCROLL       ENDP

; position the cursor
DOSSETCP     PROC     NEAR
             PUSH     EBX
             PUSH     ES
             MOV      AX,SEL_UDATA
             MOV      ES,AX
             MOV      BX,10H
             MOV      BINTFRAME.VMINT,EBX
             MOV      BINTFRAME.VMEBX,EBX
             MOV      AH,2
             MOV      EBX,OFFSET BINTFRAME
             VM86CALL
             POP      ES
             POP      EBX
             RET
DOSSETCP     ENDP

SETCP        PROC     NEAR
IF           DOSOUT
             CALL32S  DOSSETCP
ELSE
             PUSH     ES
             MOV      AX,SEL_DATA
             MOV      ES,AX
             MOV      EAX,80
             MUL      DH
             XOR      DH,DH
             ADD      AX,DX
             MOV      ES:CURSOR,EAX
             POP      ES
             RET
ENDIF
SETCP        ENDP


; display a screen full
DISPLAY      PROC     NEAR
             MOV      AX,0600H
             CALL32S  SCROLL       ; clear screen
             XOR      DX,DX
             CALL32S  SETCP        ; home cursor
             MOV      CX,24
             MOV      EDX,BASE
DISP0:
             PUSH     EDX
             CALL     SHORT PLINE  ; print lines
             POP      EDX
             INC      EDX
             CMP      EDX,PTRPTR
             JZ       DISP00
             DEC      CX
             JNZ      SHORT DISP0
DISP00:
             RET
DISPLAY      ENDP

; print a line pointed to by [edx] going backwards to \n
PLINE        PROC     NEAR
; get line ptr
             MOV      EDX,GS:[EDX*4]
PLINE0:
             MOV      AL,GS:[EDX]  ; get char fm str
             DEC      DX
             CMP      AL,0DH       ; Long line?
             JZ       SHORT PLINE2
             CALL     SHORT OOUCH  ; Output character
             CMP      AL,0AH       ; End of string
             JNZ      SHORT PLINE0
PLINE1:
             MOV      AL,0DH       ; Output CR
             CALL     SHORT OOUCH
PLINE2:
             RET
PLINE        ENDP


; get a character from the file
GFCHAR       PROC     NEAR
             PUSH     EBX
             PUSH     EDX
             PUSH     ECX
GFSKIP:
             OR       FBUFL,0      ; buffer empty?
             JZ       SHORT FILLBUFF
             MOV      EAX,FBUFP    ; buffer used up?
             CMP      AX,FBUFL
             JNZ      SHORT FMBUFF
FILLBUFF:
             MOV      AX,FHANDLE   ; read from file
             MOV      PINTFRAME.VMEBX,EAX
             MOV      ECX,512
             MOV      EDX,OFFSET FBUF
             MOV      AH,3FH
             MOV      EBX,OFFSET PINTFRAME
             VM86CALL              ; read buffer
             JNC      SHORT NOFERR
; file error here
             MOV      AH,9
             MOV      EDX,OFFSET FEMSG
             VM86CALL
             JMP32S   USTOP        ; back to DOS
NOFERR:      OR       AX,AX
             MOV      FBUFL,AX     ; store buffer length
             MOV      AL,0AH
             JZ       SHORT GFCEOF ; End of file?
             XOR      EAX,EAX      ; clear buffer pointer
             MOV      FBUFP,EAX
; This is where the character is read from a good buffer
FMBUFF:
             MOV      EBX,FBUFP
             MOV      AL,FBUF[EBX]
             INC      EBX
             MOV      FBUFP,EBX
             CMP      AL,0DH       ; skip CR
             JCC32    Z,GFSKIP
GFCEOF:
             POP      ECX
             POP      EDX
             POP      EBX
             RET
GFCHAR       ENDP

; output character in al
OOUCH        PROC     NEAR
IF           DOSOUT
             PUSHAD
             PUSH     ES
             ASSUME   ES:DAT32
             MOV      BX,SEL_DATA
             MOV      ES,BX
             MOV      AH,2
             MOV      DL,AL
             MOV      EBX,OFFSET PINTFRAME
             VM86CALL
             POP      ES
             POPAD
             RET
ELSE
             CALL32F  SEL_CODE32,OUCH
             RET
ENDIF
OOUCH        ENDP


             PROT_CODE_END
