REM Program: Hex Editor v3.6a, Module 3 of 4, PD 2003.
REM Author: Erik Jon Oredson AS. Csci
REM Release: 04/01/2003.
REM Status: Public Domain.
REM Email: eoredson@yahoo.com
REM Urls: www.simtel.net www.filegate.net
REM  www.winsite.com

' get include file.
REM $INCLUDE: 'hexedit.inc'

REM Clipboard routines:

SUB Clipboard(Var)
' declare error trap.
ON LOCAL ERROR GOTO Error.Routine

IF Var = 1 THEN
   GOSUB CopyToClipboard
   EXIT SUB
END IF
IF Var = 2 THEN
   GOSUB PasteFromClipboard
   EXIT SUB
END IF
IF Var = 3 THEN
   GOSUB ClipboardUndo
   EXIT SUB
END IF
IF Var = 4 THEN
   GOSUB ClipboardUndoAll
   EXIT SUB
END IF
TopProgram:
EXIT SUB

' copy hilighted area to clipboard file.
CopyToClipboard:
 IF CopyPositionStart = 0 THEN
    RETURN
 END IF
 ' store clipboard length.
 ByteLength# = CopyPositionEnd - CopyPositionStart + 1#
 IF ByteLength# + 4# > 2147483647# THEN
    StatusMessage = "Copy extends beyond clipboard file length."
    GOSUB DisplayStatus2
    RETURN
 END IF
 Bytes$ = RIGHT$("00000000" + HEX$(ByteLength#), 8)
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 1, 2)))
 PUT #4, 1, CopyByte
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 3, 2)))
 PUT #4, 2, CopyByte
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 5, 2)))
 PUT #4, 3, CopyByte
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 7, 2)))
 PUT #4, 4, CopyByte
 ' write clipboard.
 Temp# = FilePosition
 Temp2# = 4#
 FOR NextByte = CopyPositionStart TO CopyPositionEnd
    SeekPosition = NextByte
    GOSUB LseekFile
    GOSUB ReadFile
    FileByte = Buffer
    Temp2# = Temp2# + 1#
    PUT #4, Temp2#, FileByte
 NEXT
 FilePosition = Temp#
 GOSUB ClearStatus
 PRINT "Editing file: "; RTRIM$(Filename); " :";
 PRINT STR$(ByteLength#) + " bytes copied to clipboard.";
 GOSUB LocateCursor2
 RETURN

' paste clipboard to current file position.
PasteFromClipboard:
 ' read clipboard length.
 IF LOF(4) = 0 THEN
    RETURN
 END IF
 ' get clipboard length.
 ByteLength# = 0#
 GET #4, 1, CopyByte
 ByteLength# = ByteLength# + ASC(CopyByte) * 16 ^ 6
 GET #4, 2, CopyByte
 ByteLength# = ByteLength# + ASC(CopyByte) * 16 ^ 4
 GET #4, 3, CopyByte
 ByteLength# = ByteLength# + ASC(CopyByte) * 16 ^ 2
 GET #4, 4, CopyByte
 ByteLength# = ByteLength# + ASC(CopyByte) 
 ' check end of file.
 IF FilePosition + ByteLength# - 1# > FileLength THEN
    StatusMessage = "Paste extends beyond file length."
    GOSUB DisplayStatus2
    RETURN
 END IF
 ' clear hilight area.
 IF CopyPositionStart > 0# THEN
    GOSUB ResetHilightBytes
 END IF
 ' store paste area to undo clipboard file.
 GOSUB StoreUndoPaste
 IF ValidPaste = 0 THEN
    RETURN
 END IF
 ' write paste bytes.
 Temp# = FilePosition
 LastPage = FilePage
 FOR NextByte = 1# TO ByteLength#
    GET #4, NextByte + 4#, FileByte
    SeekPosition = FilePosition
    GOSUB LseekFile
    GOSUB WriteFile
    GOSUB CalculatePosition1
    IF LastPage = FilePage THEN
       GOSUB ClearPageByte
    END IF
    IF NextByte < ByteLength# THEN
       FilePosition = FilePosition + 1
    END IF
 NEXT
 FilePosition = Temp#
 GOSUB CalculatePosition1
 GOSUB DisplayPageByte
 GOSUB ClearStatus
 PRINT "Editing file: "; RTRIM$(Filename); " :";
 PRINT STR$(ByteLength#) + " bytes pasted from clipboard.";
 GOSUB LocateCursor2
 RETURN

' store area to be over-written into paste undo file.
'   see also: Struc.txt describes file format.
StoreUndoPaste:
 IF LEN(5)=0 THEN
    CopyByte = CHR$(0)
    PUT #5, 1, CopyByte
    PUT #5, 2, CopyByte
 END IF
 ValidPaste = -1
 ' get maximum entries.
 ByteEntries = 0
 GET #5, 1, CopyByte
 ByteEntries = ByteEntries + ASC(CopyByte) * 16 ^ 2
 GET #5, 2, CopyByte
 ByteEntries = ByteEntries + ASC(CopyByte) 
 IF ByteEntries >= 32767 THEN
    ValidPaste = 0
    StatusMessage = "Paste undo entries extend beyond file length."
    GOSUB DisplayStatus2
    RETURN
 END IF
 ' scan to last entry position in paste undo file.
 Temp2# = 3#
 Temp3 = 1
 DO
    ' check last entry.
    IF Temp3 > ByteEntries THEN
       EXIT DO
    END IF
    ' skip file start position.
    Temp2# = Temp2# + 4#
    LengthOfRecord# = 0#
    GET #5, Temp2#, CopyByte
    LengthOfRecord# = LengthOfRecord# + ASC(CopyByte) * 16 ^ 6
    Temp2# = Temp2# + 1#
    GET #5, Temp2#, CopyByte
    LengthOfRecord# = LengthOfRecord# + ASC(CopyByte) * 16 ^ 4
    Temp2# = Temp2# + 1#
    GET #5, Temp2#, CopyByte
    LengthOfRecord# = LengthOfRecord# + ASC(CopyByte) * 16 ^ 2
    Temp2# = Temp2# + 1#
    GET #5, Temp2#, CopyByte
    LengthOfRecord# = LengthOfRecord# + ASC(CopyByte) 
    Temp2# = Temp2# + LengthOfRecord# + 1#
    Temp3 = Temp3 + 1
 LOOP
 ' get maximum entries.
 TotalBytes# = Temp2# + ByteLength# + 8#
 IF TotalBytes# >= 2147483647# THEN
    ValidPaste = 0
    StatusMessage = "Paste undo extends beyond file length."
    GOSUB DisplayStatus2
    RETURN
 END IF
 ' store new entry.
 ByteEntries = ByteEntries + 1
 Bytes$ = RIGHT$("0000" + HEX$(ByteEntries), 4)
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 1, 2)))
 PUT #5, 1, CopyByte
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 3, 2)))
 PUT #5, 2, CopyByte
 ' store file position of undo paste area.
 Bytes$ = RIGHT$("00000000" + HEX$(FilePosition), 8)
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 1, 2)))
 PUT #5, Temp2#, CopyByte
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 3, 2)))
 Temp2# = Temp2# + 1#
 PUT #5, Temp2#, CopyByte
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 5, 2)))
 Temp2# = Temp2# + 1#
 PUT #5, Temp2#, CopyByte
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 7, 2)))
 Temp2# = Temp2# + 1#
 PUT #5, Temp2#, CopyByte
 ' store length of undo paste area.
 Bytes$ = RIGHT$("00000000" + HEX$(ByteLength#), 8)
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 1, 2)))
 Temp2# = Temp2# + 1#
 PUT #5, Temp2#, CopyByte
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 3, 2)))
 Temp2# = Temp2# + 1#
 PUT #5, Temp2#, CopyByte
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 5, 2)))
 Temp2# = Temp2# + 1#
 PUT #5, Temp2#, CopyByte
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 7, 2)))
 Temp2# = Temp2# + 1#
 PUT #5, Temp2#, CopyByte
 ' write undo clipboard.
 Temp# = FilePosition
 FOR NextByte = Temp# TO Temp# + ByteLength# - 1#
    SeekPosition = NextByte
    GOSUB LseekFile
    GOSUB ReadFile
    FileByte = Buffer
    Temp2# = Temp2# + 1#
    PUT #5, Temp2#, FileByte
 NEXT
 FilePosition = Temp#
 RETURN

