; rawread.asm
;
; this program reads a DOS cluster using only BIOS disk calls.  All
; of the tasks usually done by DOS, e.g. FAT lookup, cluster to
; logical sector translation, logical to physical translation, are
; all done by this program instead.  The idea is to be able to create
; a program that can access DOS disks from a bootable floppy without
; having to have DOS.
;
; well, that's what it used to do.  Now it's supposed to do something
; completely different.  Its job is to scan the entire surface of the
; hard drive, looking for the specified string.  If that string is
; found, it is to print the full path and directory entry, including
; the file date and time.
;
; but wait!  There's more.  Now what we have is a number of raw
; routines which could prove useful for manipulating a DOS file
; structure outside of the DOS environment.  The main routine still
; should be kept (if renamed), since the order in which these things
; are done is important (e.g. later calls depend on data set up by
; earlier calls).
;
; get filename
; parse filename into subdirs
; locate root dir and cluster size
; follow subdir routing to filename
; report file size, date & time
;
        .MODEL small
        .STACK 0200h
        .586P

        .DATA
PartEntry STRUC
        Bootable        db ?    ;80h = bootable, 00h = nonbootable
        BeginHead       db ?    ;beginning head
        BeginSector     db ?    ;beginning sector
        BeginCylinder   db ?    ;beginning cylinder
        FileSystem      db ?    ;name of file system
        EndHead         db ?    ;ending head
        EndSector       db ?    ;ending sector
        EndCylinder     db ?    ;ending cylinder
        StartSector     dd ?    ;starting sector (relative to beg. of disk)
        PartSectors     dd ?    ;number of sectors in partition
PartEntry ENDS

BootSector STRUC
        Jump            db ?            ;E9 xx xx or EB xx 90
        JumpTarget      dw ?            ;E9 xx xx or EB xx 90
        OemName         db '????????'   ;OEM name & version
                                        ;Start of BIOS parameter block
        BytesPerSec     dw ?            ;bytes per sector
        SecPerClust     db ?            ;sectors per cluster
        ResSectors      dw ?            ;number of reserved sectors
        FATs            db ?            ;number of file allocation tables
        RootDirEnts     dw ?            ;number of root-dir entries
        Sectors         dw ?            ;total number of sectors
        Media           db ?            ;media descriptor byte
        FATsecs         dw ?            ;number of sectors per FAT
        SecPerTrack     dw ?            ;sectors per track
        Heads           dw ?            ;number of heads
        HiddenSecs      dd ?            ;number of hidden sectors
        HugeSectors     dd ?            ;num sectors if Sectors==0
                                        ;End of BIOS parameter block
BootSector ENDS

DirEntry STRUC
        FileName        db '????????'   ;name
        Extension       db '???'        ;extension
        Attributes      db ?            ;attributes
        Reserved        db 10 dup (?)   ;reserved
        Time            dw ?            ;time stamp
        Date            dw ?            ;date stamp
        StartCluster    dw ?            ;starting cluster
        FileSize        dd ?            ;file size
DirEntry ENDS

BootFileName  db  "CONFIG  SYS"         ;the boot loader for this OS
MBR     DB      0200h DUP (?)
buff    DB      0200h * 40h DUP (?)
ClustOffs       dd      ?

CR      EQU     0DH
LF      EQU     0AH


        .CODE
main PROC
        STARTUPCODE                     ;initialize stuff
        call    FetchMBR C              ;fetch the master boot record
        jc      @@exit
        mov     cx,4                    ;search up to four partitions
        add     bx,01aeh                ;point to partition table (-10h)
@@FindBootable:
        add     bx,10h                  ;point to next entry
        cmp     BYTE ptr [bx],80h       ;is it a bootable partition?
        loopnz  @@FindBootable
        call    FetchSector C,                                \
                WORD ptr [(PartEntry PTR bx).BeginHead],      \
                WORD ptr [(PartEntry PTR bx).BeginSector],    \
                WORD ptr [(PartEntry PTR bx).BeginCylinder],  \
                OFFSET MBR, ds ;SEG MBR
