;==============================================================================
;FMARK.ASM - mark a position in memory,
;            above which TSRs will later be cleared by RELEASE
;            this version leaves a minimal size MARK in memory,
;              storing the rest on disk
;            requires a single command line parameter naming
;              the file where the mark will be stored
;
; Syntax:  FMARK [/Q] [d:][path]filename
;==============================================================================
; written for TASM
; by Kim Kokkonen, TurboPower Software
; Copyright (c) 1986,1993 Kim Kokkonen, TurboPower Software.
; May be freely distributed but not sold except by permission.
; telephone: 719-260-6641, Compuserve 76004,2611
;==============================================================================
; version 2.0  6/17/86
;   start at a version number compatible with other TSR utilities
; :
; long intervening history
; :
; version 3.0  9/24/91
;   add Quiet option
; version 3.1  11/4/91
;   no change
; version 3.2  11/22/91
;   change method of accessing high memory
;   store parent len as well as parent segment
; version 3.3  1/8/92
;   detect full disk while writing mark file
;   erase partial mark file if any error occurs during execution
; version 3.4  2/14/92
;   no change
; version 3.5 10/18/93
;   new hybrid method for finding high memory
;==============================================================================
;
Cseg    segment public para
        assume  cs:Cseg, ds:Cseg, es:Cseg
        locals  @@

        org     016H               ;access to parent's PSP
parpsp  label   word

        org     02CH
envseg  label   word               ;access to environment segment

        org     80H
cmdlen  label   byte               ;command line length
        org     81H
cmdlin  label   byte               ;first character of command line

        org     100H

fmark   proc    near

;deallocate environment block of this mark
        push    es
        mov     ax,envseg       ;environment segment
        mov     es,ax           ;  into es
        mov     ah,49H
        int     21H             ;deallocate, no reason for an error to occur
        pop     es

;find first mcb in high memory, if any
        mov     ax,5802h
        int     21H                     ;get umb link status
        or      al,al                   ;high memory already linked?
        jz      @@u1                    ;jump if not

        call    findhidos               ;find mcb using linked chain

        or      ax,ax                   ;valid mcb seg returned?
        jz      @@u9                    ;jump if not
        mov     cx,ax
        jmp     @@9                     ;found first umb

@@u1:   mov     ax,5803H
        mov     bx,1
        int     21H                     ;link umb
        jc      @@u9                    ;jump if failed

        call    findhidos               ;find mcb using linked chain

        push    ax                      ;save result
        mov     ax,5803H
        xor     bx,bx
        int     21H                     ;unlink umb
        pop     ax                      ;restore result

        or      ax,ax                   ;valid mcb seg returned?
        jz      @@u9                    ;jump if not
        mov     cx,ax
        jmp     @@9                     ;found first umb

@@u9:   mov     ax,3000h                ;get DOS version
        int     21H
        cmp     al,3
        jb      @@7                     ;no XMS driver possible
        mov     ax,4300h
        int     2Fh                     ;multiplex call for XMS
        cmp     al,80h                  ;proper signature?
        jne     @@7                     ;no XMS driver
        mov     ax,4310h                ;get XMS control address
        int     2Fh
        mov     xmsxxx,bx               ;save it
        mov     xmsxxx[2],es
        mov     ah,10h
        mov     dx,0FFFFh
        call    dword ptr xmsadr        ;ask to allocate FFFF paras of UMB
        cmp     bl,0B0h                 ;will fail with B0 if UMBs avail
        je      @@0
        cmp     bl,0B1h                 ;will fail with B1 if UMBs all allocated
        jne     @@7                     ;no UMBs exist
@@0:    int     12H
        mov     cl,6
        shl     ax,cl                   ;get segment of top of memory

@@1:    mov     es,ax
        cmp     byte ptr es:[0000h],'M' ;potential mcb?
        jnz     @@6                     ;not an mcb, try next segment