' undo last paste clipboard.
ClipboardUndo:
 IF LEN(5)=0 THEN
    CopyByte = CHR$(0)
    PUT #5, 1, CopyByte
    PUT #5, 2, CopyByte
    RETURN
 END IF
 GOSUB UndoClipboard
 IF ValidPaste = 0 THEN
    RETURN
 END IF
 GOSUB ClearStatus
 PRINT "Editing file: "; RTRIM$(Filename); " :";
 PRINT STR$(ByteLength#) + " undo bytes copied from clipboard.";
 GOSUB LocateCursor2
 RETURN

' undo all paste clipboard.
ClipboardUndoAll:
 IF LEN(5)=0 THEN
    CopyByte = CHR$(0)
    PUT #5, 1, CopyByte
    PUT #5, 2, CopyByte
    RETURN
 END IF
 ByteEntries = 0
 GET #5, 1, CopyByte
 ByteEntries = ByteEntries + ASC(CopyByte) * 16 ^ 2
 GET #5, 2, CopyByte
 ByteEntries = ByteEntries + ASC(CopyByte)
 TotalEntries = ByteEntries
 DO
    GOSUB UndoClipboard
    IF ValidPaste = 0 THEN
       EXIT DO
    END IF
 LOOP
 GOSUB ClearStatus
 PRINT "Editing file: "; RTRIM$(Filename); " :";
 PRINT STR$(TotalEntries) + " undo pastes copied from clipboard.";
 GOSUB LocateCursor2
 RETURN

' copy last undo paste entry.
UndoClipboard:
 ' get last entry.
 ByteEntries = 0
 GET #5, 1, CopyByte
 ByteEntries = ByteEntries + ASC(CopyByte) * 16 ^ 2
 GET #5, 2, CopyByte
 ByteEntries = ByteEntries + ASC(CopyByte) 
 ValidPaste = -1
 IF ByteEntries = 0 THEN
    ValidPaste = 0
    RETURN
 END IF
 ' scan to last entry position in paste undo file.
 Temp# = FilePosition
 Temp2# = 3#
 Temp3 = 1
 DO
    ' check last entry.
    IF Temp3 = ByteEntries THEN
       EXIT DO
    END IF
    ' skip file start position.
    Temp2# = Temp2# + 4#
    LengthOfRecord# = 0#
    GET #5, Temp2#, CopyByte
    LengthOfRecord# = LengthOfRecord# + ASC(CopyByte) * 16 ^ 6
    Temp2# = Temp2# + 1#
    GET #5, Temp2#, CopyByte
    LengthOfRecord# = LengthOfRecord# + ASC(CopyByte) * 16 ^ 4
    Temp2# = Temp2# + 1#
    GET #5, Temp2#, CopyByte
    LengthOfRecord# = LengthOfRecord# + ASC(CopyByte) * 16 ^ 2
    Temp2# = Temp2# + 1#
    GET #5, Temp2#, CopyByte
    LengthOfRecord# = LengthOfRecord# + ASC(CopyByte) 
    Temp2# = Temp2# + LengthOfRecord# + 1#
    Temp3 = Temp3 + 1
 LOOP
 ' restore last entry.
 ByteEntries = ByteEntries - 1
 Bytes$ = RIGHT$("0000" + HEX$(ByteEntries), 4)
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 1, 2)))
 PUT #5, 1, CopyByte
 CopyByte = CHR$(VAL("&H" + MID$(Bytes$, 3, 2)))
 PUT #5, 2, CopyByte
 ' restore file position of undo paste area.
 NextByte = 0#
 GET #5, Temp2#, CopyByte
 NextByte = NextByte + ASC(CopyByte) * 16 ^ 6
 Temp2# = Temp2# + 1#
 GET #5, Temp2#, CopyByte
 NextByte = NextByte + ASC(CopyByte) * 16 ^ 4
 Temp2# = Temp2# + 1#
 GET #5, Temp2#, CopyByte
 NextByte = NextByte + ASC(CopyByte) * 16 ^ 2
 Temp2# = Temp2# + 1#
 GET #5, Temp2#, CopyByte
 NextByte = NextByte + ASC(CopyByte) 
 ' restore length of undo paste area.
 ByteLength# = 0#
 Temp2# = Temp2# + 1#
 GET #5, Temp2#, CopyByte
 ByteLength# = ByteLength# + ASC(CopyByte) * 16 ^ 6
 Temp2# = Temp2# + 1#
 GET #5, Temp2#, CopyByte
 ByteLength# = ByteLength# + ASC(CopyByte) * 16 ^ 4
 Temp2# = Temp2# + 1#
 GET #5, Temp2#, CopyByte
 ByteLength# = ByteLength# + ASC(CopyByte) * 16 ^ 2
 Temp2# = Temp2# + 1#
 GET #5, Temp2#, CopyByte
 ByteLength# = ByteLength# + ASC(CopyByte) 
 ' undo paste area.
 Temp# = FilePosition
 Temp3# = NextByte
 LastPage = FilePage
 FOR FilePosition = Temp3# TO Temp3# + ByteLength# - 1#
    Temp2# = Temp2# + 1#
    GET #5, Temp2#, FileByte
    SeekPosition = FilePosition
    GOSUB LseekFile
    GOSUB WriteFile
    GOSUB CalculatePosition1
    IF LastPage = FilePage THEN
       GOSUB ClearPageByte
    END IF
 NEXT
 FilePosition = Temp#
 GOSUB CalculatePosition1
 GOSUB DisplayPageByte
 RETURN