;
; here's the point at which our OS loader would begin, with the
; BootSector structure in memory.
;
        mov     bx, OFFSET MBR
        call    CalcClustOff C, \
                WORD ptr [(BootSector PTR bx).ResSectors],    \
                WORD ptr [(BootSector PTR bx).FATsecs],       \
                WORD ptr [(BootSector PTR bx).FATs],          \
                WORD ptr [(BootSector PTR bx).RootDirEnts],   \
                WORD ptr [(BootSector PTR bx).BytesPerSec],   \
                WORD ptr [(BootSector PTR bx).SecPerClust]
        mov     WORD ptr [ClustOffs],ax
        mov     WORD ptr [ClustOffs+2],dx
        call    CalcClust2 C,                                 \
                WORD ptr [(BootSector PTR bx).ResSectors],    \
                WORD ptr [(BootSector PTR bx).FATsecs],       \
                WORD ptr [(BootSector PTR bx).FATs]
        ; now dx:ax contains the logical sector for cluster 2
        call    LsectToGeom C,                                \
                ax, dx,                                       \
                WORD ptr [(BootSector PTR bx).HiddenSecs]  ,  \
                WORD ptr [((BootSector PTR bx).HiddenSecs)+2],\
                [(BootSector PTR bx).Heads],                  \
                [(BootSector PTR bx).SecPerTrack]

        mov     dl,80h
        mov     bx,offset buff
        mov     al,[(BootSector PTR MBR).SecPerClust]
        mov     ah,2h                   ; get ready to read
        int     13h
        ; now find our desired filename within buffer (which has the root dir)

        call    FindFile C, \
                bx, 200h * 40h, offset BootFileName
        xor     dh,dh
        mov     dl,[(BootSector PTR MBR).SecPerClust]
        mov     si,ax
        mov     ax,[(DirEntry PTR si).StartCluster]
        mul     dx
        add     ax,WORD ptr [ClustOffs]
        adc     dx,WORD ptr [ClustOffs+2]
        ; now dx:ax contains logical sector number for start of file

        call    LsectToGeom C, \
                ax, dx, \
                WORD ptr [(BootSector PTR MBR).HiddenSecs]  ,  \
                WORD ptr [((BootSector PTR MBR).HiddenSecs)+2],\
                [(BootSector PTR MBR).Heads],                  \
                [(BootSector PTR MBR).SecPerTrack]
        mov     dl,80h
        mov     ax,204h                 ; read in 2k worth of data
        int     13h

@@exit:
        EXITCODE                        ;exit to DOS
ENDP    main

;
; FetchMBR -    fetches the Master Boot Record from the first physical
;               hard disk and stores it in the location MBR.
;
; INPUT:     none
; OUTPUT:    AX is error code if CY set, ES:BX ==> MBR
; DESTROYED: none
;
FetchMBR PROC    C
        USES    cx, dx                  ;save registers we'll use
        mov     dx,80h                  ;first physical disk
        mov     cx,1                    ;head 1, sector 0
        mov     bx,ds                   ;
        mov     es,bx                   ;point to boot record buffer
        mov     bx,OFFSET MBR           ;read into boot record
        mov     ax,0201h                ;read one sector
        int     13h                     ;BIOS read
        ret                             ;return to main
FetchMBR ENDP

;
; FetchSector - fetches the physical sector described by the passed
;               parameters and stores it in the named buffer
;
; INPUT:     head, sector, cylinder, buffer
; OUTPUT:    AX is error code if CY set, ES:BX ==> Boot
; DESTROYED: none
;
FetchSector PROC C  head:BYTE, sector:BYTE, cylinder:BYTE, buffer:DWORD
        USES    cx, dx                  ;save registers we'll use
        mov     ch, [cylinder]          ;
        mov     cl, [sector]            ;
        mov     dh, [head]              ;
        mov     dl, 80h                 ;first physical hard drive
        les     bx, [buffer]            ;
        mov     ax,0201h                ;read one sector
        int     13h                     ;BIOS read
        ret                             ;return to main
FetchSector ENDP

;
; GeomToLsect - converts to logical sector number from the physical
;               geometry (head, cylinder, track).  See LsectToGeom.
;
; INPUT:     cx, dx are set with cylinder/track, and head respectively
;            HiddenSecs, Heads, SecPerTrack
; OUTPUT:    lsect
; DESTROYED: none
;
GeomToLsect PROC    C lsect:DWORD, dHiddenSecs:DWORD,    \
                      dHeads:WORD, dSecPerTrack:WORD, buffer:DWORD
        USES    ax                      ;save registers we'll use
        mov     ax, WORD ptr [lsect]    ;load lsect into DX:AX
        mov     dx, WORD ptr [lsect+2]  ;
        stc                             ;add one additional
        adc     ax, WORD ptr [dHiddenSecs]   ;add starting sector
        adc     dx, WORD ptr [dHiddenSecs+2] ;
        div     [dSecPerTrack]          ;
        mov     cl,dl                   ;store sector in cl
        xor     dx,dx                   ;
        div     [dHeads]                ;
        mov     dh,dl                   ;store head in dh
        mov     ch,al                   ;store low 8 bits of cylinder in ch
        shr     ax,1                    ;
        shr     ax,1                    ;
        and     al,0c0h                 ;pass through two hi bits only
        or      cl,ah                   ;mov bits into location
        ret                             ;
GeomToLsect ENDP