@@2:    mov     cx,ax                   ;save potential start mcb in cx
@@3:    inc     ax
        add     ax,es:[0003h]           ;ax = start of next mcb
        jc      @@5                     ;can't be an mcb if we wrapped
        mov     es,ax                   ;address of next mcb
        mov     dl,es:[0000h]
        cmp     dl,'M'
        jz      @@3                     ;good start mcb
        cmp     dl,'Z'
        jz      @@9                     ;good end mcb
@@5:    mov     ax,cx                   ;restore last start segment
@@6:    cmp     ax,0FFFFh               ;top of memory?
        je      @@7
        inc     ax                      ;try next segment
        jmp     @@1

@@7:    xor     cx,cx                   ;no matching UMB
@@9:    mov     firsthimcb,cx           ;store first high mcb

;parse command line for file name
        push    cs
        pop     es
        mov     si,offset cmdlin ;point to command line
        mov     di,offset filnm  ;point to filename storage string
        cld

get1:   lodsb                    ;get first non-blank
        cmp     al,32            ;skip space
        je      get1
        cmp     al,9             ;skip tab
        je      get1
        cmp     al,13            ;check for end of input
        jne     geto             ;got a non-blank, now get the parameter
        cmp     di,offset filnm  ;filename already specified?
        jne     gotit            ;done if so
        jmp     error            ;no parameter --> error

geto:   cmp     al,'/'           ;check for option
        je      getoc
        cmp     al,'-'
        jne     get2
getoc:  lodsb
        call    upcase
        cmp     al,'Q'
        jnz     operr
        mov     quiet,1          ;set quiet option
        jmp     get1             ;loop around

operr:  mov     dx,offset badopt ;bad option
        jmp     errmsg

get2:   cmp     di,offset filnm  ;filename already specified?
        jne     operr            ;error if so

get2a:  call    upcase           ;upcase char in al
        stosb                    ;store the non-blank character
        lodsb                    ;get next character
        cmp     al,32
        je      get1             ;loop back around for blank
        cmp     al,9
        je      get1             ;loop back around for tab
        cmp     al,13
        je      gotit            ;terminate for <cr>
        jmp     short get2a      ;keep adding characters to filename

;create the specified file
gotit:  mov     al,0
        stosb                    ;terminate ASCIIZ pathname
        mov     dx,offset filnm
        xor     cx,cx            ;normal attribute
        mov     ah,3CH
        int     21H              ;DOS CREAT
        jae     store

        mov     dx,offset badfil ;bad file name
        jmp     errmsg

;store the interrupt vector table
store:  mov     bx,ax           ;keep file handle in bx
        push    ds              ;save ds
        mov     cx,400H         ;1024 bytes to store
        xor     ax,ax
        mov     ds,ax           ;segment 0
        assume  ds:nothing
        mov     dx,ax           ;offset 0
        call    blockwrite      ;write interrupts to file
        assume  ds:cseg

;store the EGA save pointer
egasav: push    ds              ;save ds
        mov     cx,0008H        ;8 bytes to store
        mov     ax,0040H
        mov     ds,ax           ;BIOS data segment
        assume  ds:nothing
        mov     dx,00A8H        ;EGA save table pointer
        call    blockwrite      ;write save pointers to file
        assume  ds:cseg

;store the interapplications communications area
intcom: push    ds              ;save ds
        mov     cx,0010H        ;16 bytes to store
        mov     ax,0040H
        mov     ds,ax           ;BIOS data segment
        assume  ds:nothing
        mov     dx,00F0H        ;interapplications communication area
        call    blockwrite      ;write interapp communications area to file
        assume  ds:cseg

;store the parent's psp
parent: push    ds
        mov     cx,2                    ;2 bytes to store
        mov     dx,offset parpsp        ;point to parent's psp
        call    blockwrite              ;write parent psp to file
        assume  ds:cseg

