;******************************************************************************
;
;   (C) Copyright IBM Corp. 1989-1993
;
;   Module:   HIGHLOAD.INC - Code used to prepare for LoadHigh or DeviceHigh
;
;   Date:     May 14, 1992
;
;******************************************************************************
;
;   Modification log:
;
;     DATE    WHO      DESCRIPTION
;   --------  -------  ----------------------------------------------
;   05/14/92  t-richj  Original
;   06/21/92  t-richj  Final revisions before check-in
;
;******************************************************************************
;
; This file contains routines needed to parse and implement user-given
; command-line options of the form "/S/L:3,0x500;2;7,127;0x0BE4".  InitVar()
; and Parsevar() are used to parse this data and place it in encoded form into
; the variables in highvar.inc, for use by the rest of the routines.
;
; DeviceHigh accepts this command-line (handled in sysconf.asm, not here):
;    DEVICEHIGH SIZE=hhhhhh module opts
; Or, DeviceHigh and LoadHigh accept any of the following:
;    DH/LH module opts
;    DH/LH [/S][/L:umb[,size][;umb[,size]]*] module opts
;    DH/LH [/L:umb[,size][;umb[,size]]*][/S] module opts
; The initial UMB,SIZE pair designates the module's load address; the remainder
; of the UMB and SIZE pairs are used to indicate specific UMBs to be left
; available during the load.
;
; When an actual load is ready to be performed, a call to HideUMBs() will
; temporarily allocate (as owner 8+"HIDDEN  ") all free elements in any
; upper-memory block which was not specified by the user... in addition, if
; UMBs were marked to shrink (/S option) to a certain size ("umb,size"), any
; elements in that umb SAVE the lower-half of the newly-shrunken one are also
; allocated.  After the load, the function UnHideUMBs() (in highexit.inc) will
; free any UMBs so allocated.
;
; When a device driver loads, there is the additional problem of allocating its
; initial load site; this should be restricted to the first UMB specified on
; the command-line.  The function FreezeUM temporarily allocates all remaining
; free upper-memory elements (as owner 8+"FROZEN  "), except those in the load
; UMB.  Then the initial allocation may be made, and a call to UnFreeze will
; return any so-allocated memory elements to FREE, for the true load.  Note
; that UnFreeze leaves HIDDEN elements allocated; it only frees FROZEN ones.
;
;******************************************************************************
;
;   Public:
;
;___PROCEDURES_________________________________________________________________
;
;   AddrToUmb   - converts a segment address in AX to its appropriate UMB #
;   BigFree     - makes ES:0 point to the largest free MCB in UMB given as AL
;   FixMem      - scans the UM chain and concatenates adjacent free MCBs
;   FreezeUM    - Marks FROZEN all UM elements now FREE, save those in load UMB
;   GetLoadSize - Returns the load UMB minimum size (0 if not specified)
;   GetLoadUMB  - Returns the load UMB number in AL (-1 if not specified)
;   GetSize     - Returns the UMB in AL's minimum size (0 if not specified)
;   GetXNum     - reads a 32-bit ASCII number at ES:SI and returns it in DX:AX
;   HideUMBs    - links UMBs and hides upper-memory as appropriate
;   InitVar     - initializes all the variables used in ParseVar and HideUMBs
;   NextMCB     - moves an MCB pointer forward to the next MCB
;   ParseVar    - parses [/S][/L:umb[,size][;umb[,size]]*] and builds the table
;   PrTable     - produces a printout of the variables in highvar.inc
;   StoLoadSize - Overrides the load UMB minimum size with what's in AX
;   StoLoadUMB  - Overrides the load UMB number with what's in AL
;   UmbHead     - returns in AX the address of the first UMB block (0x9FFF)
;   UnFreeze    - Marks FROZEN elements as FREE
;
;___VARIABLES__________________________________________________________________
;
;   gnradix     - After a call to GetXNum, is 16 or 10, depending on the # read
;
;   Internal:
;___PROCEDURES_________________________________________________________________
;
;   convUMB     - checks after GetXNum to convert an address to a UMB number
;   findUMB     - makes ES:0 point to the first MCB in UMB given as AL
;   fm_link     - links UMBs not already linked in
;   fm_unlink   - unlinks UMBs if fm_umb is set to 0
;   frezMCB     - marks as 8+FROZEN the MCB at ES:0
;   hideMCB     - marks as HIDDEN the MCB at ES:0
;   hideUMB     - marks as HIDDEN all FREE elements in UMB passed as AL
;   hideUMB?    - hides as appropriate the UMB in CL
;   hl_unlink   - unlinks UMBs if fm_umb is set to 0; restores strategy too
;   incArgc     - increments fm_argc, for use with LH command-line parsing
;   isEOL       - returns with ZF set iff AL contains CR or LF, or 0
;   isFreeMCB   - returns with ZF set if current MCB (ES:0) is FREE
;   isFrozMCB   - returns with ZF set if current MCB (ES:0) is FROZEN
;   isSpecified - sets ZF if UMB in AL wasn't specified in DH/LH line.
;   isSysMCB    - sets ZF iff ES points to an MCB owned by "SC" + (8 or 9)
;   isTiny      - returns with ZF set if user didn't specify /S
;   isWhite     - returns with ZF set iff AL contains whitespace (or "=")
;   loadLow     - returns AL==0 if UMB0 == 0, else AL==1
;   mul32       - multiplies the number in DX:AX by gnradix
;   parseL      - parses ":nnnn[,nnnn][;nnnn[,nnnn]]*" for ParseVar
;   setUMBs     - links umbs and sets allocation strategy for a load
;   shrinkMCB   - breaks an MCB into two pieces, the lowest one's size==AX
;   stowSiz     - marks a given UMB as having a given minimum size
;   stowUMB     - marks a given UMB as used, if it hasn't been so marked before
;   toDigit     - converts a character-digit to its binary counterpart
;   toPara      - divides DX:AX by 16; result in AX only
;   toUpper     - accepts one argument (probly a register), and upper-cases it.
;   unHideMCB   - marks as FREE the MCB at ES:0
;   unMarkUMB   - marks a given UMB as unused, even if previously marked used
;
;******************************************************************************

SWTCH   equ     '/'             ; Switch character

DOS_CHECK_STRATEGY  equ 5800h   ; Int 21h, Func 58h, Svc 0 = check alloc strat
DOS_SET_STRATEGY    equ 5801h   ; Int 21h, Func 58h, Svc 1 = set alloc strategy
DOS_CHECK_UMBLINK   equ 5802h   ; Int 21h, Func 58h, Svc 2 = check link state
DOS_SET_UMBLINK     equ 5803h   ; Int 21h, Func 58h, Svc 3 = set link state
DOS_GET_DOS_LISTS   equ   52h   ; Int 21h, Func 52h = return list of lists
DOS_UMB_HEAD        equ   8Ch   ; Offset from ES (after func52h) to get UMBHead

CR      equ     0Dh             ; Carriage Return
LF      equ     0Ah             ; Line Feed
TAB     equ     09h             ; Tab character (^I)

; -----------------------------------------------------------------------------
; This file needs arena.inc, but it should already have been included.  Because
; of MASM's strange method of two-pass definitions, we can't just include it
; inside of an ifndef, or it won't make it to stage two.  Odd odd odd.
; -----------------------------------------------------------------------------

; -----------------------------------------------------------------------------
;*** toUpper - accepts one argument (probly a register), and upper-cases it.
;    BUGBUG: t-richj 6/3/92 - This method, though used elsewhere (for example,
;            used in sysinit2.asm to upper-case config.sys), relies on ASCII
;            conventions.  Might be bad.
; -----------------------------------------------------------------------------

toUpper macro   arg
        and     arg, 0DFh
        endm

; -----------------------------------------------------------------------------
;*** NextMCB - moves an MCB pointer forward to the next MCB
; -----------------------------------------------------------------------------
; ENTRY:       First arg - seg pointer (ES?); second, a scratch register (AX?)
; EXIT:        Seg pointer moved forward, scratch register = distance moved
; ERROR EXIT:  None
; USES:        Flags, arguments
; -----------------------------------------------------------------------------

NextMCB macro   seg, reg
        mov     reg, seg
        add     reg, seg:[arena_size]
        inc     reg                     ; Inc 1 paragraph for the header
        mov     seg, reg
        endm