;
; LsectToGeom - converts from logical sector number to the physical
;               geometry (head, cylinder, track) in the form required
;               by the BIOS (Int 13h) disk read and write calls.
;
; INPUT:     lsect, HiddenSecs, Heads, SecPerTrack
; OUTPUT:    cx, dx are set with cylinder/track, and head respectively
; DESTROYED: none
;
LsectToGeom PROC    C lsect:DWORD, lHiddenSecs:DWORD,    \
                      lHeads:WORD, lSecPerTrack:WORD, buffer:DWORD
        USES    ax                      ;save registers we'll use
        mov     ax, WORD ptr [lsect]    ;load lsect into DX:AX
        mov     dx, WORD ptr [lsect+2]  ;
        stc                             ;add one additional
        adc     ax, WORD ptr [lHiddenSecs]   ;add starting sector
        adc     dx, WORD ptr [lHiddenSecs+2] ;
        div     [lSecPerTrack]          ;
        mov     cl,dl                   ;store sector in cl
        xor     dx,dx                   ;
        div     [lHeads]                ;
        mov     dh,dl                   ;store head in dh
        mov     ch,al                   ;store low 8 bits of cylinder in ch
        shr     ax,1                    ;
        shr     ax,1                    ;
        and     al,0c0h                 ;pass through two hi bits only
        or      cl,ah                   ;mov bits into location
        ret                             ;
LsectToGeom ENDP

;
; CalcClust2  - calculates the starting logical sector number of
;               cluster 2, (the beginning of data space for
;               partitions).
;
; INPUT:     ResSectors, FATsecs, FATs
; OUTPUT:    dx:ax contains the starting logical sector number
; DESTROYED: none
;
CalcClust2 PROC    C cResSectors:WORD, cFATsecs:WORD, cFATs:BYTE
        xor     dx,dx                   ;
        mov     ax,[cFATsecs]           ;
        mul     [cFATs]                 ;
        add     ax,[cResSectors]        ;
        adc     dx,0                    ;
        ret
CalcClust2 ENDP

;
; CalcClustOff - calculates the starting logical sector number of
;               cluster 0, which isn't really a cluster, but the
;               number returned is useful for calculations converting
;               cluster number to logical sector
;
; INPUT:     ResSectors, FATsecs, FATs
; OUTPUT:    dx:ax contains the starting logical sector number
; DESTROYED: none
;
CalcClustOff PROC    C dResSectors:WORD, dFATsecs:WORD, dFATs:BYTE, \
        dRootDirEnts:WORD, dBytesPerSec:WORD, dSecPerClust:BYTE
        LOCAL clustLo:WORD, clustHi:WORD
        xor     dh,dh
        mov     ax,[dFatSecs]
        mov     dl,[dFATs]
        mul     dx
        add     ax,[dResSectors]
        adc     dx,0
;        call    CalcClust2 C, [dResSectors], [dFATsecs], [dFATs]
        ; now dx:ax = FATs * FATsecs + ResSectors
        mov     [clustLo],ax
        mov     [clustHi],dx
        mov     dx,20h                  ; bytes per dir entry
        mov     ax,[dRootDirEnts]       ;
        mul     dx                      ; multiply 'em out
        div     [dBytesPerSec]          ; and divide by bytes/sec
        add     [clustLo],ax            ;
        adc     [clustHi],dx            ; create the aggregate
        mov     al,[dSecPerClust]       ;
        xor     ah,ah                   ;
        shl     ax,1                    ; AX = SecPerClust * 2
        sub     [clustLo],ax            ;
        sbb     [clustHi],0             ; propagate carry flag
        mov     ax,[clustLo]            ;
        mov     dx,[clustHi]            ;
        ret
CalcClustOff ENDP

;
; FindFile -    given a memory buffer containing the directory data
;               and a static file name for which to search, this routine
;               finds the file and returns a pointer to its directory
;               entry in ds:si
;
; INPUT:        dirbuffer, filespec
; OUTPUT:       ax    contains pointer to directory entry (or NULL)
; DESTROYED:    none
;
FindFile PROC C dirbuffer:WORD, limit:WORD, filespec:WORD
        USES    cx, dx, di, si, es
        mov     cx,ds                   ;
        mov     es,cx                   ; es and ds point to same segment
        cld                             ; always count forward
        mov     ax,[dirbuffer]          ; load 'em up
        add     [limit],ax
        mov     dx,[filespec]           ;
keepsearching:
        mov     cx,11                   ; size of dos filename (8.3)
        mov     si,dx                   ;
        mov     di,ax                   ;
        repe    cmpsb                   ; compare 'em
        jz      foundit                 ;
        add     ax,20h                  ; size of directory entry
        cmp     ax,[limit]
        jb      keepsearching
        xor     ax,ax

foundit:
        ret
FindFile ENDP
        END