;MEM.ASM
code    segment
        assume  cs:code,ds:code
        org     100h
start:  jmp     begin
        db      5 dup ('stack   ')
stack_area      dw 0

topline db      ' CURSOR LOCATION: 00000  CONTENTS: 00',32,32,32
help?   db      '(? FOR HELP)  '
find?   db      'FIND:',32,32,32,32,32,32,32,32,32 ;because I'm using Qedit...
jump?   db      'JUMP TO:',32,32,32,32,32,32
search  db      'SEARCHING... '
escmsg  db      0,'Press ESC to QUIT',0,0
        db      0,'Arrow keys - move cursor',32,32,32,32,32,32,32,32,32,32,32,32
        db      '(& Ctrl-Left Arrow & Ctrl-Right Arrow)',0
        db      0,'Home - left end of line',32,32,32,32,32,32,32,32,32,32,32,32,32
        db      'End - right end of line',0
        db      0,'Ctrl-Home - top of screen',32,32,32,32,32,32,32,32,32,32,32
        db      'Ctrl-End - bottom of screen',0
        db      0,'Ctrl-PgUp - beginning of memory',32,32,32,32,32
        db      'Ctrl-PgDn - end of memory',0
        db      0,'PgUp - previous screen',32,32,32,32,32,32,32,32,32,32,32,32,32,32
        db      'PgDn - next screen',0
        db      0,'Scroll Lock ON to scroll screen with Arrow Keys',0
        db      0,'Ctrl-F - Find string of characters',0
        db      0,'Ctrl-L - Continue to search for string',0
        db      0,'Ctrl-J - Jump to hexadecimal memory address',0
        db      0,0,'PRESS ANY KEY TO RETURN...',0

        db      'MEM.COM (c)1987 by Doug Cox '
findstr db      32,32,32,32,32,32,32,32
cursave dw      0
saves   dw      0
strcnt  dw      0
oldcur  dw      0
chars   dw      0
curptr  dw      0
toplef  dw      0
mode    db      0

begin:  lea     ax,stack_area
        mov     sp,ax
        mov     ah,0fh          ;function to check screen mode
        int     10h
        cmp     al,0bh          ;indicates display mode
        jb      notega          ;if monochrome or CGA
        jmp     exit
notega: mov     mode,al         ;save it

        mov     ax,0b000h       ;video memory address
        cmp     mode,7
        je      mono            ;if monochrome display
        add     ax,800h         ;if CGA display
mono:   mov     es,ax
;save old cursor location
        call    getcur
        dec     dh
        mov     oldcur,dx       ;row in dh / col in dl
;save old screen
        mov     di,0
        lea     si,oldscr
oldlp0: mov     cx,80           ;columns
oldlp:  mov     al,es:[di]
        mov     ds:[si],al
        inc     di
        inc     di
        inc     si
        loop    oldlp
        dec     dh              ;rows
        jnz     oldlp0

        mov     ah,0            ;function to set screen mode
        mov     al,2            ;code for 25x80 B&W
        int     10h             ;clear screen

        mov     ah,1            ;function to change cursor shape
        mov     cx,7            ;top & bottom row of cursor
        int     10h

;put top line on screen
        lea     si,topline
        mov     di,0            ;location on screen
        mov     cx,53           ;for count
        mov     ah,70           ;for screen attribute
        call    movit           ;move [si] into [di] & inc di cx times

;put memory contents on screen
memtop: mov     toplef,0        ;memory location (& data segment)
        mov     curptr,0        ;cursor offset location in memory
        mov     dx,0100h        ;row 1/column 0
        call    movcur          ;initialize cursor at top left
newscrn:mov     ah,7            ;screen attribute
        mov     si,0            ;offset of memory to put on screen
        mov     di,160          ;top left of screen
        cmp     toplef,0ff8ch   ;last possible toplef
        jnc     atend           ;to not write past 0fffffh
        mov     cx,1920         ;80*24 screen
        mov     ds,toplef       ;toplef is also data segment beginning
        call    movit           ;lodsb, stosw, loop
new2:   mov     dx,cs
        mov     ds,dx           ;put data seg back

;put cursor location & contents at screen top
locont: mov     dx,toplef       ;data segment
        mov     bx,dx           ;for first binihex call
        mov     cl,4
        shl     dx,cl
        add     dx,curptr       ;offset addr
        jnc     locont2
        add     bx,1000h        ;increment 5th byte