; -----------------------------------------------------------------------------
;*** InitVar - initializes all the variables used in ParseVar and HideUMBs
; -----------------------------------------------------------------------------
; ENTRY:       None
; EXIT:        Variables listed in highvar.inc are initialized
; ERROR EXIT:  None
; USES:        Flags, variables in highvar.inc
; -----------------------------------------------------------------------------
; Note that element 0 references UMB 0 (conventional), not UMB 1.  Its contents
; are largely ignored, but it is initialized nonetheless.
; -----------------------------------------------------------------------------

        public  InitVar

InitVar proc    near
        pushreg <ax, cx, di, es>
        dataseg es                      ;Point ES into appropriate data segment

        xor     ax, ax
        mov     fUmbTiny, al            ;Shrink UMBs? (made 1 if /S given)
        mov     fInHigh,  al            ;Set to 1 when DH/LH has been called
        mov     SegLoad,  ax            ;Load Address (seg), used for DH only
        mov     UmbLoad, UNSPECIFIED    ;Later is the # of the 1st spec'd UMB
        mov     fm_argc,  al            ;Start with zero args having been read

        cld

        mov     cx, MAXUMB              ;For each entry
        mov     di, offset ES:UmbUsed   ;on the UmbUsed array,
        rep     stosb                   ;       Store 0

        mov     cx, MAXUMB              ;Okay... for each entry
        mov     di, offset ES:UmbSize   ;on the UmbSize array,
        rep     stosw                   ;       Store 0

        normseg es              ; Return ES

        popreg  <es, di, cx, ax>
        ret

InitVar endp

; -----------------------------------------------------------------------------
;*** FixMem - scans the upper memory chain and concatenates adjacent free MCBs
; -----------------------------------------------------------------------------
; ENTRY   : None
; EXIT    : None
; ERROR   : None
; USES    : Flags, fm_umb, fm_strat
; -----------------------------------------------------------------------------

        public  FixMem