parlen: push    ds
        mov     ax,ds:[parpsp]
        dec     ax
        mov     ds,ax
        assume  ds:nothing
        mov     dx,3                    ;ds:dx -> parent's length
        mov     cx,2
        call    blockwrite              ;write parent psp to file
        assume  ds:cseg

;determine whether EMS is present
ems:    push    bx              ;temporarily store the file handle
        xor     bx,bx           ;zero the EMS handle count in case EMS not present
        mov     dx,offset emsnm
        mov     ax,3D00H
        int     21H
        jb      sthand          ;EMS driver not installed

;EMS present, close the open "handle" first
        mov     bx,ax           ;EMS handle into bx
        mov     ah,3EH
        int     21H             ;close handle

;get the current EMS page map
        mov     ah,4DH
        mov     di,offset emsmap ;es=cs already
        xor     bx,bx           ;required by some versions of EMM (bug workaround)
        cld                     ;required by some versions of EMM
        int     67H
        or      ah,ah
        jz      sthand          ;result ok
        xor     bx,bx           ;error, return zero EMS handles

;store the number of active handles
sthand: mov     emscnt,bx               ;count of active handles

;write the handle table to disk
        shl     bx,1
        shl     bx,1                    ;4 bytes per handle
        inc     bx
        inc     bx                      ;2 more bytes for the handle count
        mov     cx,bx                   ;number of bytes to write
        pop     bx                      ;get file handle back
        mov     dx,offset emscnt        ;what we're writing
        push    ds
        call    blockwrite              ;write EMS table to file
        assume  ds:cseg

;write the allocated mcb chain
stomcb: push    bx                      ;save file handle
        mov     ah,52H                  ;get first mcb segment
        int     21H
        mov     ax,es:[bx-2]            ;ax=first mcb
        push    cs
        pop     es                      ;es=cs

        mov     di,emscnt               ;get starting address of mcbmap
        shl     di,1
        shl     di,1
        add     di,offset emsmap
        mov     si,di                   ;cs:[si] -> mcbcnt
        add     di,2                    ;cs:[di] -> mcbmap
        xor     cx,cx                   ;cx will count mcbs
        cld
        push    ds
        assume  ds:nothing

mcbnext:stosw                           ;store mcb segment held by ax
        mov     ds,ax                   ;ds:[0] points to mcb
        mov     ax,ds:[1]               ;get mcb owner
        stosw                           ;store it
        inc     cx                      ;increment count
        cmp     byte ptr ds:[0],'Z'     ;end of mcb chain?
        je      mcbdone
        mov     ax,ds                   ;restore ax to mcb segment
        inc     ax                      ;skip over mcb itself
        add     ax,ds:[3]               ;add length of memory block
        jmp     mcbnext

mcbdone:mov     ax,cs:firsthimcb        ;check for high memory
        or      ax,ax
        jz      mcbend
        mov     cs:firsthimcb,0         ;only do it once
        jmp     mcbnext

mcbend: pop     ds
        assume  ds:cseg
        mov     [si],cx                 ;store number of mcbs
        sub     di,si                   ;di=number of bytes in mcb group table
        mov     cx,di                   ;count of bytes in cx
        mov     dx,si                   ;what we're writing (mcbcnt+mcbmap)
        pop     bx                      ;get file handle back
        push    ds
        call    blockwrite              ;write mcb table to file
        assume  ds:cseg

;close up the table file
closfl: mov     ah,3EH
        int     21H                     ;close handle
        jae     idstr                   ;ok, continue

        mov     dx,offset badcls        ;error while closing file
        jmp     delfil

;put a standard ID string into the PSP for RELEASE to check
idstr:  mov     si,offset id
        mov     di,60H                  ;unused area of the PSP
        mov     cx,idlen                ;characters in ID string
        cld
        rep     movsb                   ;copy string

;copy the filename into the command tail
        mov     si,offset filnm
        mov     di,offset cmdlin
        xor     cx,cx
nxtf:   lodsb
        or      al,al
        jz      donf
        inc     cx
        stosb
        jmp     nxtf