locont2:mov     di,36           ;location on screen
        mov     ch,1            ;for count
        call    binihex         ;show 5th byte of location
        mov     bx,dx
        mov     ch,4
        call    binihex         ;show other 4 bytes of location

        mov     bx,curptr       ;cursor location in memory
        mov     ds,toplef
        mov     bh,[bx]         ;contents
        mov     cx,cs
        mov     ds,cx
        mov     di,70           ;screen loc
        mov     ch,2            ;2 chars
        call    binihex
;read keyboard
wt:   mov ah,0        ;function to read keyboard
        int     16h             ;program stays here, mostly
        cmp     al,0
        je      extended_code

        call    case            ;to jump to following subroutines
        db      ctrls/3
diff2   equ     $
        db      10      ;^J
        dw      jump
        db      6       ;^F
        dw      find
        db      12      ;^L
        dw      again
        db      3fh     ;'?'
        dw      emenu
        db      1bh     ;ESC
        dw      exit
ctrls   equ     $-diff2
        dw      wt

;if at last possible screen of memory
atend:  mov     dx,toplef
        mov     ds,dx           ;don't change ds before getting ds:[toplef]
        mov     cl,4
        shl     dx,cl           ;rotate left 1 byte
        mov     cx,0ffffh
        sub     cx,dx
        inc     cx
        mov     dx,1920         ;80*24
        sub     dx,cx
        call    movit           ;put memory on screen, but put blanks past ffffh
        mov     cx,dx
        mov     ax,0700h        ;attribute & blank
        rep     stosw
        jmp     new2

extended_code:
        mov     al,ah
        call    case
        db      extends/3       ;number of routines to check
diff1   equ     $
        db      71      ;Home
        dw      lftside
        db      79      ;End
        dw      rtside
        db      73      ;PgUp
        dw      prev
        db      81      ;PgDn
        dw      next
        db      72      ;Up arrow
        dw      curup
        db      80      ;Down arrow
        dw      curdn
        db      75      ;Left arrow
        dw      curlft
        db      77      ;Right arrow
        dw      currt
        db      116     ;Ctrl-Right arrow
        dw      movrt
        db      115     ;Ctrl-Left arrow
        dw      movlft
        db      132     ;Ctrl-PgUp
        dw      memtop
        db      118     ;Ctrl-PgDn
        dw      goend
        db      119     ;Ctrl-Home
        dw      scrtop
        db      117     ;Ctrl-End
        dw      scrbot
extends equ     $-diff1
        dw      wt    ;go there if not one of above


;'?'
emenu:  call    savcur
        lea     si,escmsg
        call    menwrt
        cmp     al,1bh          ;ESC
        je      jmpexit
        cmp     al,3            ;^C
        je      jmpexit
        mov     dx,cursave
        call    movcur
        jmp     newscrn

jmpexit:jmp     exit

;Ctrl-Home
scrtop: call    getcur          ;row in dh & col in dl
        mov     al,80
        mul     dh
        sub     curptr,ax
        add     curptr,80       ;because top row here is 1 not 0
        mov     dh,1            ;top row
        call    movcur
        jmp     locont

;Ctrl-End
scrbot: call    getcur
        mov     al,24           ;bottom row
        sub     al,dh           ;cursor row
        mov     ah,80
        mul     ah
        add     curptr,ax
;now test for end of memory
        mov     ax,toplef
        mov     cl,4
        shl     ax,cl
        add     ax,curptr
        jnc     scrbot2         ;if not past 0fffffh
        call    curend
        jmp     locont

scrbot2:mov     dh,24           ;bottom row
        call    movcur
        jmp     locont

;Ctrl-PgDn
goend:  mov     toplef,0ff8ch   ;last possible toplef
        mov     curptr,1855     ;80*23+15
        mov     dx,180fh        ;row 24 col 15
        call    movcur
        jmp     newscrn

;Home
lftside:call    getcur
        push    dx
        mov     dh,0
        sub     curptr,dx       ;decrement curptr to 1st col
        pop     dx
        mov     dl,0
        call    movcur
        jmp     locont

;End
rtside: mov     ax,toplef
        cmp     ax,0ff8ch       ;last screen in memory
        jc      rtside2
        mov     cl,4
        shl     ax,cl
        add     ax,curptr
        cmp     ax,0fff0h       ;beginning of last line in memory
        jc      rtside2
        call    curend
        jmp     locont

rtside2:call    getcur
        mov     al,79
        sub     al,dl
        mov     ah,0
        add     curptr,ax
        mov     dl,79
        call    movcur
        jmp     locont

;PgUp
prev:   cmp     toplef,0
        je      jmpwait
        cmp     toplef,78h      ;1920d shifted right (24*80) screen
        jnc     prev2           ;if not within 24*80 of beginning of memory
        jmp     memtop