FixMem  proc    near

        pushreg <ax, bx, cx, dx, es>

        call    fm_link         ; Link in UMBs

        call    UmbHead         ; Get first upper-memory MCB address (0x9FFF)
        jc      fmX             ; (if couldn't get it, leave now).

        mov     es, ax          ; It returns in AX, so move it to ES.

;
; - Walk MCB Chain ------------------------------------------------------------
;

        xor     dx, dx          ; We're keeping the address of the last MCB
        mov     cx, dx          ; in CX... and the last owner
        inc     dx              ; in dx as we go through the loop:

; ------------------------------------------
; FM10--DX  = last MCB's owner's PSP address
;       CX  = last MCB's address (segment)
; ------------------------------------------

fm10:   mov     al, es:[arena_signature]        ; if 'Z', don't repeat loop
        mov     bx, es:[arena_owner]            ; if not zero, do nothing
        or      bx, dx                          ; dx was owner of previous MCB
        jnz     fm30                            ; If not both zero, don't cat.

        ; - Coalesce memory blocks at ES:00 and CX:00 -------------------------

fm20:   mov     bx, es:[arena_size]             ; Grab this block's Size,
        mov     es, cx                          ; Go back to prev MCB's address
        mov     es:[arena_signature], al        ; & move the SECOND sig here

        add     bx, es:[arena_size]             ; Size += first MCB's size
        add     bx, 1h                          ; And add one for the header
        mov     es:[arena_size], bx             ; Write the size

        ; ---------------------------------------------------------------------

fm30:   mov     cx, es                  ; Put this address on the stack
        mov     dx, es:[arena_owner]    ; And remember its owner

        NextMCB es, bx                  ; Move to the next MCB

        cmp     al, arena_signature_end
        jnz     fm10                    ; If signature != 'Z', there are more.

fmX:    call    fm_unlink               ; Unlink UMBs

        popreg  <es, dx, cx, bx, ax>
        ret

FixMem  endp

; -----------------------------------------------------------------------------
;*** fm_link - links UMBs not already linked in
; -----------------------------------------------------------------------------
; ENTRY:    None
; EXIT:     fm_umb == 0 if not linked in previously, 1 if already linked in
; ERROR:    None
; USES:     AX, BX, fm_umb
; -----------------------------------------------------------------------------

fm_link proc    near
        mov     ax, DOS_CHECK_UMBLINK
        int     21h                     ; Current link-state is now in al

        putdata fm_umb, al              ; So store it in fm_umb for later

        mov     ax, DOS_SET_UMBLINK
        mov     bx, 1
        int     21h

        ret
fm_link endp

; -----------------------------------------------------------------------------
;*** fm_unlink - unlinks UMBs if fm_umb is set to 0
; -----------------------------------------------------------------------------
; ENTRY:    fm_umb == 1 : leave linked, else unlink
; EXIT:     None
; ERROR:    None
; USES:     AX, BX
; -----------------------------------------------------------------------------

fm_unlink       proc    near
        xor     bx, bx
        getdata bl, fm_umb              ; fm_umb already has the old link-state
        mov     ax, DOS_SET_UMBLINK
        int     21h                     ; so just use that, and call int 21h
        ret
fm_unlink       endp

; -----------------------------------------------------------------------------
;*** ParseVar - parses [/S][/L:umb[,size][;umb[,size]]*] and builds the table
; laid out in highvar.inc
; -----------------------------------------------------------------------------
; ENTRY:    ES:SI points to command tail of LoadHigh/DeviceHigh (whitespace ok)
; EXIT:     ES:SI points to first character in child program name
; ERROR:    ES:SI points to character which caused error, carry set, AX == code
; USES:     ES:SI, AX, flags, variables in highvar.inc
; -----------------------------------------------------------------------------
; Error codes (in AX if carry set on return):
;
PV_InvArg       equ     1       ; Invalid argument passed
PV_BadUMB       equ     2       ; Bad UMB number passed (duplicate?)
PV_InvSwt       equ     3       ; Unrecognized switch passed
;
; This routine exects ES:SI to point to a string much like the following:
;    "/S/L:1,200;2 module options"
; Optionally, the string can begin with whitespace; neither /S nor /L is
; required, though that's what this routine is supposed to parse.
;
optS            equ     'S'     ; /S
optL            equ     'L'     ; /L:...
;

        public  ParseVar

ParseVar        proc    near
        pushreg <di, ds, es>

        push    es              ; Make DS:SI point to it, as well as ES:SI
        pop     ds              ; (regardless if we're in devhigh or loadhigh)
        cld

; ------------------------------------------------
; PV10--ES:SI = any whitespace on the command-line
; ------------------------------------------------

pv10:   lodsb                   ; here, ES:SI=="  /L..."--must eat whitespace
        call    isWhite
        jz      pv10            ;       ES:SI==" /L..."--keep eating.
        cmp     al, SWTCH
        jz      pv20            ;       ES:SI=="/L..."--go process a switch

        dec     si              ; Backup--it's now "odule options", and we need
        clc                     ; that "m" we just read (or whatever it is).
        jmp short pvX           ; Then return with carry clear == we're done.

pv20:   lodsb                   ; Just read 'S' or 'L', hopefully
        toUpper al              ; So we make it upper-case, and...
        cmp     al, optS        ; just read 'S'?
        jnz     pv30

        call    incArgc         ; If it's /S, it's another arg for LH to skip.

        putdata fUmbTiny, 1     ; /S, so ES:SI=="  /L..." or " module opts", or
        jmp short pv10          ; possibly even "/L...".

pv30:   cmp     al, optL        ; If it's not 'L' either, then 'tis a bad
        jnz     pvE1            ; switch!

        call    incArgc         ; If it's /L, it's another arg for LH to skip.

        call    parseL
        jnc     pv10            ; If no carry, go back and look for more

        dec     si              ; Else, back up and exit.
        jmp short pvErr         ; AX has already been set by parseL

pvE1:   mov     ax, PV_InvSwt   ; Unrecognized switch passed
pvErr:  dec     si
        dec     si
        stc
pvX:    popreg  <es, ds, di>
        ret
ParseVar        endp

; -----------------------------------------------------------------------------
;*** parseL - parses ":nnnn[,nnnn][;nnnn[,nnnn]]*" for ParseVar
; -----------------------------------------------------------------------------
; ENTRY:    ES:SI points to colon
; EXIT:     ES:SI points to first character not parsed
; ERROR:    Carry set; rewind three characters and return (see ParseVar)
; USES:     ES:SI, flags, AX, CX, DX, variables in highvar.inc
; -----------------------------------------------------------------------------
; If the string here is terminated with anything other than whitespace or a
; switchchar (perhaps it's /S or another /L:... ), then we return with carry
; set, indicating that they've screwed up the syntax.  The 3-character rewind
; makes sure the app /L: is reported as being the culprit.
; -----------------------------------------------------------------------------

parseL  proc    near

        lodsb
        cmp     al, ':'         ; Make sure they did /L:
        jnz     plE1            ; If they didn't, return with carry set.

; ------------------------------------------
; PL10--ES:SI = a UMB number, after /L: or ;
; ------------------------------------------

pl10:   call    GetXNum         ; After this, 'tis ",size" or ";umb" or " mod"
        jc      plE2            ; And error if it's a bad number.
        call    convUMB         ; Convert any address to a UMB number

        mov     cl, al          ; Remember the UMB number
        call    stowUMB         ; Mark this UMB # as used;
        jc      plE2            ; If it was already marked, it'll error

        call    incArgc         ; Each UMB number is another arg for LH to skip

        lodsb
        cmp     al, ';'         ; Did "umb;" ?
        jz      pl10            ; Yep: go back and get another UMB.

        call    isWhite         ; Did "umb " ?
        jz      plX             ; Yep: return (it'll go back to whitespace)

        call    isEOL           ; Did "umb" ?
        jz      plSwX           ; If so, backup and exit like everything's ok

        cmp     al, SWTCH       ; Did "umb/" ? (as in, "/L:1,100;2/S")
        jz      plSwX           ; If so, back up ES:SI one character and return

        cmp     al, ','         ; Did "umb," ?
        jnz     plE1            ; Just what the heck DID they do? Return error.

; --- Read a size -------------------------------------------------------------

        call    GetXNum         ; Stop on "size;" or "size " or anything else
        jc      plE1            ; And error if it's a bad size.

        call    toPara          ; Convert from bytes to paragraphs

        call    stowSiz         ; CL still has the UMB number for this routine

        call    incArgc         ; Each UMB size is another arg for LH to skip

        lodsb
        cmp     al, ';'         ; They did "umb,size;", so get another UMB.
        jz      pl10            ;

        call    isWhite         ; Did it end with whitespace?
        jz      plX             ; If so, we're done here--go back.

        call    isEOL           ; Did they do "umb,size" and end??? (stupid)
        jz      plSwX           ; If so, backup and exit like everything's ok

        cmp     al, SWTCH       ; Did they do "umb,size/" ?
        jz      plSwX           ; If so, again, we're done here.

plE1:   mov     ax, PV_InvArg   ; If not, we don't know WHAT they did...
        dec     si
        stc
        ret

plE2:   mov     ax, PV_BadUMB   ; In this case, they've specified a UMB twice
        stc
        ret

plSwX:  dec     si              ; If we hit a '/' character, back up one char
                                ; so the whitespace checker will see it too.

plX:    clc                     ; Then just return with carry clear, so
        ret                     ; ParseVar will go about its business.
parseL  endp

; -----------------------------------------------------------------------------
;*** incArgc - increments fm_argc, for use with LoadHigh command-line parsing
; -----------------------------------------------------------------------------
; ENTRY:    None
; EXIT:     None
; ERROR:    None
; USES:     fm_argc, flags
; -----------------------------------------------------------------------------

incArgc proc    near
        push    ax

        getdata al, fm_argc     ; Obtain previous value of fm_argc,
        inc     al              ; Increment it,
        putdata fm_argc, al     ; And store it right back.

        pop     ax
        ret
incArgc endp

; -----------------------------------------------------------------------------
;*** isEOL - returns with ZF set iff AL contains CR or LF, or 0
; -----------------------------------------------------------------------------
; ENTRY:    AL contains character to test
; EXIT:     ZF set iff AL contains CR or LF, or 0
; ERROR:    None
; USES:     ZF
; -----------------------------------------------------------------------------

isEOL   proc    near
        cmp     al, 0           ; Null-terminator
        jz      ieX
        cmp     al, CR          ; Carriage Return
        jz      ieX
        cmp     al, LF          ; LineFeed
ieX:    ret
isEOL   endp

; -----------------------------------------------------------------------------
;*** isWhite - returns with ZF set iff AL contains whitespace (or "=")
; -----------------------------------------------------------------------------
; ENTRY:    AL contains character to test
; EXIT:     ZF set iff AL contains space, tab, or equals
; ERROR:    None
; USES:     ZF
; -----------------------------------------------------------------------------

isWhite proc    near
        cmp     al, ' '         ; Space
        jz      iwX
        cmp     al, '='         ; Equals (treat as whitespace)
        jz      iwX
        cmp     al, TAB         ; Tab
iwX:    ret
isWhite endp

; -----------------------------------------------------------------------------
;*** unMarkUMB - marks a given UMB as unused, even if previously marked used
; -----------------------------------------------------------------------------
; ENTRY:    AL contains UMB number
; EXIT:     None
; ERROR:    None
; USES:     Flags, variables in highvar.inc
; -----------------------------------------------------------------------------

unMarkUMB       proc    near
        pushreg <ax, bx, di, es>
        dataseg es

        xor     ah, ah
        mov     bx, ax
        mov     es:UmbUsed[bx], 0
        cmp     UmbLoad, al
        jnz     umu10

        mov     UmbLoad, 0      ; If unmarked the load UMB, load into convent.

umu10:  popreg  <es, di, bx, ax>
        normseg es
        ret
unMarkUMB       endp

; -----------------------------------------------------------------------------
;*** stowUMB - marks a given UMB as used, if it hasn't been so marked before
;            -- accepts a UMB # in AL, and makes sure it hasn't yet been
; listed in the /L:... chain.  If it's the first one specified, it sets UmbLoad
; to that UMB #... and in any case, it marks the UMB as specified.
; -----------------------------------------------------------------------------
; ENTRY:    AL contains UMB number, as specified by the user
; EXIT:     None
; ERROR:    Carry set if UMB # is less than 0 or >= MAXUMB (see highvar.inc)
; USES:     AX, Flags, variables in highvar.inc
; -----------------------------------------------------------------------------

stowUMB proc    near
        cmp     al, MAXUMB
        jb      su10
        stc
        ret                     ; Ooops-- UMB>=MAXUMB

su10:   pushreg <bx, di, si, ds, es>

        dataseg es              ; Point ES into appropriate data segment
        dataseg ds              ; Point DS into appropriate data segment

        cmp     UmbLoad, UNSPECIFIED    ; If this, we haven't been here before
        jne     su20
        mov     UmbLoad, al     ; So remember this UMB as the load UMB slot.

su20:   or      al, al          ; If they gave UMB 0, there's really nothing
        jz      su30            ; that we should do here.

        mov     bl, al
        xor     bh, bh
        mov     ax, 1           ; Now, AX = 1, and BX = UMB Number

        xchg    ES:UmbUsed[bx], al

        or      al, al          ; If it was already 1, then al==1... and that
        jz      su30            ; means an error.

        stc                     ; OOOPS!  This one's been used before.  :(

su30:   popreg  <es, ds, si, di, bx>
        normseg ds
        normseg es
        ret
stowumb endp

; -----------------------------------------------------------------------------
;*** stowSiz - marks a given UMB as having a given minimum size
; -----------------------------------------------------------------------------
; ENTRY:    CL contains UMB number, AX contains size
; EXIT:     None
; ERROR:    None
; USES:     AX, DX, Flags, variables in highvar.inc
; -----------------------------------------------------------------------------

stowSiz proc    near
        pushreg <bx, di, es>
        dataseg es                      ; Point ES into appropriate data seg

        mov     bl, cl                  ; Now bl==UMB number, AX==size
        mov     bh, 0                   ;     bx==UMB number, AX==size
        shl     bl, 1                   ;     bx==offset into array, AX=size
        mov     es:UmbSize[bx], ax      ; Store the size

        popreg  <es, di, bx>
        normseg es                      ; Return ES to where it was
        ret
stowSiz endp

; -----------------------------------------------------------------------------
;*** toDigit - converts a character-digit to its binary counterpart
;            -- verifies that CL contains a valid character-digit; if so, it
; changes CL to its counterpart binary digit ((CL-'0') or (CL-'A'+10)).  A-F
; are considered valid iff gnradix is 16.
; -----------------------------------------------------------------------------
; ENTRY:    CL contains a digit ('0' to '9' or, if gnradix==16, 'A' to 'F')
; EXIT:     CL contains digit in binary (0 to 9 or, if gnradix==16, 0 to 15)
; ERROR:    Carry set indicates invalid digit; carry clear indicates good digit
; USES:     CL, Flags
; -----------------------------------------------------------------------------
; If the string is preceeded with "0x", the value is read as hexadecimal; else,
; as decimal.  After a read, you may check the radix by examining gnradix--it
; will be 10 or 16.
; -----------------------------------------------------------------------------

public  gnradix
gnradix dw      ?               ; Must be a word--16x16 multiplication

toDigit proc    near
        cmp     gnradix, 16
        jnz     td20            ; Don't check hex digits if radix isn't 16

        cmp     cl, 'a'
        jb      td10
        cmp     cl, 'f'
        ja      tdE             ; Nothing valid above 'z' at all...
        sub     cl, 'a'-10      ; Make 'a'==10 and return.
;       clc                     ; <- CLC is implicit from last SUB
        ret

td10:   cmp     cl, 'A'
        jb      td20            ; Below 'A'?  Not a letter...
        cmp     cl, 'F'
        ja      tdE             ; Above 'F'?  Not a digit.
        sub     cl, 'A'-10      ; Make 'A'==10 and return.
;       clc                     ; <- CLC is implicit from last SUB
        ret

td20:   cmp     cl, '0'         ; If less than zero,
        jb      tdE             ; Done.
        cmp     cl, '9'         ; Or, if greater than nine,
        ja      tdE             ; Done.
        sub     cl, '0'         ; Okay--make '0'==0 and return.
;       clc                     ; <- CLC is implicit from last SUB
        ret

tdE:    stc
        ret
todigit endp

; -----------------------------------------------------------------------------
;*** GetXNum - reads a 32-bit ASCII number at ES:SI and returns it in DX:AX
; -----------------------------------------------------------------------------
; ENTRY:    ES:SI points to an ascii string to scan
; EXIT:     ES:SI moved to first invalid digit, DX:AX contains value read
; ERROR:    Carry set if # is too big, or has no digits (EOL possibly)
; USES:     ES:SI, DX, AX, Flags, gnradix
; -----------------------------------------------------------------------------
; If the string is preceeded with "0x", the value is read as hexadecimal; else,
; as decimal.  After a read, you may check the radix by examining gnradix--it
; will be 10 or 16.
; -----------------------------------------------------------------------------

        public  GetXNum

GetXNum proc    near
        pushreg <bx, cx, ds>

        cld
        xor     ax, ax
        xor     bx, bx
        xor     cx, cx
        xor     dx, dx                  ; Start with 0 (makes sense)

        mov     gnradix, 10             ; And default to a radix of 10 (dec)

        mov     cl, byte ptr es:[si]    ; Now AX=0, BX=0, CH=0/CL=char, DX=0
        call    toDigit
        jc      gxnE                    ; If it's not a digit, leave now.

        or      cl, cl
        jnz     gxn20                   ; Doesn't have '0x'
        mov     cl, byte ptr es:[si+1]
        cmp     cl, 'x'                 ; Either 'x'...
        jz      gxn10
        cmp     cl, 'X'                 ; ...or 'X' means it's hexadecimal
        jnz     gxn20

gxn10:  mov     gnradix, 16
        inc     si                      ; Since we read "0x", march over it.
        inc     si

; ------------------------------------------------------
; GXN20--ES:SI = a digit in a number; if not, we're done
;        DX:AX = current total
;        BX    = 0
;        CH    = 0
; ------------------------------------------------------

gxn20:  mov     cl, byte ptr es:[si]    ; Now DX:AX=current total, CH=0/CL=char
        inc     si

        call    toDigit         ; Accepts only valid digits, A-F -> 10-16
        jc      gxnQ            ; <- Ah... wasn't a digit.  Stop.

        call    mul32           ; Multiply DX:AX by gnradix
        jc      gxnX            ; (if it's too big, error out)

        add     ax, cx          ; Add the digit
        adc     dx, bx          ; (BX is 0!)--Adds 1 iff last add wrapped
        jc      gxnX            ; If _that_ wrapped, it's too big.
        jmp short gxn20

gxnE:   stc                     ; In this case, we need to set the carry
        jmp short gxnX          ; and leave--there were no digits given.

gxnQ:   dec     si              ; Don't read in the offensive character.
        clc                     ; And clear carry, so they know it's okay.

gxnX:   popreg  <ds, cx, bx>
        ret
GetXNum endp

; -----------------------------------------------------------------------------
;*** mul32 - multiplies the number in DX:AX by gnradix
; -----------------------------------------------------------------------------
; ENTRY:   DX:AX = the number to be multiplied, BX = 0, gnradix = multiplier
; EXIT:    DX:AX has been multiplied by gnradix if carry clear; BX still 0
; ERROR:   Carry set if number was too large
; USES:    Flags, AX, DX
; -----------------------------------------------------------------------------

mul32   proc    near
        push    ax              ; DX=old:hi, AX=old:lo, TOS=old:lo, BX=0
        mov     ax, dx          ; DX=old:hi, AX=old:hi, TOS=old:lo, BX=0
        mul     gnradix         ; DX=?,      AX=new:hi, TOS=old:lo, BX=0
        jc      m32E            ; Too big?

        mov     dx, ax          ; DX=new:hi, AX=new:hi, TOS=old:lo, BX=0
        pop     ax              ; DX=new:hi, AX=old:lo, TOS=orig,   BX=0

        xchg    dx, bx          ; DX=0,      AX=old:lo, TOS=orig,   BX=new:hi
        mul     gnradix         ; DX=carry,  AX=new:lo, TOS=orig,   BX=new:hi
        xchg    dx, bx          ; DX=new:hi, AX=new:lo, TOS=orig,   BX=carry
        add     dx, bx          ; DX=new:hi, AX=new:lo, TOS=orig,   BX=carry
        xor     bx, bx          ; DX=new:hi, AX=new:lo, TOS=orig,   BX=0
        ret

m32E:   pop     ax
        ret
mul32   endp

; -----------------------------------------------------------------------------
;*** toPara - divides DX:AX by 16; result in AX only (discards extra DX data)
; -----------------------------------------------------------------------------
; ENTRY:   DX:AX = the number to be divided
; EXIT:    Interpereting DX:AX as bytes, AX=paragraph equivalent, 0xFFFF max
; ERROR:   None
; USES:    Flags, AX, DX
; -----------------------------------------------------------------------------
; Note: The 386 has a 32-bit SHR, which would work perfectly for this... but we
;       can't ensure a 386 host machine.  Sorry.
; -----------------------------------------------------------------------------

toPara  proc    near
        push    cx              ; DX:AX=HHHH hhhh hhhh hhhh:LLLL llll llll llll

        mov     cl, 4           ;
        shr     ax, cl          ; DX:AX=HHHH hhhh hhhh hhhh:0000 LLLL llll llll
        xchg    ax, dx          ; DX:AX=0000 LLLL llll llll:HHHH hhhh hhhh hhhh
        mov     cl, 12
        shl     ax, cl          ; DX:AX=0000 LLLL llll llll:hhhh 0000 0000 0000
        or      ax, dx          ;    AX=hhhh LLLL llll llll

        pop     cx
        ret
toPara  endp

ifdef DEBUG_PRTABLE
; -----------------------------------------------------------------------------
;*** PrTable - produces a printout of the variables in highvar.inc
;            -- The data are reported in this format:
;
;       ShrinkUMBs? (01/00)     LoadAddr (DH only)      UMB0 (load UMB number)
;       00      conv_used?      conv_size (ignored)
;       01      umb1_used?      umb1_size (00==not specified)
;       02      umb2_used?      umb2_size
;               ...
;       0F      umb15_used?     umb15_size
;
; -----------------------------------------------------------------------------
; ENTRY/EXIT : Nothing
; USES:        Flags, AX
; -----------------------------------------------------------------------------
; This routine is for debugging only, and should not be included in the final
; versions of loadhigh and devicehigh (if it's called, you'll know).  To use
; it, you'll need to add "include wordout.inc" if it's not available already.
; -----------------------------------------------------------------------------

        public  PrTable

PrTable proc    near
        pushreg <cx, si, ds>
        dataseg ds                      ; Point DS into appropriate data seg

        mov     al, fUmbTiny
        call    byteout
        printtab
        mov     ax, SegLoad
        call    wordout
        printtab
        mov     al, UmbLoad
        call    byteout
        printtab
        mov     al, fInHigh
        call    byteout
        printcr
        printcr

        xor     cx,  cx                 ; For each entry

; ----------------------------
; PT10--CL = index into arrays
; ----------------------------

pt10:   mov     al, cl
        call    byteout
        printtab

        mov     si, offset DS:UmbUsed   ; on the UmbUsed array,
        mov     ax, cx
        add     ax, si
        mov     si, ax
        lodsb                           ; Get used element
        call    byteout
        printtab

        mov     si, offset DS:UmbSize   ; on the UmbSize array,
        mov     ax, cx
        shl     ax,  1                  ; ax *= 2 (two bytes per element)
        add     ax, si
        mov     si, ax
        lodsw                           ; Get size element
        call    wordout
        printcr

        inc     cx
        cmp     cx, MAXUMB
        jb      pt10

        popreg  <ds, si, cx>
        normseg ds                      ; Return DS
        ret
PrTable endp

endif   ; DEBUG_PRTABLE

; -----------------------------------------------------------------------------
;*** UmbHead - returns in AX the address of the first UMB block (0x9FFF)
; -----------------------------------------------------------------------------
; ENTRY:  Nothing
; EXIT:   AX contains 0x9FFF for most systems
; ERROR:  Carry set if pointer is 0xFFFF (if not set up yet--DH runs into this)
; USES:   Flags, AX
; -----------------------------------------------------------------------------
; Early in the boot-cycle, the pointer used to obtain this value isn't set up;
; to be precise, before a UMB provider is around.  In this event, the pointer
; is always set to 0xFFFF; it changes once a provider is around.  On most
; machines (all of 'em I've seen), it changes to 0x9FFF at that point.
; -----------------------------------------------------------------------------

        public  UmbHead

UmbHead proc    near
        pushreg <si, ds, es>

        mov     ah, DOS_GET_DOS_LISTS   ; Call int 21h, function 52h...
        int     21h

        mov     ax, es:[DOS_UMB_HEAD]   ; And read what's in ES:[008C]
        cmp     ax, 0FFFFh
        jz      uhE                     ; If it's 0xFFFF, it's an error...

        clc                             ; Else, it isn't (CLC done by prev cmp)
        jmp short uhX

uhE:    stc
uhX:    popreg  <es, ds, si>
        ret
UmbHead endp

; -----------------------------------------------------------------------------
;*** isSysMCB - sets ZF iff ES points to an MCB owned by "SC" + (8 or 9)
; -----------------------------------------------------------------------------
; ENTRY:  ES:0 should point to a valid MCB
; EXIT:   ZF set if owned by SC+8 or SC+9 (for japan)
; USES:   Flags
; -----------------------------------------------------------------------------

isSysMCB        proc    near
        push    ax

        mov     ax, es:[arena_owner]    ; Check the owner...
        cmp     ax, SystemPSPOwner      ; 8 (for US OR Japan) is valid
        jz      ism10
        cmp     ax, JapanPSPOwner       ; 9 (for Japan) is valid
        jz      ism10
        jmp short ismX                  ; Anything else isn't.

ism10:  mov     ax, word ptr es:[arena_name]    ; Check the name...
        cmp     ax, 'CS'

ismX:   pop     ax
        ret
isSysMCB        endp

; -----------------------------------------------------------------------------
;*** AddrToUmb - converts a segment address in AX to its appropriate UMB number
; -----------------------------------------------------------------------------
; ENTRY:  AX contains a segment address
; EXIT:   AX will contain the UMB number which contains the address (0==conv)
; ERROR:  If the address is above UM Range, AX will return as FFFF.
; USES:   Flags, AX
; -----------------------------------------------------------------------------
; An address in the following areas is treated as:
;    0      <-> umbhead (0x9FFF)          = Conventional memory
;    0x9FFF <-> addr of first UM sys MCB  = UMB #1
;      ...
;    addr of last UM sys MCB <-> TOM      = invalid; returns #0xFFFF
; -----------------------------------------------------------------------------

AddrToUmb       proc    near
        pushreg <cx, dx, es>

        mov     dx, ax          ; DX = address to search for

        call    UmbHead         ; AX = first segment
        jc      atuE            ; If it couldn't get it, error out.

        mov     es, ax          ; ES = first UMB segment
        xor     cx, cx          ; Pretend we're on UMB 0 for now... (cx = UMB#)

; ----------------------------------------
; ATU10--ES - Current MCB address
;        DX - Address given for conversion
;        CX - Current UMB #
; ----------------------------------------

atu10:  mov     ax, es
        cmp     ax, dx          ; Present segment >= given segment?
        jae     atuX            ; Yep--done.

        call    isSysMCB        ; Returns with ZF set if this is a system MCB
        jnz     atu20

        inc     cx              ; If it _was_ a system MCB, we're in a new UMB.

atu20:  mov     al, es:[arena_signature]
        cmp     al, arena_signature_end
        jz      atu30           ; 'Z' means this was the last MCB... that's it.

        NextMCB es, ax

        jmp short atu10

; -----------------------------------------------------------------------------
; if we get to atu30, they specified a number that was past the last MCB.
; make sure it's not _inside_ that MCB before we return an error condition.
; -----------------------------------------------------------------------------

atu30:  mov     ax, es
        add     ax, es:[arena_size]
        cmp     ax, dx          ; Present >= given?
        jae     atuX            ; Yep! It _was_ inside.

atuE:   xor     cx, cx          ; Else, fall through with UMB # == -1
        dec     cx              ; (that makes it return 0xFFFF and sets CF)

atuX:   mov     ax, cx          ; Return the UMB number in AX
        popreg  <es, dx, cx>
        ret
AddrToUmb       endp

; -----------------------------------------------------------------------------
;*** convUMB - checks after GetXNum to convert an address to a UMB number
;            -- if GetXNum read a hex number, we interperete that as a segment
; address rather than a UMB number... and use that address to look up a UMB.
; This routine checks for that condition and calls AddrToUmb if necessary.
; -----------------------------------------------------------------------------
; ENTRY:  AX contains a UMB number or segment, gnradix has been set by GetXNum
; EXIT:   AX will contain a UMB number
; ERROR:  None
; USES:   Flags, AX
; -----------------------------------------------------------------------------

convUMB proc    near
        cmp     gnradix, 16
        jnz     cu10            ; If it didn't read in hex, it's not an address
        call    AddrToUmb       ; Else, convert the address to a UMB number
        cmp     ax, 0FFFFh
        jnz     cu10
        inc     ax              ; If too high, ignore it (make it conventional)
cu10:   ret
convUMB endp

; -----------------------------------------------------------------------------
;*** setUMBs - links umbs and sets allocation strategy for a load
;            -- if LoadHigh, the allocation strategy MAY be LOW_FIRST instead
; of the usual HIGH_FIRST.  See the code.
; -----------------------------------------------------------------------------
; ENTRY:  None
; EXIT:   None
; ERROR:  None
; USES:   Flags, fm_umb, fm_strat
; -----------------------------------------------------------------------------

setUMBs proc    near
        pushreg <ax, bx>

        call    fm_link

ifdef HV_LoadHigh
        mov     ax, DOS_CHECK_STRATEGY
        int     21h

        putdata fm_strat, al    ; Store the current strategy for later restore

        and     ax, 007Fh       ; 0000.0000.0111.1111 == All that other stuff
        push    ax              ; Watch this carefully...

        call    loadLow         ; returns al==0 if load low, al==1 if loadhigh
        ror     al, 1           ; Shift that to al==0 or al==0x80

        pop     bx              ; ...pushed as AX above
        or      bl, al          ; Now we have 0000.0000.?111.1111 in BX;

        mov     ax, DOS_SET_STRATEGY ; with ? ==1 if load highfirst.  Perfect!

        int     21h
endif

        popreg  <bx, ax>
        ret
setUMBs endp

; -----------------------------------------------------------------------------
;*** loadLow - returns AL==0 if UMB0 == 0, else AL==1
; -----------------------------------------------------------------------------
; ENTRY:  None
; EXIT:   AL==0 if mem strategy should be set to LOW_FIRST, else AL==1
;         Carry set if UMB0 not specified (_NOT_ an error)
; ERROR:  None
; USES:   Flags, fm_strat, fm_umb
; -----------------------------------------------------------------------------
; We want to set the memory strategy to LOW_FIRST if the user specified a
; load UMB, and it is 0.  That 0 can be either from the user having _specified_
; zero (/L:0;...), or from having specified a too-big min size (/L:1,99999999)
; such that the load UMB is too small, and shouldn't be used.
; -----------------------------------------------------------------------------


loadLow proc    near
        push    ds
        dataseg ds              ; Point DS into appropriate data segment

        mov     al, UmbLoad
        cmp     al, UNSPECIFIED
        jnz     ll10

        mov     al, 1           ; Return with AL==1 && STC if no UMBs specified
        stc
        jmp short llX

ll10:   or      al, al          ; AL=the load UMB: Is it == 0?
        jz      llX             ; Yep... CF==0 (from OR) && AL=0, so just exit

        mov     al, 1
        clc

llX:    pop     ds              ; Return DS to where it was
        normseg ds              ;
        ret
loadLow endp

; -----------------------------------------------------------------------------
;*** HideUMBs - links UMBs and hides upper-memory as appropriate
; -----------------------------------------------------------------------------
; ENTRY:  None
; EXIT:   None
; ERROR:  None
; USES:   Flags, fm_strat, fm_umb
; -----------------------------------------------------------------------------

        public  HideUMBs

HideUMBs        proc
        pushreg <ax, cx, ds, es>

        call    UmbTest         ; See if we REALLY linked in anything...
        jc      husX            ; ...if not, there's nothing for us to do.

        call    FixMem          ; Concatenate adjacent free MCBs in upper mem
        call    setUMBs         ; Link UMBs and set memory-allocation strategy

        putdata fInHigh, 1      ; Remember that we're now running high

        call    GetLoadUMB      ; See if they gave us a list to leave free
        cmp     al, UNSPECIFIED ; If they didn't,
        jz      husX            ; then we shouldn't do this loop:

        xor     cx, cx

; -----------------------------------------------
; HUS10-CX - UMB number (after inc, 1==first UMB)
; -----------------------------------------------

hus10:  inc     cx              ; For each UMB:
        cmp     cx, MaxUMB
        jae     hus20

        mov     al, cl          ; (stopping as soon as we're outside of the
        push    es
        call    findumb         ; valid range of UMBs)
        pop     es              ; push/pop: trash what findumb finds.  :-)
        jc      hus20

        call    hideUMB?        ; hide what we need to hide.

        jmp short hus10

hus20:  call    GetLoadUMB      ; Now check if they offered /L:0
        or      al, al          ; --Is the load UMB 0? (-1==unspecified)
        jnz     husX            ; If not, we're done.

        call    hl_unlink       ; If so, however, fix UMBs and strategy.

husX:   popreg  <es, ds, cx, ax>
        ret
HideUMBs        endp

; -----------------------------------------------------------------------------
;*** GetLoadUMB - Returns the load UMB number in AL (-1 if not specified)
; -----------------------------------------------------------------------------
; ENTRY:  None
; EXIT:   AL == load UMB
; ERROR:  None
; USES:   Flags, AX
; -----------------------------------------------------------------------------

        public  GetLoadUMB

GetLoadUMB      proc    near
        getdata al, UmbLoad
        ret
GetLoadUMB      endp

; -----------------------------------------------------------------------------
;*** GetLoadSize - Returns the load UMB minimum size (0 if not specified)
; -----------------------------------------------------------------------------
; ENTRY:  None
; EXIT:   AX == load UMB minimum size
; ERROR:  None
; USES:   Flags, AX
; -----------------------------------------------------------------------------

        public  GetLoadSize

GetLoadSize     proc    near
        pushreg <bx, si, ds>
        dataseg ds

        mov     al, UmbLoad

        xor     ah, ah                  ;    ax==UMB
        mov     bx, offset DS:UmbSize   ;                     bx==array
        shl     al, 1                   ;    ax==offset
        add     ax, bx                  ;    ax==element index
        mov     si, ax                  ; ds:si==element index

        lodsw                           ;    ax==size

        popreg  <ds, si, bx>
        normseg ds
        ret
GetLoadSize     endp

; -----------------------------------------------------------------------------
;*** GetSize - Returns the UMB in AL's minimum size (0 if not specified)
; -----------------------------------------------------------------------------
; ENTRY:  AL == a UMB number
; EXIT:   AX == UMB minimum size, as specified by the user
; ERROR:  None
; USES:   Flags, AX
; -----------------------------------------------------------------------------

GetSize proc    near
        pushreg <bx, si, ds>
        dataseg ds

        xor     ah, ah                  ;    ax==UMB
        mov     bx, offset DS:UmbSize   ;                     bx==array
        shl     al, 1                   ;    ax==offset
        add     ax, bx                  ;    ax==element index
        mov     si, ax                  ; ds:si==element index

        lodsw                           ;    ax==size

        popreg  <ds, si, bx>
        normseg ds
        ret
GetSize endp

; -----------------------------------------------------------------------------
;*** StoLoadUMB - Overrides the load UMB number with what's in AL
; -----------------------------------------------------------------------------
; ENTRY:   AL == new load UMB
; EXIT:    None
; ERROR:   None
; USES:    Flags, AX
; -----------------------------------------------------------------------------
; CAUTION: Should only be used if /L:... was used.  Logically, that is the only
;          time you would ever need this, so that's okay.
; -----------------------------------------------------------------------------

        public  StoLoadUMB

StoLoadUMB      proc    near
        putdata UmbLoad, al
        ret
StoLoadUMB      endp

; -----------------------------------------------------------------------------
;*** StoLoadSize - Overrides the load UMB minimum size with what's in AX
; -----------------------------------------------------------------------------
; ENTRY:  AL == new load size
; EXIT:   None
; ERROR:  None
; USES:   Flags, AX
; -----------------------------------------------------------------------------

        public  StoLoadSize

StoLoadSize     proc
        push    dx

        getdata dl, UmbLoad             ; Put UMB# in DL and size in AX
        cmp     dl, UNSPECIFIED
        jz      sls10

        call    stowsiz                 ; We've got a function to do just this

sls10:  pop     dx
        ret
StoLoadSize     endp

; -----------------------------------------------------------------------------
;*** hideUMB - marks as HIDDEN all FREE elements in UMB passed as AL
; -----------------------------------------------------------------------------
; ENTRY:    AL must indicate a valid UMB; 0==conv && is invalid.
; EXIT:     None; free elements in UMB marked as hidden
; ERROR:    None
; USES:     Flags
; -----------------------------------------------------------------------------

hideUMB proc    near
        pushreg <ax, es>

        call    findUMB         ; Returns with carry if err, else ES == MCB
        jc      huX

; ------------------------------------------------
; HU10--ES - MCB inside UMB; if it's a system MCB,
;            we're not in the same UMB, so exit.
; ------------------------------------------------

hu10:   call    isSysMCB        ; Returns with ZF set if owner is SYSTEM
        jz      huX             ; If it is, we've finished the UMB.
        call    isFreeMCB       ; Returns with ZF set if owner is 0
        jnz     hu20

        call    hideMCB

hu20:   mov     al, es:[arena_signature]
        cmp     al, arena_signature_end
        jz      huX             ; 'Z' means this was the last MCB... that's it.

        NextMCB es, ax          ; Go on forward.

        jmp short hu10

huX:    popreg  <es, ax>
        ret
hideUMB endp

; -----------------------------------------------------------------------------
;*** isTiny - returns with ZF set if user didn't specify /S
; -----------------------------------------------------------------------------
; ENTRY:    None
; EXIT:     ZF set if user DIDN'T specify /S
; ERROR:    None
; USES:     Flags
; -----------------------------------------------------------------------------

isTiny  proc    near
        push    ax
        getdata al, fUmbTiny
        or      al, al
        pop     ax
        ret
isTiny  endp

; -----------------------------------------------------------------------------
;*** isFreeMCB - returns with ZF set if current MCB (ES:0) is FREE
; -----------------------------------------------------------------------------
; ENTRY:    ES:0 should point to an MCB
; EXIT:     ZF set if MCB is free, else !ZF
; ERROR:    None
; USES:     Flags
; -----------------------------------------------------------------------------

isFreeMCB       proc    near
        or      ES:[arena_owner], 0
        ret
isFreeMCB       endp

; -----------------------------------------------------------------------------
;*** hideMCB - marks as HIDDEN the MCB at ES:0
; -----------------------------------------------------------------------------
; ENTRY:    ES:0 should point to an MCB
; EXIT:     None; MCB marked as HIDDEN
; ERROR:    None
; USES:     None
; -----------------------------------------------------------------------------

hideMCB proc    near
        mov     es:[arena_owner], SystemPSPOwner
        mov     word ptr es:[arena_name+0], 'IH'
        mov     word ptr es:[arena_name+2], 'DD'
        mov     word ptr es:[arena_name+4], 'NE'
        mov     word ptr es:[arena_name+6], '  '
        ret
hideMCB endp

; -----------------------------------------------------------------------------
;*** unHideMCB - marks as FREE the MCB at ES:0
; -----------------------------------------------------------------------------
; ENTRY:    ES:0 should point to an MCB
; EXIT:     None; MCB marked as FREE
; ERROR:    None
; USES:     None
; -----------------------------------------------------------------------------

unHideMCB       proc    near
        push    ax
        mov     es:[arena_owner], FreePSPOwner
        mov     ax, '  '
        mov     word ptr es:[arena_name+0], ax
        mov     word ptr es:[arena_name+2], ax
        mov     word ptr es:[arena_name+4], ax
        mov     word ptr es:[arena_name+6], ax
        pop     ax
        ret
unHideMCB       endp

; -----------------------------------------------------------------------------
;*** findUMB - makes ES:0 point to the first MCB in UMB given as AL
;            -- returns UmbHEAD pointer (0x9FFF) if passed AL==0
; -----------------------------------------------------------------------------
; ENTRY:    AL should be to a valid UMB number
; EXIT:     ES:0 points to first MCB in UMB (_not_ the 8+SC MCB that heads it)
; ERROR:    Carry set if couldn't reach UMB (too high)
; USES:     Flags, ES
; -----------------------------------------------------------------------------

findUMB proc    near
        pushreg <ax, cx, dx>

        xor     ah, ah          ; Zap ah, so al==ax

        mov     dx, ax          ; Store the to-be-found UMB number in DX

        call    UmbHead         ; Returns first UMB segment in AX
        mov     es, ax
        xor     cx, cx          ; Pretend we're on UMB 0 for now...

; ---------------------------------------------
; FU10--CX - This UMB number; 0 == conventional
;       DX - The UMB number they're looking for
;       ES - The current MCB address
; ---------------------------------------------

fu10:   cmp     cx, dx          ; If CX==DX, we've found the UMB we're
        jz      fuX             ; searching for--so exit.

        call    isSysMCB        ; Returns with ZF set if owner is SYSTEM
        jnz     fu20

        inc     cx              ; If it _was_ SYSTEM, we're in a new UMB.

fu20:   mov     al, es:[arena_signature]
        cmp     al, arena_signature_end
        jz      fuE             ; 'Z' means this was the last MCB... that's it.

        NextMCB es, ax          ; Go on forward.

        jmp short fu10

fuE:    stc
fuX:    popreg  <dx, cx, ax>    ; The address is already in ES.
        ret
findUMB endp

; -----------------------------------------------------------------------------
;*** BigFree - makes ES:0 point to the largest free MCB in UMB given as AL
; -----------------------------------------------------------------------------
; ENTRY:    AL should be to a valid UMB number
; EXIT:     ES:0 points to largest free MCB in UMB, AX returns its size
; ERROR:    Carry set if couldn't reach UMB (0 or too high)
; USES:     Flags, ES
; -----------------------------------------------------------------------------

        public  BigFree

BigFree proc    near
        pushreg <bx, cx>

        call    findUMB                 ; Returns with CF if err, else ES==MCB
        jc      bfX                     ; (would be "jc bfE"; it just does stc)

        xor     bx, bx                  ; Segment address of largest free MCB
        xor     cx, cx                  ; Size of largest free MCB

; ---------------------------------------------
; BF10--ES - Current MCB address
;       BX - Address of largest free MCB so far
;       CX - Size of largest free MCB so far
; ---------------------------------------------

bf10:   call    isSysMCB                ; If we've left the MCB, we're done.
        jz      bf30

        call    isFreeMCB               ; Returns with ZF set if owner is 0
        jnz     bf20

        cmp     cx, es:[arena_size]     ; Compare sizes...
        jg      bf20                    ; Unless we're bigger,

        mov     bx, es                  ; Store this new element's address,
        mov     cx, es:[arena_size]     ; and its size.

bf20:   mov     al, es:[arena_signature]
        cmp     al, arena_signature_end
        jz      bf30                    ; 'Z' means this was the last MCB.

        NextMCB es, ax                  ; Go on forward.

        jmp short bf10

bf30:   mov     es, bx                  ; Return the address
        mov     ax, cx                  ; Return the size
        or      bx, bx
        jnz     bfX                     ; (if size==0, there's nothing free)

bfE:    stc
bfX:    popreg  <cx, bx>

        ret
BigFree endp

; -----------------------------------------------------------------------------
;*** isSpecified - sets ZF if UMB in AL wasn't specified in DH/LH line.
; -----------------------------------------------------------------------------
; ENTRY:    AL should be to a valid UMB number
; EXIT:     ZF set if UMB wasn't specified, ZF clear if it was
; ERROR:    None
; USES:     Flags
; -----------------------------------------------------------------------------

isSpecified     proc    near
        push    ax

        xor     bh, bh
        mov     bl, al
        getdata al, DS:UmbUsed[bx]
        or      al, al                  ; Sets ZF if al==0 (ie, if unspecified)

        pop     ax
        ret
isSpecified     endp

; -----------------------------------------------------------------------------
;*** shrinkMCB - breaks an MCB into two pieces, the lowest one's size==AX
; -----------------------------------------------------------------------------
; ENTRY:    AX == new size, ES:0 == current MCB
; EXIT:     None; MCB broken if carry clear
; ERROR:    Carry set if MCB isn't as large as AX+0x20 (not a useful split)
; USES:     Flags
; -----------------------------------------------------------------------------
; If the size of the to-be-split MCB isn't at least 0x20 bytes greater than
; the specified new size, the split is useless; if it's onnly 0x10 bytes, that
; 0x10 will be used to make a header that mentions a 0-byte free space, and
; that just sucks up 0x10 bytes for nothing.  So we make 0x20 bytes the
; minimum for performing a split.
; -----------------------------------------------------------------------------

MIN_SPLIT_SIZE  equ     20h

shrinkMCB       proc    near
        pushreg <bx, cx, es>

        mov     bx, ax                  ; Move things around... and
        mov     ax, es                  ; save this one for later.

        mov     cx, es:[arena_size]
        sub     cx, MIN_SPLIT_SIZE
        cmp     bx, cx                  ; {New size} vs {Current Size-20h}
        ja      smE                     ; if wanted_size > cur-20h, abort.

        mov     dl, es:[arena_signature]
        mov     cx, es:[arena_size]

        mov     word ptr es:[arena_size], bx
        mov     byte ptr es:[arena_signature], 'M'

        add     ax, bx
        inc     ax
        mov     es, ax                  ; Move to new arena area

        mov     ax, cx
        sub     ax, bx
        dec     ax                      ; And prepare the new size

        mov     byte ptr es:[arena_signature], dl
        mov     word ptr es:[arena_owner], 0
        mov     word ptr es:[arena_size], ax
        mov     ax, '  '
        mov     word ptr es:[arena_name+0], ax
        mov     word ptr es:[arena_name+2], ax
        mov     word ptr es:[arena_name+4], ax
        mov     word ptr es:[arena_name+6], ax

        clc
        jmp short smX

smE:    stc

smX:    popreg  <es, cx, bx>
        ret
shrinkMCB       endp

; -----------------------------------------------------------------------------
;*** hideUMB? - hides as appropriate the UMB in CL
; -----------------------------------------------------------------------------
; ENTRY:    CL should be to a valid UMB number, and AX to its address (findUMB)
; EXIT:     None; UMB is hidden as necessary
; ERROR:    None
; USES:     Flags, AX, CX
; -----------------------------------------------------------------------------
; PRIMARY LOGIC:
;
; If the UMB is specified in the DH/LH statement, then:
;    If the largest free segment is too small (check specified size), then:
;       Pretend it wasn't ever specified, and fall out of this IF.
;    Else, if largest free segment is LARGER than specified size, then:
;       If /S was given on the command-line, then:
;          Break that element into two pieces
;          Set a flag that we're shrinking
;       Endif
;    Endif
; Endif
; If the UMB is NOT specified (or was removed by the above):
;    Hide all free elements in the UMB
;    If the flag that we're shrinking was set, then:
;       UN-hide the lower portion of the shrunken UMB
;    ENDIF
; ENDIF
; -----------------------------------------------------------------------------

hideUMB?        proc
        pushreg <bx, dx, es>

        mov     al, cl
        call    isSpecified     ; Returns ZF set if al's umb was NOT specified
        jz      hu?20

        mov     al, cl          ; Retrieve the size of the largest
        call    BigFree         ; free element in AX; put its address in ES
        jc      hu?20           ; Oops.  Errors mean skip this part.

        push    ax              ; TOS==size of BigFree in UMB (popped as BX)
        mov     al, cl          ; Retrieve the user's specified
        call    GetSize         ; minimum size for this umb (into AX)
        pop     bx              ; Now BX==BigFree, AX==Specified Size

        or      ax, ax          ; If they didn't specify one,
        jz      hu?20           ; Skip over all this.

        cmp     ax, bx          ; Ah... if (specified > max free)
        jbe     hu?10

        mov     al, cl          ;    Then mark that UMB as unused.  Nya nya.
        call    unMarkUMB
        jmp short hu?20

hu?10:  call    isTiny          ; Returns ZF clear if user specified /S
        jz      hu?20

        call    shrinkMCB       ; They specified /S, so shrink the MCB to AX
        jc      hu?20           ; Ah... if didn't shrink after all, skip this:

        mov     dx, es
        jmp short hu?30         ; Skip the spec check.. we wanna hide this one.

hu?20:  mov     ax, cx
        call    isSpecified     ; If they specified this UMB, we're done...
        jnz     hu?X            ; so leave.

        xor     dx, dx

hu?30:  mov     al, cl

        call    hideUMB         ; Hides everything in UMB #al

        or      dx, dx          ; Did we shrink a UMB?  If not, DX==0,
        jz      hu?X            ; So we should leave.

        mov     es, dx          ; Ah, but if it isn't, DX==the MCB's address;
        call    unHideMCB       ; Un-hides the lower portion of that MCB.

hu?X:   popreg  <es, dx, bx>
        ret
hideUMB?        endp

; -----------------------------------------------------------------------------
;*** UnFreeze - Marks FROZEN elements as FREE
; -----------------------------------------------------------------------------
; Entry:  None
; Exit:   None; all 8+FROZEN elements are marked as FREE, from any UMB.
; Error:  None
; Uses:   Flags
; -----------------------------------------------------------------------------

        public  UnFreeze

UnFreeze        proc    near
        pushreg <ax, es>

        call    UmbHead         ; Returns with carry if err, else ES == MCB
        jc      ufX

        mov     es, ax

; ------------------------------
; UF10--ES - Current MCB address
; ------------------------------

uf10:   call    isFrozMCB       ; Returns with ZF set if MCB is FROZEN
        jnz     uf20
        call    unHideMCB

uf20:   mov     al, es:[arena_signature]

        cmp     al, arena_signature_end
        jz      ufX             ; 'Z' means this was the last MCB... that's it.

        NextMCB es, ax          ; Go on forward.
        jmp short uf10

ufX:    popreg  <es, ax>
        ret
UnFreeze        endp

; -----------------------------------------------------------------------------
;*** isFrozMCB - returns with ZF set if current MCB (ES:0) is FROZEN
; -----------------------------------------------------------------------------
; ENTRY:    ES:0 should point to an MCB
; EXIT:     ZF set if MCB is frozen, else !ZF
; ERROR:    None
; USES:     Flags
; -----------------------------------------------------------------------------

isFrozMCB       proc    near
        push    ax

        mov     ax, es:[arena_owner]    ; Check the owner...
        cmp     ax, SystemPSPOwner      ; 8 (for US OR Japan) is valid
        jnz     ifmX

        mov     ax, word ptr es:[arena_name]
        cmp     ax, 'RF'
        jnz     ifmX
        mov     ax, word ptr es:[arena_name+2]
        cmp     ax, 'ZO'
        jnz     ifmX
        mov     ax, word ptr es:[arena_name+4]
        cmp     ax, 'NE'
        jnz     ifmX
        mov     ax, word ptr es:[arena_name+6]
        cmp     ax, '  '

ifmX:   pop     ax
        ret
isFrozMCB       endp

; -----------------------------------------------------------------------------
;*** frezMCB - marks as 8+FROZEN the MCB at ES:0
; -----------------------------------------------------------------------------
; ENTRY:    ES:0 should point to an MCB
; EXIT:     None; MCB frozen
; ERROR:    None
; USES:     None
; -----------------------------------------------------------------------------

frezMCB proc    near
        mov     es:[arena_owner], SystemPSPOwner
        mov     word ptr es:[arena_name+0], 'RF'
        mov     word ptr es:[arena_name+2], 'ZO'
        mov     word ptr es:[arena_name+4], 'NE'
        mov     word ptr es:[arena_name+6], '  '
        ret
frezMCB endp

; -----------------------------------------------------------------------------
;*** FreezeUM - Marks FROZEN all UM elements now FREE, save those in load UMB
; -----------------------------------------------------------------------------
; Entry:  None
; Exit:   None; all free elements not in load UMB marked as 8+FROZEN
; Error:  None
; Uses:   Flags
; -----------------------------------------------------------------------------

FreezeUM        proc    near
        pushreg <ax, cx, dx, es>

        call    GetLoadUMB
        xor     ah, ah          ; Zap ah, so al==ax
        mov     dx, ax          ; Store the load UMB in DX, so we can skip it

        call    UmbHead         ; Returns first UMB segment in AX
        mov     es, ax
        xor     cx, cx          ; Pretend we're on UMB 0 for now...

; -----------------------------------------
; FUM10--ES - Current MCB address
;        CX - Current UMB number
;        DX - UMB number to skip (load UMB)
; -----------------------------------------

fum10:  call    isSysMCB        ; Returns with ZF set if owner is SYSTEM
        jnz     fum20

        inc     cx              ; If it _was_ SYSTEM, we're in a new UMB.

fum20:  cmp     cx, dx          ; If this is the load UMB, we don't want to
        jz      fum30           ; freeze anything... so skip that section.

        call    isFreeMCB       ; Oh.  If it's not free, we can't freeze it
        jnz     fum30           ; either.

        call    frezMCB

fum30:  mov     al, es:[arena_signature]
        cmp     al, arena_signature_end
        jz      fumX            ; 'Z' means this was the last MCB... that's it.

        NextMCB es, ax          ; Go on forward.
        jmp short fum10

fumX:   popreg  <es, dx, cx, ax>
        ret
FreezeUM        endp

; -----------------------------------------------------------------------------
;*** UmbTest - returns with carry set if UMBs are not available, else CF==false
; -----------------------------------------------------------------------------
; ENTRY:    None
; EXIT:     Carry is clear if UMBs are available, or set if they are not
; ERROR:    None
; USES:     CF (AX,BX,DS,ES pushed 'cause they're used by others)
; -----------------------------------------------------------------------------

        public  UmbTest

UmbTest proc    near
        pushreg <ax, bx, ds, es>

        call    fm_link                 ; Link in UMBs (if not already linked)
        call    WalkMem                 ; Check to see if they're really linked
        pushf                           ; And remember what we found out
        call    fm_unlink               ; Unlink UMBs (if WE have linked 'em)
        popf                            ; And restore what we found out.

        popreg  <es, ds, bx, ax>
        ret
UmbTest endp

; -----------------------------------------------------------------------------
;*** WalkMem - travels memory chain and returns carry clear iff UMBs are linked
; -----------------------------------------------------------------------------
; ENTRY:    None
; EXIT:     Carry SET if MCB chain stops before 9FFF, CLEAR if stops >= 9FFF.
; ERROR:    None
; USES:     Flags
; -----------------------------------------------------------------------------

WalkMem proc    near
        pushreg <ax, bx, es>

        mov     ah, DOS_GET_DOS_LISTS   ; Call int 21h, function 52h...
        int     21h

        mov     ax, es:[bx -2]
        mov     es, ax

; ------------------------------
; UM10: ES = Current MCB pointer
; ------------------------------

um10:   mov     al, es:[arena_signature]
        cmp     al, arena_signature_end
        jz      um20                    ; If signature == 'Z', hay no more.

        NextMCB es, bx                  ; Move to the next MCB
        jmp short um10                  ; And restart the loop.

um20:   mov     ax, es
        cmp     ax, 9FFFh               ; This sets CF iff ax < 9FFF.

        popreg  <es, bx, ax>
        ret
WalkMem endp

; -----------------------------------------------------------------------------
;*** hl_unlink - unlinks UMBs if fm_umb is set to 0; restores strategy too
; -----------------------------------------------------------------------------
; ENTRY:    fm_umb == 1 : leave linked, else unlink
; EXIT:     None
; ERROR:    None
; USES:     AX, BX
; -----------------------------------------------------------------------------

hl_unlink       proc    near
        xor     bh, bh
        getdata bl, fm_umb              ; Restore original link-state
        mov     ax, DOS_SET_UMBLINK
        int     21h

ifdef HV_LoadHigh
        xor     bh, bh
        getdata bl, fm_strat            ; Restore original mem-alloc strategy

        mov     ax, DOS_SET_STRATEGY
        int     21h
endif
        ret
hl_unlink       endp

