; 24 April 1995
; Follower.asm - Andrew Kohlsmith
; Follows MCBs, DOS Device Drivers, DPBs, DOS Buffers, etc.
; based on MCB.ASM, also by Andrew Kohlsmith

; 24 Apr 1995: Interesting...  If you get to the last MCB link and follow it
; as if it were a normal link, you get into the HMA MCB's!  DOS must change
; the 'Z' to an 'M' if you LH the program!  Good for anti-detection? Who knows?

; 24 Apr: Just found out: If a DOS block (PID=0008) has a filename of "SD",
; then a driver MCB array like that found in the first MCB is embedded.
; So far, These filenames in a DOS block are special:
; SC  = ? device driver headers?  VDISK/XMMXXXX/etc are found 2 paras below.
;       SC also marks the unusable area at a000:0 - afff:ffff...
; SD  = DOS Device Driver MCB array
; UMB = found (on my machine) at B000:0, 1 para before himem MCB.

jumps
model small

include macros.inc             ; standard macros

;.stack                        ; standard stack

;==============================================================================
.data
;==============================================================================
INVARS         dw ?, ?
in_special_mcb db ?            ; nonzero if special DOS mcb
;next_mcb      dw ?            ; seg of MCB after special one

init_msg       db 0dh, 0ah, "FOLLOWER V0.1 By TZ 04/24/95", 0dh, 0ah, '$'
invars_at      db 0dh, 0ah, "INVARS at $"

;--- MCB-related messages -----------------------------------------------------
; What an entry looks like:
;P_ID Para Reserved ------------ Program ----------  (MCB Link @ xxxx:xxxx)
;0008 0018 00 00 00 00 00 00 00 00 00 00 00 ABCDEFG  (DOS MCB)

mcb_1st_at     db 0dh, 0ah, "First MCB Link at $"
mcb_SC_break   db 0dh, 0ah, '"SC"-Type DOS MCB Link Breakdown  (MCB @ $'
mcb_SD_break   db 0dh, 0ah, '"SD"-Type DOS MCB Link Breakdown  (MCB @ $'
mcb_UMB_break  db 0dh, 0ah, '"UMB"-Type DOS MCB Link Breakdown (MCB @ $'
mcb_break2     db '):', 0dh, 0ah, 79 dup ('='), '$'
mcb_S_end      db 0dh, 0ah, "=== Regular MCBs follow========================="
               db "==============================", 0dh, 0ah, '$'
mcb_line       db 0dh, 0ah, "P_ID Para Reserved ------------ Program ---------"
               db "-- $"
mcb_at         db " MCB @ $"
mcb_followhi   db 0dh, 0ah, 0ah, "MCB table ends in low memory."
               db 0dh, 0ah, "Attempting to follow link into upper memory..."
               db 0dh, 0ah, "(may abort if no upper memory)...", 0dh, 0ah, '$'
bad_mcb_msg    db 0dh, 0ah, 0ah, "*** BAD MCB, Backtracking!", 0dh, 0ah, '$'
mcb_corrupt    db 0dh, 0ah, 0ah, 07h, "MCB Table Corrupt!$"

; MCB flags (special PID numbers)
mcb_free       db " (free MCB)$"      ; PID = 0000
mcb_dos        db " (DOS MCB)$"       ; PID = 0008

; Following is a list of all MCB types.  Only "M" and "Z" are valid outside of
; an special DOS MCB.  Each entry is 27 characters long.
;                 " 1234567890123456789012345$"
mcb_types        label byte
mcb_A          db "                          $"
mcb_B          db "BUFFERS= storage          $"
mcb_C          db "BUFFERS= EMS workspace    $"
mcb_D          db "Device Driver             $"
mcb_E          db "Device Driver Appendage   $"
mcb_F          db "FILES= control block      $"
mcb_G          db "                          $"
mcb_H          db "                          $"
mcb_I          db "File System Driver        $"
mcb_J          db "                          $"
mcb_K          db "                          $"
mcb_L          db "LASTDRIVE= dir struc array$"
mcb_M          db "MCB link                  $"
mcb_N          db "                          $"
mcb_O          db "                          $"
mcb_P          db "                          $"
mcb_Q          db "                          $"
mcb_R          db "                          $"
mcb_S          db "STACKS= code/data area    $"
mcb_T          db "                          $"
mcb_U          db "                          $"
mcb_V          db "                          $"
mcb_W          db "                          $"
mcb_X          db "FCBS= control block       $"
mcb_Y          db "                          $"
mcb_Z          db "Final MCB                 $"