prev2:  sub     toplef,78h
gonew:  jmp     newscrn

;PgDn
next:   mov     dx,toplef
        cmp     dx,0ff8ch       ;last possible toplef
        jnc     jmpwait         ;if at end of memory
        add     dx,78h          ;1920d shifted right
        mov     toplef,dx
        mov     cl,4
        shl     dx,cl
        add     dx,curptr
        jnc     gonew           ;if cursor won't be past 0fffffh
        call    curend
        jmp     newscrn

curjmp: jmp     locont

;Left Arrow
curlft: call    lftcur
        jnc     curjmp
        jmp     newscrn

;Right Arrow
currt:  call    rtcur
        jnc     curjmp
        jmp     newscrn

;Ctrl-Right Arrow
movrt:  mov     cx,10
rtlp:   push    cx
        call    rtcur
        pop     cx
        loop    rtlp
        jmp     newscrn

;Ctrl-Left Arrow
movlft: mov     cx,10
lftlp:  push    cx
        call    lftcur
        pop     cx
        loop    lftlp
        jmp     newscrn

jmpwait:jmp     wt

;Up Arrow
curup:  call    scrlock
        jnz     up              ;if ScrollLock is ON
        call    getcur
        cmp     dh,1
        je      curup2          ;if on top line
        dec     dh
        call    movcur
        sub     curptr,80
        jmp     locont

curup2: cmp     toplef,0
        jz      jmpwait
        sub     toplef,5
        jmp     newscrn

;ScrollLock ON
up:     cmp     toplef,0
        je      jmpwait         ;if at beginning of memory
        sub     toplef,5
        call    getcur
        cmp     dh,24
        je      downend         ;If at bottom
        inc     dh
        call    movcur
        add     curptr,80       ;because of toplef change
        jmp     newscrn

jmpwait2:jmp    wt

;Down Arrow
curdn:  call    scrlock
        jnz     down            ;if ScrollLock is ON
        cmp     toplef,0ff8ch
        jc      curdn3          ;if not at last screen in memory
        mov     ax,toplef
        mov     cl,4
        shl     ax,cl
        add     ax,curptr
        add     ax,80
        jc      jmpwait2        ;if move would put cursor past 0fffffh
curdn3: call    getcur
        cmp     dh,24
        je      curdn2          ;if on bottom line
        inc     dh
        call    movcur
        add     curptr,80
        jmp     locont

curdn2: add     toplef,5
        cmp     toplef,0ff8ch
        jc      downend         ;if not at end of memory
        call    curend
        jmp     newscrn         ;don't need to increment curptr

;ScrollLock ON
down:   cmp     toplef,0ff8ch
        jnc     jmpwait2        ;if at end of memory
        add     toplef,5        ;80 shifted right
        call    getcur
        cmp     dh,1
        je      downend         ;if cursor is on top line
        dec     dh
        call    movcur
        sub     curptr,80       ;because of toplef change
downend:jmp     newscrn

;jump to input hex address
jump:   call    savcur
        lea     si,jump?
        call    top
        mov     bp,96           ;for screen location in getstr
        call    getstr          ;get input
        cmp     dx,0            ;input string length
        jnz     jumpsk
        jmp     find15          ;if no input
jumpsk: mov     si,di           ;just after last input char on screen
        mov     ax,0
        mov     di,ax           ;for offset address
        mov     bx,ax           ;for segment address
        mov     dx,0004h        ;dh for cl & dl for loop
;get offset address
jumplp: dec     si
        dec     si
        cmp     si,96           ;input screen location
        jl      jumpxit         ;if no good input
        mov     al,es:[si]
        call    hex2bin         ;convert to binary
        jc      jumplp          ;if not hexadecimal
        mov     cl,dh
        mov     ah,0            ;rol changes it
        add     dh,4            ;to rol ax to next nibble to left
        rol     ax,cl
        or      di,ax           ;ta daa!
        dec     dl
        jnz     jumplp
;get segment address
        dec     si
        dec     si
        mov     al,es:[si]
        call    hex2bin
        jc      jumpxit
        mov     ah,0
        or      bx,ax
jumpxit:mov     saves,es
        jmp     find16          ;to jump to address


find:   call    savcur
        lea     si,find?
        call    top
        mov     bp,90
        call    getstr
;put string in findstr
        cmp     dx,0
        jne     find12
        jmp     find15          ;if no input
find12: mov     si,90
        lea     di,findstr
        mov     cx,8
find5:  mov     al,es:[si]
        mov     ds:[di],al
        inc     si
        inc     si
        inc     di
        loop    find5