' read 1 byte from file.
ReadFile:
 InregsX.AX = &H3F00
 InregsX.BX = Handle
 InregsX.CX = 1
 InregsX.DS = VARSEG(Buffer)
 InregsX.DX = VARPTR(Buffer)
 CALL InterruptX(&H21, InregsX, OutregsX)
 RETURN

' write 1 byte to file.
WriteFile:
 Buffer = FileByte
 InregsX.AX = &H4000
 InregsX.BX = Handle
 InregsX.CX = 1
 InregsX.DS = VARSEG(Buffer)
 InregsX.DX = VARPTR(Buffer)
 CALL InterruptX(&H21, InregsX, OutregsX)
 RETURN

' position pointer in file.
LseekFile:
 ' calculate high/low seek position.
 SeekPosition2 = SeekPosition - 1
 High = INT(SeekPosition2 / 65536)
 Low = SeekPosition2 AND 65535
 ' seek position in file.
 InregsX.AX = &H4200
 InregsX.BX = Handle
 InregsX.CX = INT(VAL("&H" + HEX$(High)))
 InregsX.DX = INT(VAL("&H" + HEX$(Low)))
 CALL InterruptX(&H21, InregsX, OutregsX)
 RETURN

' clear top status area
ClearStatus:
 COLOR Yellow
 LOCATE 2, 4
 PRINT SPACE$(74);
 LOCATE 2, 4
 RETURN

' display status line message and prompt type 2.
DisplayStatus2:
 StatusMessage = StatusMessage + " Press <esc> to continue:"

' display the status line message and prompt for key.
DisplayStatusLine:
 GOSUB ClearStatus
 PRINT StatusMessage;
 GOSUB LocateCursor2
 WHILE INKEY$ <> CHR$(27): WEND
 GOSUB DisplayFilename
 RETURN

' select cursor on window.
LocateCursor2:
 IF CopyPositionStart = 0 THEN
    GOSUB LocateCursor
 ELSE
    GOSUB LocateHilightCursor
 END IF
 RETURN