;==============================================================================
.code
org 100
;==============================================================================
main proc near
        mov ax, @data          ; init DS, ES
        mov ds, ax
        mov es, ax

        mov ah, 9              ; print header
        mov dx, o init_msg
        int 21h

        mov ah, 52h            ; Get INVARS ptr
        int 21h                ; INVARS @ ES:BX

        mov [INVARS], bx       ; save INVARS for later
        push es
        pop [INVARS+2]

        mov ax, o invars_at    ; DS:AX = message
        call Print_Pointer_Msg ; display INVARS ptr

        call Follow_MCB        ; follow MCBs

        mov ax, 4c00h
        int 21h
main endp

;----------
; Follow_MCB
; Does what it says.  Follows the MCB list until the end and returns.
; Bombs if MCB table corrupt.
;----------
Follow_MCB proc
        mov [in_special_mcb], 0        ; clear flag

        les bx, dword ptr [INVARS]     ; ES:BX = INVARS
        sub bx, 2              ; ES:BX is now ptr to 1st mcb

        mov dx, es:[bx]        ; DX = seg of 1st mcb
        mov es, dx             ; ES = seg of 1st mcb
        xor bx, bx             ; ES:BX = 1st mcb

        mov ax, o mcb_1st_at   ; message
        call Print_Pointer_Msg ; print "1st MCB at xxxx:xxxx"

        mov ah, 2
        mov dl, 0dh
        int 21h
        mov dl, 0ah
        int 21h                ; print CR/LF

display_mcb_loop:
        call Print_Mcb         ; display MCB information
        jnc mcb_ok

; MCB was bad, if [in_special_mcb], grab the address of the next mcb and
; continue there.

        mov ah, 9
        mov dx, o bad_mcb_msg
        int 21h

        pop es
;        mov dx, [next_mcb]     ; seg of next mcb
;        mov es, dx

        dec [in_special_mcb]
        jmp display_mcb_loop

mcb_ok:
; if the MCB was ok, check for special DOS MCB types:

        cmp word ptr es:[1], 0008h     ; is it a DOS block?
        jnz normal_mcb         ; jmp if not

        cmp word ptr es:[8], "DS"      ; is it an SD-type block?
        jnz normal_mcb         ; jmp if not

; Put this back in when I want to work on SC and UMB jumps...
;        jnz check_sc          ; jmp if not

        mov ax, o mcb_SD_break
        jmp short do_block

check_sc:
        cmp word ptr es:[8], "CS"      ; is it an SC-type block?
        jnz check_umb          ; jmp if not

        mov ax, o mcb_SC_break
        jmp short do_block

check_umb:
        cmp word ptr es:[8], "MU"      ; first 2 letters of "UMB"?
        jnz normal_mcb         ; jmp if not

        mov ax, o mcb_UMB_break

do_block:
        xor bx, bx
        call Print_Pointer_Msg

        mov ah, 9
        mov dx, o mcb_break2
        int 21h                ; print msg

        inc [in_special_mcb]   ; flag special mcb breakdown
        mov dx, es             ; DX = MCB segment
        add dx, 1              ; drop into 1st MCB (+1 para)
        mov ax, dx             ; AX = MCB+1

; we already have MCB+1, so just add on the length of this MCB to find out
; where the second one lies.  We need to know so we can drop out of
; [in_special_mcb].

        add ax, es:[3]         ; add on length of MCB
        push ax
;        mov [next_mcb], ax    ; save seg of next MCB
        mov es, dx             ; ES = special MCB (internal)

        jmp short display_mcb_loop     ; jmp back and show internal MCB

normal_mcb:
        cmp byte ptr [in_special_mcb], 0    ; are we doing special mcb?
        jnz skip_bad_mcb_check   ; jmp if so

        cmp byte ptr es:[0],'M'  ; is there another?
        jz skip_bad_mcb_check    ; jmp if so

        cmp byte ptr es:[0],'Z'  ; The last one?
        jnz bad_mcb              ; jmp if not

; If the last link was found to be in low memory, try to follow link up into
; high memory.  Just treat the link as if it were an 'M' link.  LoadHigh must
; change the last MCB to 'M' before executing the program.  Possible cool
; utility?  Perhaps...

; Note that I don't try to follow a "Z" link in high memory.  I don't know
; if it would serve any purpose of if a "Z" link in high memory is really a
; "Z" link.  Maybe SuperLoadHigh changes it to an "M"...  :-)

        mov dx, es             ; get seg of MCB in DX
        cmp dx, 0a000h         ; was it in low memory?
        jae follow_mcb_done    ; jmp if not

        mov ah, 9
        mov dx, o mcb_followhi
        int 21h                ; print message

        jmp short skip_bad_mcb_check   ; follow MCB like usual

; Not M or Z, bad entry...  just bomb, let DOS lock up.

bad_mcb:
        mov dx, o mcb_corrupt  ; if we end up here, there is a
        mov ah, 9              ; corrupt MCB table  <bad thing>
        int 21h

        mov ax, 4cffh
        int 21h