;write 'SEARCHING...'
        lea     si,search
        mov     cx,12
        mov     di,108
        mov     ah,7
        call    movit
;search for string
        mov     di,0            ;beginning of memory offset
        mov     bx,0
        mov     saves,es
        mov     strcnt,dx       ;save it for again:
        mov     cx,0ffffh       ;for scasb count
;jumped to from again:
find14: mov     es,bx           ;beginning of memory segment
        dec     dx              ;because cmpsb doesn't look at 1st char
        jnz     find10
;if only one char to find
        mov     al,findstr
        repnz   scasb
        jmp     short find11
;subroutine to increment segment
find9:  mov     di,0
        mov     cx,0ffffh
        add     bx,1000h
        mov     es,bx           ;to next segment
        jc      find7           ;if past last segment
;big find loop
find10: lea     si,findstr
        mov     al,[si]
        repnz   scasb           ;repeat until z=true or cx=0
        jnz     find9           ;if string not found in current segment (cx=0)
        mov     bp,di           ;save it
        push    cx
        mov     cx,dx           ;for cmpsb count
        inc     si              ;point to remainder of string
        repz    cmpsb           ;compare strings (repeat until z=false or cx=0)
        pop     cx              ;get scasb count back
        mov     di,bp           ;get it back
        jnz     find10          ;if string not found
;jump to address
find11: dec     di              ;offset addr
        mov     cl,4
        rol     bx,cl           ;move segment addr to right-most byte
        and     bx,0fh          ;clear all but right-most byte
find16: mov     dx,bx           ;for div by word
        mov     ax,di
        mov     cx,80
        div     cx              ;remainder is in dx (& dl = column)
        mov     curptr,dx       ;column (dh=0)
        sub     di,curptr       ;to get toplef
        jnc     find13
        dec     bx
find13: mov     cl,4
        shr     di,cl
        mov     cl,4
        ror     bx,cl           ;back to previous shape
        add     di,bx
        mov     toplef,di       ;ta-daa!
        mov     dh,1            ;put cursor on first row
        mov     cursave,dx      ;for movcur
;clear message & exit
find7:  mov     es,saves
find15: mov     ax,700h
        mov     di,108
        mov     cx,12
        rep     stosw
;put '? FOR HELP' back
        lea     si,help?
        call    top
        mov     dx,cursave
        call    movcur
        jmp     newscrn

;look for string again
again:  call    savcur
        lea     si,search
        mov     cx,12
        mov     di,108
        mov     ah,7
        call    movit
        mov     dx,strcnt
        mov     saves,es
        mov     di,toplef
        mov     bx,di
        mov     cl,4
        rol     di,cl
        and     di,0fff0h       ;clear right byte
        add     di,curptr       ;offset addr
        inc     di              ;start looking 1 byte beyond current location
        jnc     againsk
        add     bx,1000h
againsk:and     bx,0f000h       ;clear all but left byte (for seg addr)
        mov     cx,0ffffh
        sub     cx,di           ;for count to end of current segment
        jmp     find14


;return to DOS
exit:   mov     bh,0            ;page 0
        mov     ah,bh           ;function to set screen mode
        mov     al,mode
        int     10h
;restore old cursor location
        mov     dx,oldcur       ;old cursor location
        dec     dh
        call    movcur
;restore old screen
        inc     dh
        mov     di,0
        lea     si,oldscr
exitlp: mov     cx,80           ;columns
        mov     ah,7
        call    movit           ;move [si] into [di] & inc di cx times
        dec     dh              ;rows
        jnz     exitlp
        int     20h             ;return to DOS

;***CALLED ROUTINES***

top     proc    near
        mov     cx,14
        mov     di,78
        mov     ah,70
        call    movit
        ret
top     endp

movit   proc    near
movlp:  lodsb
        stosw
        loop    movlp
        ret
movit   endp

;save cursor location
savcur  proc    near
        call    getcur
        mov     cursave,dx
        mov     dx,1900h        ;row 25,col 0
        jmp     short movcur
savcur  endp

;move cursor to row in dh & col in dl
movcur  proc    near
        mov     ah,2
        jmp     short cur
movcur  endp

;put row in dh & col in dl
getcur  proc    near
        mov     ah,3
cur:    mov     bh,0
        int     10h
        ret
getcur  endp

;to jump to a subroutine (semi-trick)
case    proc    near
        pop     bx      ;RET address
        mov     cl,[bx] ;number of comparisons to make
        mov     ch,0
caslop: inc     bx      ;to a db
        cmp     al,[bx]
        je      go
        inc     bx      ;to a dw
        inc     bx
        loop    caslop