' locate cursor on window.
LocateCursor:
 IF FileLength = False THEN
    LOCATE 1, 1, 0
    RETURN
 END IF
 IF CurrentWindow = False THEN
    LOCATE PageRow + 3, Column + 5, 1
 ELSE
    LOCATE PageRow + 3, PageColumn + 54, 1
 END IF
 RETURN

' locate cursor on window for hilighted area.
LocateHilightCursor:
 IF FileLength = False THEN
    LOCATE 1, 1, 0
    RETURN
 END IF
 IF CurrentWindow = False THEN
    LOCATE PageRow + 3, Column + 7, 1
 ELSE
    LOCATE PageRow + 3, PageColumn + 55, 1
 END IF
 RETURN

' reset hilight bytes.
ResetHilightBytes:
 Temp# = FilePosition
 Temp2# = FilePage
 IF CopyPositionStart < (Temp2# - 1) * 320 + 1 THEN
    CopyPositionStart = (Temp2# - 1) * 320 + 1
 END IF
 IF CopyPositionEnd > (Temp2# - 1) * 320 + 320 THEN
    CopyPositionEnd = (Temp2# - 1) * 320 + 320
 END IF
 IF CopyPositionEnd > FileLength THEN
    CopyPositionEnd = FileLength
 END IF
 FOR FilePosition = CopyPositionStart TO CopyPositionEnd
    GOSUB CalculatePosition1
    COLOR White
    GOSUB BytePrint
 NEXT
 CopyPositionStart = 0
 CopyPositionEnd = 0
 FilePosition = Temp#
 GOSUB CalculatePosition1
 IF PageColumn + 1 <= 20 THEN
    IF FilePosition + 1 <= FileLength THEN
       GOSUB ClearPageByte
       PageColumn = PageColumn + 1
       FilePosition = FilePosition + 1
       GOSUB DisplayPageByte
    END IF
 END IF
 COLOR Yellow
 GOSUB BytePrint
 RETURN

' redraw editing screen
RedrawScreen:
 ScreenDrawn = True
 CALL DisplayScreen
 GOSUB DisplayFilename
 GOSUB DisplayHexPage
 GOSUB DisplayPageByte
 IF FileDisplay = 0 THEN
    GOSUB DisplayFilename3
 END IF
 RETURN

' print the byte.
BytePrint:
 SeekPosition = FilePosition
 GOSUB LseekFile
 GOSUB ReadFile
 FileByte = Buffer
 ByteValue = ASC(FileByte)
 GOSUB CalculateColumn
 ' display hex byte.
 LOCATE PageRow + 3, Column + 5, 0
 PRINT RIGHT$("00" + HEX$(ByteValue), 2);
 ' check right window toggled.
 IF CurrentWindow2 = False THEN
    ' display ascii byte.
    LOCATE PageRow + 3, PageColumn + 54, 0
    ' skip unprintable characters.
    SELECT CASE ByteValue
    CASE 0, 7, 9 TO 13, 28 TO 32
       PRINT ".";
    CASE ELSE
       PRINT FileByte;
    END SELECT
 END IF
 RETURN

' display name of file being edited.
DisplayFilename:
 GOSUB ClearStatus
 PRINT "Editing file: "; RTRIM$(Filename); " ";
 IF FileLength = False THEN
    GOSUB LockedFile
    RETURN
 END IF
 GOSUB DisplayPosition
 RETURN

' clear current byte on screen.
ClearPageByte:
 IF FileLength = False THEN
    LOCATE 1, 1, 0
    RETURN
 END IF
 IF CopyPositionStart > 0# THEN
    GOSUB ResetHilightBytes
 END IF
 COLOR White
 GOSUB BytePrint
 RETURN

' clear current hilighted byte on screen.
ClearHilightPageByte:
 IF FileLength = False THEN
    LOCATE 1, 1, 0
    RETURN
 END IF
 COLOR White
 GOSUB BytePrint
 RETURN

' calculate current screen page number, row, and column, given file position.
CalculatePosition1:
 FilePage = INT((FilePosition - 1) / 320) + 1
 PageRow = INT((FilePosition - (FilePage - 1) * 320 - 1) / 20) + 1
 PageColumn = INT((FilePosition - (FilePage - 1) * 320) - (PageRow - 1) * 20)
 RETURN

' display screen of current page of hex/ascii values of file being edited.
DisplayHexPage:
 IF FileLength = False THEN
    LOCATE 1, 1, 0
    RETURN
 END IF
 COLOR White
 Row = False
 Column = False
 ColumnSpace = False
 FOR NextByte = (FilePage - 1) * 320 + 1 TO (FilePage - 1) * 320 + 320
    LOCATE Row + 4, Column + 6, 0
    IF NextByte <= FileLength THEN
       SeekPosition = NextByte
       GOSUB LseekFile
       GOSUB ReadFile
       FileByte = Buffer
       PRINT RIGHT$("00" + HEX$(ASC(FileByte)), 2);
    ELSE
       PRINT "  ";
    END IF
    ColumnSpace = ColumnSpace + 1
    IF ColumnSpace = 4 THEN
       PRINT " ";
       Column = Column + 1
       ColumnSpace = False
    END IF
    Column = Column + 2
    IF Column > 44 THEN
       Row = Row + 1
       Column = False
    END IF
 NEXT
 GOSUB RedrawRightWindow
 RETURN

' redraw right window.
RedrawRightWindow:
 IF CurrentWindow2 = False THEN
    GOSUB RedrawWindow1
 ELSE
    GOSUB RedrawWindow2
 END IF
 RETURN

' redraw right window of ascii values.
RedrawWindow1:
 COLOR White
 Row = False
 Column = False
 FOR NextLine = 0 TO 15
    LOCATE NextLine + 4, 54, 0
    PRINT " ";
 NEXT
 FOR NextByte = (FilePage - 1) * 320 + 1 TO (FilePage - 1) * 320 + 320
    LOCATE Row + 4, Column + 55, 0
    IF NextByte <= FileLength THEN
       SeekPosition = NextByte
       GOSUB LseekFile
       GOSUB ReadFile
       FileByte = Buffer
       ByteValue = ASC(FileByte)
       ' skip unprintable characters
       SELECT CASE ByteValue
       CASE 0, 7, 9 TO 13, 28 TO 32
          PRINT ".";
       CASE ELSE
          PRINT FileByte;
       END SELECT
    ELSE
       PRINT " ";
    END IF
    Column = Column + 1
    IF Column > 19 THEN
       Row = Row + 1
       Column = False
    END IF
 NEXT
 RETURN

' redraw right window of hex ranges.
RedrawWindow2:
 Row = False
 FOR NextLine = (FilePage - 1) * 320 + 1 TO (FilePage - 1) * 320 + 320 STEP 20
    LOCATE Row + 4, 54, 0
    Row = Row + 1
    IF NextLine <= FileLength THEN
       IF NextLine + 19 <= FileLength THEN
          COLOR White
          PRINT "x"; RIGHT$("00000000" + HEX$(NextLine - 1), 8);
          COLOR Yellow
          PRINT " - ";
          COLOR White
          PRINT "x"; RIGHT$("00000000" + HEX$(NextLine + 19 - 1), 8);
       ELSE
          COLOR White
          PRINT "x"; RIGHT$("00000000" + HEX$(NextLine - 1), 8);
          COLOR Yellow
          PRINT " - ";
          COLOR White
          PRINT "x"; RIGHT$("00000000" + HEX$(FileLength - 1), 8);
       END IF
    ELSE
       PRINT SPACE$(22);
    END IF
 NEXT
 RETURN

' display current byte being edited.
DisplayPageByte:
 IF FileDisplay2 THEN
    FileDisplay2 = 0
    GOSUB ClearStatus
    PRINT "Editing file: "; RTRIM$(Filename); " ";
 END IF
 ' display byte.
 IF FileLength = False THEN
    LOCATE 1, 1, 0
    RETURN
 END IF
 IF CopyPositionStart > 0# THEN
    GOSUB ResetHilightBytes
 END IF
 COLOR Yellow
 GOSUB BytePrint
 ' store the current byte value being edited.
 AsciiValue = ASC(FileByte)
 GOSUB DisplayPosition
 RETURN

' display 64-byte name of file being editied.
DisplayFilename3:
 Z$ = ShortFilename
 GOSUB Deconcatenate
 Var = INT((80 - (LEN(RTRIM$(Z$))) + 2) / 2) - 1
 LOCATE 3, Var
 COLOR 0, 7
 PRINT " ";Z$;" ";
 COLOR 7, 0
 GOSUB LocateCursor2
 RETURN

' deconcatenate long path with \..\ characters
Deconcatenate:
 DO
    IF LEN(Z$) > 64 THEN
       V1 = INSTR(Z$, "\")
       IF V1 > 0 THEN
          DO
             IF MID$(Z$, V1, 4) = "\..\" THEN
                V1 = INSTR(V1 + 1, Z$, "\")
             ELSE
                EXIT DO
             END IF
          LOOP
          V2 = INSTR(V1 + 1, Z$, "\")
          IF V2 > 0 THEN
             Z$ = LEFT$(Z$, V1) + ".." + MID$(Z$, V2)
          ELSE
             EXIT DO
          END IF
       END IF
    ELSE
       EXIT DO
    END IF
 LOOP
 DO
    V = INSTR(Z$, "\..\..\")
    IF V > 0 THEN
       Z$ = LEFT$(Z$, V - 1) + "\..\" + MID$(Z$, V + 7)
    ELSE
       EXIT DO
    END IF
 LOOP
 IF LEN(Z$) > 64 THEN
    Z$ = LEFT$(Z$, 64)
 END IF
 RETURN

' display info on file being edited.
DisplayPosition:
 COLOR Yellow
 StringLength = LEN("Editing file: " + RTRIM$(Filename) + " ") + 4
 LOCATE 2, StringLength, 0
 PRINT SPACE$(76 - StringLength);
 LOCATE 2, StringLength, 0
 IF FileLength = False THEN
    GOSUB LockedFile
    RETURN
 END IF
 IF CurrentWindow2 = False THEN
    PRINT "(Position:"; STR$(FilePosition); ") ";
 ELSE
    PRINT "(Position: "; RIGHT$("00000000" + HEX$(FilePosition - 1), 8); "H) ";
 END IF
 PRINT "(Ascii:"; STR$(AsciiValue); ") ";
 PRINT "(Hex: "; RIGHT$("00" + HEX$(AsciiValue), 2); "H)";
 GOSUB LocateCursor2
 RETURN

' calculate the spaces between hex value groups.
CalculateColumn:
 SELECT CASE PageColumn
 CASE 1 TO 4
    Column = (PageColumn - 1) * 2 + 1
 CASE 5 TO 8
    Column = (PageColumn - 1) * 2 + 2
 CASE 9 TO 12
    Column = (PageColumn - 1) * 2 + 3
 CASE 13 TO 16
    Column = (PageColumn - 1) * 2 + 4
 CASE 17 TO 20
    Column = (PageColumn - 1) * 2 + 5
 END SELECT
 RETURN

' display message for locked file.
LockedFile:
 COLOR White
 PRINT "<locked file>";
 LOCATE 1, 1, 0
 RETURN

' critical error trap.
Error.Routine:
 IF ScreenDrawn THEN
    CLS
 END IF
 ScreenDrawn = False
 COLOR White
 PRINT "Hex Editor Clipboard " + Version + " " + Release + " critical error trap:"
 COLOR Yellow
 ErrorTrap = ERR
 SELECT CASE ERR
 CASE 5
    PRINT "Syntax error." ' should not happen.
 CASE 6
    PRINT "Overflow."
 CASE 9
    PRINT "Subscript out of range."
 CASE 14
    PRINT "Out of string space."
 CASE 24
    PRINT "Device timeout."
 CASE 25
    PRINT "Device fault."
 CASE 27
    PRINT "Out of paper."
 CASE 52
    PRINT "Bad file name or number."
 CASE 53
    PRINT "File not found."
 CASE 54
    PRINT "Bad file mode."
 CASE 55
    PRINT "File already open."
 CASE 57
    PRINT "Device I/O error."
 CASE 58
    PRINT "File already exists."
 CASE 59
    PRINT "Bad record length."
 CASE 61
    PRINT "Disk full."
 CASE 62
    PRINT "Input past eof."
 CASE 63
    PRINT "Bad record length."
 CASE 64
    PRINT "Bad filename."
 CASE 67
    PRINT "Not enough file handles."
 CASE 68
    PRINT "Device unavailable."
 CASE 70
    PRINT "Permission denied."
 CASE 71
    PRINT "Disk not ready."
 CASE 72
    PRINT "Disk-media error."
 CASE 75
    PRINT "Path/File access error."
 CASE 76
    PRINT "Pathname not found."
 CASE 81
    PRINT "Invalid name."
 CASE ELSE
    PRINT "Untrapped error" + STR$(ERR) + "."
 END SELECT
 ' display error prompt.
 COLOR Green
 PRINT "Press R(etry), C(ontinue), Q(uit):";
 ' get keypress.
 DO
    ErrorRespond$ = Nul
    DO
       ErrorRespond$ = INKEY$
       IF LEN(ErrorRespond$) THEN
          EXIT DO
       END IF
    LOOP
    ' parse key.
    SELECT CASE LCASE$(ErrorRespond$)
    CASE "r"
       PRINT "r"
       RESUME
    CASE "c"
       PRINT "c"
       RESUME NEXT
    CASE "q"
       PRINT "q"
       RESUME TopProgram
    END SELECT
 LOOP

' anything goes here stops program.
StopProgram:
 COLOR Plain
 END
END SUB

REM Viewfile routine:

' returns Var=1 to 9 of file number.
SUB Viewfiles(Var)
' declare error trap.
ON LOCAL ERROR GOTO Error.Routine2
 GOSUB StoreArea
TopProgram1:
 GOSUB DrawMenu
 MenuSelect = 1
 GOSUB DrawCurrentMenuSelection
 IF Mouse.Present THEN
    CALL MouseFunction(ShowMouse, 0)
 END IF

 ' keyboard/mouse input loop.
 DO
    CharInput$ = ""

    ' call mouse subroutine.
    CALL MouseDriver

    ' check left mouse button.
    IF Mouse.ButtonX THEN
       Mouse.Row = Mouse.RowX
       Mouse.Column = Mouse.ColumnX
       GOSUB MouseButton1
    ELSE
       ' check right/middle mouse button.
       IF Mouse.Button2 OR Mouse.Button3 THEN
          GOSUB RestoreArea
          Var = False
          EXIT SUB
       ELSE
          ' check mouse position.
          IF Mouse.Row OR Mouse.Column THEN
             GOSUB MoveMouse
          END IF
       END IF
    END IF
    ' store keyboard buffer.
    CharInput$ = INKEY$
    IF LEN(CharInput$) THEN
       SELECT CASE LEN(CharInput$)
       CASE 1
          SELECT CASE ASC(CharInput$)
          CASE 13 ' Enter
             GOSUB RestoreArea
             IF MenuSelect > NumberFiles THEN
                Var = 0
             ELSE
                Var = MenuSelect
             END IF
             EXIT SUB
          CASE 27 ' Escape
             GOSUB RestoreArea
             Var = False
             EXIT SUB
          CASE 49 TO 57 ' 1 to 9
             GOSUB EraseCurrentMenuSelection
             MenuSelect = ASC(CharInput$) - 48
             GOSUB DrawCurrentMenuSelection
          END SELECT
       CASE 2
          SELECT CASE ASC(RIGHT$(CharInput$, 1))
          CASE 72 ' Up
             GOSUB MenuUp
          CASE 80 ' Down
             GOSUB MenuDown
          END SELECT
       END SELECT
    END IF
    ' release time slice,
    Var = ReleaseTime
 LOOP
 EXIT SUB

' draws menu.
DrawMenu:
 IF Mouse.Present THEN
    CALL MouseFunction(HideMouse, 0)
 END IF
 BoxDrawX1 = 4
 BoxDrawX2 = 14
 BoxDrawY1 = 6
 BoxDrawY2 = 21
 BoxDrawLength = 16
 GOSUB DrawBox
 COLOR White, Black
 FOR Var2 = 1 TO 9
    LOCATE 4 + Var2, 7, 0
    PRINT MID$(STR$(Var2), 2); " ";
    IF Var2 > NumberFiles THEN
       PRINT "<none>      ";
    ELSE
       PRINT File(Var2).Filename;
    END IF
 NEXT
 IF Mouse.Present THEN
    CALL MouseFunction(ShowMouse, 0)
 END IF
 RETURN

' draws box from (BoxDrawX1,BoxDrawY1) To (BoxDrawX2,BoxDrawY2),
'  length of box from left to right being BoxDrawLength.
DrawBox:
 COLOR Yellow
 LOCATE BoxDrawX1, BoxDrawY1, 0
 PRINT CHR$(ULcorner);STRING$(BoxDrawLength - 2, Hline);CHR$(URcorner);
 FOR RowX1 = BoxDrawX1 + 1 TO BoxDrawX2 - 1
    LOCATE RowX1, BoxDrawY1, 0
    PRINT CHR$(Vline);
    LOCATE RowX1, BoxDRawY2, 0
    PRINT CHR$(Vline);
 NEXT
 LOCATE BoxDrawX2, BoxDrawY1, 0
 PRINT CHR$(LLcorner);STRING$(BoxDrawLength - 2, Hline);CHR$(LRcorner);
 COLOR 0, 7
 LOCATE 4, 9, 0
 PRINT "View Files";
 COLOR Yellow
 RETURN

' stores area under menu.
StoreArea:
 IF Mouse.Present THEN
    CALL MouseFunction(HideMouse, 0)
 END IF
 RowX1 = 0
 ColumnY1 = 0
 FOR RowX2 = 4 TO 14
    RowX1 = RowX1 + 1
    ColumnY1 = 0
    FOR ColumnY2 = 6 TO 21
       ColumnY1 = ColumnY1 + 1
       ' store ascii character.
       Area3(RowX1, ColumnY1) = SCREEN(RowX2, ColumnY2)
       ' store color. (undocumented: also stores background color).
       Area4(RowX1, ColumnY1) = SCREEN(RowX2, ColumnY2, 1)
    NEXT
 NEXT
 IF Mouse.Present THEN
    CALL MouseFunction(ShowMouse, 0)
 END IF
 RETURN

' restores area under menu.
RestoreArea:
 IF Mouse.Present THEN
    CALL MouseFunction(HideMouse, 0)
 END IF
 RowX1 = 0
 ColumnY1 = 0
 FOR RowX2 = 4 TO 14
    RowX1 = RowX1 + 1
    ColumnY1 = 0
    FOR ColumnY2 = 6 TO 21
       ColumnY1 = ColumnY1 + 1
       LOCATE RowX2, ColumnY2, 1
       ' restore color.
       TempZ = Area4(RowX1, ColumnY1)
       VarB = INT(TempZ / 16)
       VarF = TempZ MOD 16
       COLOR VarF, VarB
       ' restore ascii character.
       PRINT CHR$(Area3(RowX1, ColumnY1));
    NEXT
 NEXT
 IF Mouse.Present THEN
    CALL MouseFunction(ShowMouse, 0)
 END IF
 RETURN

' moves menu up, wrapping.
MenuUp:
 GOSUB EraseCurrentMenuSelection
 IF MenuSelect = 1 THEN
    MenuSelect = 9
 ELSE
    MenuSelect = MenuSelect - 1
 END IF
 GOSUB DrawCurrentMenuSelection
 RETURN

' moves menu down, wrapping.
MenuDown:
 GOSUB EraseCurrentMenuSelection
 IF MenuSelect = 9 THEN
    MenuSelect = 1
 ELSE
    MenuSelect = MenuSelect + 1
 END IF
 GOSUB DrawCurrentMenuSelection
 RETURN

' removes hilight from current menu selection.
EraseCurrentMenuSelection:
 IF Mouse.Present THEN
    CALL MouseFunction(HideMouse, 0)
 END IF
 COLOR White, Black
 GOSUB DisplayCurrentMenuSelection
 COLOR White, Black
 IF Mouse.Present THEN
    CALL MouseFunction(ShowMouse, 0)
 END IF
 RETURN

' adds hilight to current menu selection.
DrawCurrentMenuSelection:
 IF Mouse.Present THEN
    CALL MouseFunction(HideMouse, 0)
 END IF
 COLOR White, Blue
 GOSUB DisplayCurrentMenuSelection
 COLOR White, Black
 IF Mouse.Present THEN
    CALL MouseFunction(ShowMouse, 0)
 END IF
 RETURN

' draws current menu selection.
DisplayCurrentMenuSelection:
 LOCATE 4 + MenuSelect, 7, 0
 PRINT MID$(STR$(MenuSelect), 2); " ";
 IF MenuSelect > NumberFiles THEN
    PRINT "<none>      ";
    GOSUB ClearStatus2
    PRINT "View file"; MenuSelect; ": <none>";
 ELSE
    PRINT File(MenuSelect).Filename;
    GOSUB DisplayPath
 END IF
 RETURN

' display path of file being edited.
DisplayPath:
 IF FileLength = False THEN
    RETURN
 END IF
 GOSUB ClearStatus2
 Z$ = RTRIM$(File(MenuSelect).ShortFilename)
 GOSUB Deconcatenate2
 Z$ = MID$(Z$, 3)
 Var = LEN(Z$)
 DO
    Var = Var - 1
    IF Var = 0 THEN
       Z$ = ""
       EXIT DO
    END IF
    IF MID$(Z$, Var, 1) = "\" THEN
       Z$ = LEFT$(Z$, Var)
       EXIT DO
    END IF
 LOOP
 IF Z$ = "" THEN
    Z$ = "\"
 END IF
 PRINT "Viewing path: "; Z$; " ";
 FileDisplay2 = -1
 RETURN

' clear top status area
ClearStatus2:
 COLOR Yellow, Black
 LOCATE 2, 4
 PRINT SPACE$(74);
 LOCATE 2, 4
 RETURN

' deconcatenate long path with \..\ characters
Deconcatenate2:
 DO
    IF LEN(Z$) > 64 THEN
       V1 = INSTR(Z$, "\")
       IF V1 > 0 THEN
          DO
             IF MID$(Z$, V1, 4) = "\..\" THEN
                V1 = INSTR(V1 + 1, Z$, "\")
             ELSE
                EXIT DO
             END IF
          LOOP
          V2 = INSTR(V1 + 1, Z$, "\")
          IF V2 > 0 THEN
             Z$ = LEFT$(Z$, V1) + ".." + MID$(Z$, V2)
          ELSE
             EXIT DO
          END IF
       END IF
    ELSE
       EXIT DO
    END IF
 LOOP
 DO
    V = INSTR(Z$, "\..\..\")
    IF V > 0 THEN
       Z$ = LEFT$(Z$, V - 1) + "\..\" + MID$(Z$, V + 7)
    ELSE
       EXIT DO
    END IF
 LOOP
 IF LEN(Z$) > 64 THEN
    Z$ = LEFT$(Z$, 64)
 END IF
 RETURN

' process left mouse button.
MouseButton1:
 IF Mouse.Row >= 5 AND Mouse.Row <= 13 THEN
    IF Mouse.Column >= 7 AND Mouse.Column <= 20 THEN
       Var = Mouse.Row - 4
       GOSUB RestoreArea
       EXIT SUB
    END IF
 END IF
 RETURN

' process mouse move.
MoveMouse:
 ' check mouse selection boundaries.
 IF Mouse.Row >=5 AND Mouse.Row <= 13 THEN
    IF Mouse.Column >= 7 AND Mouse.Column <= 20 THEN
       IF Mouse.Row - 4 <> MenuSelect THEN
          GOSUB EraseCurrentMenuSelection
          MenuSelect = Mouse.Row - 4
       END IF
       GOSUB DrawCurrentMenuSelection
    END IF
 END IF
 RETURN

TopProgram2:
 EXIT SUB

' critical error trap.
Error.Routine2:
 IF ScreenDrawn THEN
    CLS
 END IF
 ScreenDrawn = False
 COLOR White
 PRINT "Hex Editor Viewfile " + Version + " " + Release + " critical error trap:"
 COLOR Yellow
 ErrorTrap = ERR
 SELECT CASE ERR
 CASE 5
    PRINT "Syntax error." ' should not happen.
 CASE 6
    PRINT "Overflow."
 CASE 9
    PRINT "Subscript out of range."
 CASE 14
    PRINT "Out of string space."
 CASE 24
    PRINT "Device timeout."
 CASE 25
    PRINT "Device fault."
 CASE 27
    PRINT "Out of paper."
 CASE 52
    PRINT "Bad file name or number."
 CASE 53
    PRINT "File not found."
 CASE 54
    PRINT "Bad file mode."
 CASE 55
    PRINT "File already open."
 CASE 57
    PRINT "Device I/O error."
 CASE 58
    PRINT "File already exists."
 CASE 59
    PRINT "Bad record length."
 CASE 61
    PRINT "Disk full."
 CASE 62
    PRINT "Input past eof."
 CASE 63
    PRINT "Bad record length."
 CASE 64
    PRINT "Bad filename."
 CASE 67
    PRINT "Not enough file handles."
 CASE 68
    PRINT "Device unavailable."
 CASE 70
    PRINT "Permission denied."
 CASE 71
    PRINT "Disk not ready."
 CASE 72
    PRINT "Disk-media error."
 CASE 75
    PRINT "Path/File access error."
 CASE 76
    PRINT "Pathname not found."
 CASE 81
    PRINT "Invalid name."
 CASE ELSE
    PRINT "Untrapped error" + STR$(ERR) + "."
 END SELECT
 ' display error prompt.
 COLOR Green
 PRINT "Press R(etry), C(ontinue), Q(uit):";
 ' get keypress.
 DO
    ErrorRespond$ = Nul
    DO
       ErrorRespond$ = INKEY$
       IF LEN(ErrorRespond$) THEN
          EXIT DO
       END IF
    LOOP
    ' parse key.
    SELECT CASE LCASE$(ErrorRespond$)
    CASE "r"
       PRINT "r"
       RESUME TopProgram1
    CASE "c"
       PRINT "c"
       RESUME TopProgram1
    CASE "q"
       PRINT "q"
       RESUME TopProgram2
    END SELECT
 LOOP

' anything goes here stops program.
StopProgram2:
 COLOR Plain
 END
END SUB