donf:   mov     cmdlen,cl

;print message and TSR
gores:  cmp     quiet,0         ;check quiet flag
        jnz     gores1
        mov     dx,offset didit
        mov     ah,9
        int     21H             ;write success message including filename
        mov     dx,offset crlf  ;newline
        mov     ah,9
        int     21H

gores1: xor     dx,dx           ;get number of paragraphs to keep
        mov     dl,cmdlen       ;length of command line
        add     dx,0090H        ;rest of PSP plus paragraph margin
        mov     cl,4
        shr     dx,cl           ;convert to paragraphs
        mov     ax,3100H
        int     21H             ;terminate but stay resident

;close mark file, delete it, show error message
errclo: mov     ah,3EH
        int     21H                     ;close handle, ignore error

;delete mark file and show error message
        assume  ds:cseg
delfil: push    dx
        mov     dx,offset filnm
        mov     ah,41H
        int     21H
        pop     dx

;error output - show syntax line
;dx has offset of error message
errmsg: mov     ah,9
        int     21H
error:
        mov     dx,offset syntax
        mov     ah,9
        int     21H
        mov     ax,4C01H
        int     21H

fmark   endp

;blockwrite assumes that ds was pushed before the call
blockwrite proc near
        mov     ah,40H
        int     21H             ;write block to file
        pop     dx              ;get return address
        pop     ds              ;get ds back
        assume  ds:cseg
        push    dx              ;return address back on stack
        mov     dx,offset nowrit
        jc      errclo          ;jump if error during write
        cmp     ax,cx
        jne     errclo          ;jump if not all bytes written
        ret
blockwrite endp

upcase  proc    near
        cmp     al,'a'
        jb      noup
        cmp     al,'z'
        ja      noup
        and     al,0DFH          ;uppercase
noup:   ret
upcase  endp

findhidos proc near
;returns ax = first high mcb, or 0 if none found
        mov     ah,52H                  ;get first mcb segment
        int     21H
        mov     ax,es:[bx-2]            ;ax=first mcb

        push    ds
        assume  ds:nothing

@@1:    cmp     ax,9FFFH                ;above 640K?
        ja      @@2                     ;jump if so
        mov     ds,ax                   ;ds:[0] points to mcb
        xor     ax,ax                   ;set ax to zero in case we exit here
        cmp     byte ptr ds:[0],'Z'     ;end of mcb chain?
        je      @@2                     ;exit if so
        mov     ax,ds                   ;restore ax to mcb segment
        inc     ax                      ;skip over mcb itself
        add     ax,ds:[3]               ;add length of memory block
        jmp     @@1

@@2:    pop     ds                      ;ds = cs
        assume  ds:cseg
        ret
findhidos endp

emsnm   db      'EMMXXXX0',0    ;file name for testing EMS presence
id      db      'FM3.5 TSR'     ;id string for RELEASE check
idlen   equ     $-id

;messages
badfil  db      13,10,'Could not open file for writing',36
nowrit  db      13,10,'Error while writing',36
badcls  db      13,10,'Error closing table file',36
badopt  db      13,10,'Bad command line option',36
syntax  db      13,10,'Syntax:  FMARK [/Q] [d:][path]filename'
crlf    db      13,10,36
didit   db      'FMARK 3.5, Copyright 1993 TurboPower Software',13,10
didit2  db      'Marked current memory position in '

;data storage area
filnm   db      50H dup (36)    ;mark file name
quiet   db      0               ;quiet flag
xmsadr  label   dword           ;XMS control address
xmsxxx  dw      2 dup (0)
firsthimcb dw   0               ;segment of first mcb in high mem
emscnt  dw      0               ;holds number of active pages in map that follows
emsmap  =       $               ;EMS page map (up to 1024 bytes)
mcbcnt  =       emsmap+400H     ;number of allocated mcbs
mcbmap  =       mcbcnt+2        ;MCB map (up to 1024 bytes)

Cseg    ends
        end    Fmark