go:     inc     bx      ;to a dw
        mov     bx,[bx]
        jmp     bx
case    endp

;to change binary to hex
binihex proc    near
binlp:  mov     cl,4
        rol     bx,cl           ;move left byte to right side
        mov     al,bl           ;this is necessary
        and     al,0fh          ;clear left byte
        add     al,30h
        cmp     al,3ah
        jl      num
        add     al,7
num:    stosb
        inc     di
        dec     ch
        jnz     binlp
        ret
binihex endp

;convert al from hex to binary
hex2bin proc    near
        cmp     al,'0'
        jl      badhex
        cmp     al,'9'
        jle     hex2            ;if number
        and     al,0dfh         ;make letters uppercase
        cmp     al,'A'
        jl      badhex
        cmp     al,'F'
        jg      badhex
        sub     al,7
hex2:   sub     al,30h
        clc
        ret
badhex: stc
        ret
hex2bin endp

;to put cursor at 0fffffh
curend  proc    near
        mov     dx,toplef
        mov     cl,4
        shl     dx,cl
        mov     ax,0ffffh
        sub     ax,dx
        mov     curptr,ax
        mov     cl,80
        div     cl
        inc     al
        mov     dh,al           ;quotient (answer) (for row)
        mov     dl,ah           ;remainder (for column)
        jmp     short movcur
curend  endp

lftcur  proc    near
        call    getcur
        cmp     dx,0100h
        je      lfttop           ;if at top left of screen
        dec     curptr
        cmp     dl,0
        je      prevlin
        dec     dl
        jmp     short lftmov
prevlin:dec     dh
        mov     dl,79
lftmov: call    movcur
        clc
lftxit: ret
lftcur  endp

lfttop: cmp     toplef,0
        je      lftxit          ;if at beginning of memory
        mov     dl,79
        call    movcur
        sub     toplef,5        ;80d shifted right
        add     curptr,79       ;because of toplef change
        stc
        ret

rtcur   proc    near
        cmp     toplef,0ff8ch
        jc      rtcur2          ;if not at last screen
        mov     ax,toplef
        mov     cl,4
        shl     ax,cl
        inc     ax
        add     ax,curptr
        jc      rtexit          ;if cursor would go beyond 0fffffh
rtcur2: inc     curptr
        call    getcur
        cmp     dl,79
        je      nexlin
        inc     dl
        jmp     short rtmov
nexlin: cmp     dh,24
        je      rtbot
        inc     dh
        mov     dl,0
rtmov:  call    movcur
        clc
rtexit: ret
rtcur   endp

rtbot:  cmp     toplef,0ff8ch   ;last toplef in memory
        jnc     rtexit
        mov     dl,0
        call    movcur
        add     toplef,5
        sub     curptr,80       ;because of toplef change
        stc
        ret

scrlock proc    near            ;check ScrollLock on or off
        push    es
        mov     ax,0
        mov     es,ax
        test    byte ptr es:[417h],00010000b
        pop     es
        ret
scrlock endp

;get string to search for
getstr  proc    near
        mov     dx,0            ;for count in cmpsb, below
        mov     di,bp
find2:  mov     ah,0            ;read keyboard function
        int     16h
        cmp     ah,14           ;Backspace key
        je      find20
        cmp     ah,83           ;Del key
        je      find20
        cmp     ah,75           ;Left Arrow key
        je      find20
        cmp     al,8
        jne     find3           ;if no erase input
;erase char
find20: cmp     di,bp
        je      find2           ;don't erase if at beginning of input location
        dec     di
        dec     di
        dec     dx
        mov     word ptr es:[di],4620h  ;attribute & space
        jmp     short find2
;put char on screen
find3:  cmp     di,106          ;last input space
        je      find4           ;exit
        cmp     al,0dh          ;Enter key
        je      find4           ;exit
        stosb
        inc     di
        inc     dx
        jmp     short find2
find4:  ret
getstr  endp

menwrt  proc    near
        mov     di,160
        mov     ax,0
        mov     cx,5
        rep     stosw
        mov     ah,7            ;for attribute
        mov     dx,24           ;rows
menlp0: mov     cx,80           ;columns
menlp:  lodsb
        cmp     al,0
        je      mensk
        stosw
        loop    menlp
mensk:  rep     stosw
        cmp     si,0
        jne     mensk2
        add     di,160
mensk2: dec     dx
        jnz     menlp0
        mov     ah,0
        int     16h             ;read keyboard
        ret
menwrt  endp

oldscr  equ     $
code    ends
        end     start