skip_bad_mcb_check:
        mov dx, es             ; DX:0 = MCB
        add dx, es:[3]         ; add on # of paras used
        inc dx                 ; include mcb (+1 para)
        mov es, dx             ; ES:0 = next MCB

        cmp byte ptr [in_special_mcb], 0    ; are we tracing special MCB?
        jz display_mcb_loop    ; jmp if not

        mov bp, sp
        cmp dx, [bp]          ; are we at next mcb already?
;        cmp dx, [next_mcb]    ; are we at next mcb already?
        jnz display_mcb_loop  ; jmp if not

        dec [in_special_mcb]  ; unflag special mcb breakdown
        pop es

        mov ah, 9
        mov dx, o mcb_S_end
        int 21h               ; display ending string

        jmp display_mcb_loop  ; keep on going...

follow_mcb_done:
        ret
Follow_MCB endp

;----------
; Print_MCB
; Just prints out the information about an MCB, including special PID codes.
; Prints out special link codes as well (@ mcbseg:[0]) for DOS-related links.
; returns with carry set on error
;----------
Print_MCB proc
        mov ah, 9
        mov dx, o mcb_line
        int 21h               ; print top line

        mov dx, o mcb_types   ; start of link descriptions
        mov al, es:[0]        ; type of MCB link
        sub al, 'A'           ; convert to number 0-25
        jge pmcb_ok_type      ; if > 0, type is ok

; we underflowed the table, type was < "A", just return.  We don't check for
; overflowing, but fuck it.  Well, at least in V0.1... :-)

        stc                   ; set carry
        ret

pmcb_ok_type:
        mov ah, 27            ; 27 chars per description
        mul ah                ; AX has offset into table
        add dx, ax            ; DX has offset to entry

        mov ah, 9
        int 21h               ; print MCB link type

        mov ah, 2
        mov dl, 0dh
        int 21h
        mov dl, 0ah
        int 21h               ; print CR/LF

        mov dx, es:[1]        ; DX = Process ID
        call printdx_spc

        mov dx, es:[3]        ; DX = size in paras
        call printdx_spc

        mov si, 5             ; ES:SI = reserved byte
        mov cx, 11            ; 11 bytes to go

pmcb_resloop:
        mov al, es:[si]
        inc si
        mov dl, al
        call printdl_spc      ; print it
        loopx cx, pmcb_resloop

        mov cx, 8             ; CX = 8 chars
        mov si, cx            ; ES:SI = program name
pmcb_prog_charloop:
        mov ah, 2             ; DOS print char
        mov al, es:[si]
        inc si
        cmp al, 20h           ; >= space?
        jb pmcb_pdot          ; jmp if not
        cmp al, 80h           ; < 128?
        jb pmcb_pchar         ; jmp if so

pmcb_pdot:
        mov al, '.'           ; default to '.'
pmcb_pchar:
        mov dl, al
        int 21h
        loopx cx, pmcb_prog_charloop   ; print all reserved bytes

        mov ax, o mcb_at
        xor bx, bx
        call Print_Pointer_Msg   ; print "MCB @ xxxx:yyyy"

        mov dx, es:[1]        ; DX = PID

        cmp dx, 8             ; PID = 8 (DOS) ?
        jnz pmcb_notdos       ; jmp if not

        mov dx, o mcb_dos     ; set up DX
        jmp s pmcb_printspecial

pmcb_notdos:
        cmp dx, 0             ; PID = 0 (free) ?
        jnz pmcb_not_free           ; jmp if not

        mov dx, o mcb_free
pmcb_printspecial:
        mov ah, 9
        int 21h               ; print special PID

pmcb_not_free:
        mov ah, 2
        mov dl, 0dh
        int 21h
        mov dl, 0ah
        int 21h               ; print CR/LF

        clc
        ret
Print_MCB endp

;----------
; Print_Pointer_Msg
; Prints the message @ DS:AX, followed by "xxxx:yyyy",
;        with xxxx = ES and yyyy = BX
;----------
Print_Pointer_Msg proc
        mov dx, ax            ; DS:DX = message
        mov ah, 9
        int 21h               ; print it

        mov dx, es
        call printdx

        mov ah, 2
        mov dl, ':'
        int 21h

        mov dx, bx
        call printdx

        ret
Print_Pointer_Msg endp

;----------
; Just dumb little function-macros so I don't waste lots of code space.
;----------
printdx proc
        printh16 dx           ; 0-pad hex dump of DX
        ret
printdx endp

printdx_spc proc
        printh16 dx           ; 0-pad hex dump of DX

        mov ah, 2
        mov dl, 20h
        int 21h               ; and a space
        ret
printdx_spc endp

printdl_spc proc
        printh8 dl            ; 0-pad hex dump of DL

        mov ah, 2
        mov dl, 20h
        int 21h               ; and a space
        ret
printdl_spc endp

end main

