diff options
Diffstat (limited to '')
-rw-r--r-- | private/ntos/boot/bootcode/ntfs/i386/ntfsboot.asm | 2740 |
1 files changed, 2740 insertions, 0 deletions
diff --git a/private/ntos/boot/bootcode/ntfs/i386/ntfsboot.asm b/private/ntos/boot/bootcode/ntfs/i386/ntfsboot.asm new file mode 100644 index 000000000..a07e341e2 --- /dev/null +++ b/private/ntos/boot/bootcode/ntfs/i386/ntfsboot.asm @@ -0,0 +1,2740 @@ + page ,132 + title ntfsboot - NTFS boot loader + name ntfsboot + +; The ROM in the IBM PC starts the boot process by performing a hardware +; initialization and a verification of all external devices. If all goes +; well, it will then load from the boot drive the sector from track 0, head 0, +; sector 1. This sector is placed at physical address 07C00h. +; +; The boot code's sole resposiblity is to find NTLDR, load it at +; address 2000:0000, and then jump to it. +; +; The boot code understands the structure of the NTFS root directory, +; and is capable of reading files. There is no contiguity restriction. +; + +MASM equ 1 + .xlist + .286 + +A_DEFINED EQU 1 + + include ntfs.inc + +DoubleWord struc +lsw dw ? +msw dw ? +DoubleWord ends + +; +; The following are various segments used by the boot loader. The first +; two are the segments where the boot sector is initially loaded and where +; the boot sector is relocated to. The third is the static location +; where the NTLDR is loaded. +; + +BootSeg segment at 07c0h ; this is where the ROM loads us initially. +BootSeg ends + +NewSeg segment at 0d00h ; this is where we'll relocate to. +NewSeg ends ; enough for 16 boot sectors + + ; 4-sector scratch + ; below where we'll load NTLDR. + +LdrSeg segment at 2000h ; we want to load the loader at 2000:0000 +LdrSeg ends + +;/********************** START OF SPECIFICATIONS ************************/ +;/* */ +;/* SUBROUTINE NAME: ntfsboot */ +;/* */ +;/* DESCRIPTIVE NAME: Bootstrap loader */ +;/* */ +;/* FUNCTION: To load NTLDR into memory. */ +;/* */ +;/* NOTES: ntfsboot is loaded by the ROM BIOS (Int 19H) at */ +;/* physical memory location 0000:7C00H. */ +;/* ntfsboot runs in real mode. */ +;/* This boot record is for NTFS volumes only. */ +;/* */ +;/* ENTRY POINT: ntfsboot */ +;/* LINKAGE: Jump (far) from Int 19H */ +;/* */ +;/* INPUT: CS:IP = 0000:7C00H */ +;/* SS:SP = 0030:00FAH (CBIOS dependent) */ +;/* */ +;/* EXIT-NORMAL: DL = INT 13 drive number we booted from */ +;/* Jmp to main in NTLDR */ +;/* */ +;/* EXIT-ERROR: None */ +;/* */ +;/* EFFECTS: NTLDR is loaded into the physical memory */ +;/* location 00020000H */ +;/* */ +;/* MESSAGES: A disk read error occurred. */ +;/* The file NTLDR cannot be found. */ +;/* Insert a system diskette and restart the system. */ +;/* */ +;/*********************** END OF SPECIFICATIONS *************************/ +BootCode segment ;would like to use BootSeg here, but LINK flips its lid + assume cs:BootCode,ds:nothing,es:nothing,ss:nothing + + org 0 ; start at beginning of segment, not 0100h. + + public _ntfsboot +_ntfsboot proc far + jmp start + .errnz ($-_ntfsboot) GT (3),<FATAL PROBLEM: JMP is more than three bytes> + + org 3 +; +; This is a template BPB--anyone who writes boot code to disk +; should either preserve the existing BPB and NTFS information +; or create it anew. +; +Version db "NTFS " ; Must be 8 characters +BPB label byte +BytesPerSector dw 0 ; Size of a physical sector +SectorsPerCluster db 0 ; Sectors per allocation unit +ReservedSectors dw 0 ; Number of reserved sectors +Fats db 0 ; Number of fats +DirectoryEntries dw 0 ; Number of directory entries +Sectors dw 0 ; No. of sectors - no. of hidden sectors +Media db 0 ; Media byte +FatSectors dw 0 ; Number of fat sectors +SectorsPerTrack dw 0 ; Sectors per track +Heads dw 0 ; Number of surfaces +HiddenSectors dd 0 ; Number of hidden sectors +SectorsLong dd 0 ; Number of sectors iff Sectors = 0 +; +; The following is the rest of the NTFS Sector Zero information. +; The position and order of DriveNumber and CurrentHead are especially +; important, since those two variables are loaded into a single 16-bit +; register for the BIOS with one instruction. +; +DriveNumber db 80h ; Physical drive number (0 or 80h) +CurrentHead db ? ; Variable to store current head no. + +SectorZeroPad1 dw 0 +SectorsOnVolume db (size LARGE_INTEGER) dup (0) +MftStartLcn db (size LARGE_INTEGER) dup (0) +Mft2StartLcn db (size LARGE_INTEGER) dup (0) +ClustersPerFrs dd 0 +DefClustersPerBuf dd 0 +SerialNumber db (size LARGE_INTEGER) dup (0) +CheckSum dd 0 +; +; The following variables are not part of the Extended BPB; they're just +; scratch variables for the boot code. +; +SectorBase dd ? ; next sector to read +CurrentTrack dw ? ; current track +CurrentSector db ? ; current sector +SectorCount dw ? ; number of sectors to read + +;**************************************************************************** +start: +; +; First of all, set up the segments we need (stack and data). +; + cli + xor ax, ax ; Set up the stack to just before + mov ss, ax ; this code. It'll be moved after + mov sp, 7c00h ; we relocate. + sti + + mov ax, Bootseg ; Address our BPB with DS. + mov ds, ax + assume ds:BootCode +; +; Now read the 16-sector boot block into memory. Then jump to that +; new version of the boot block, starting in the second sector +; (after the bootrecord sig). +; + mov SectorBase.lsw, 0 ; read sector zero. + mov SectorBase.msw, 0 + + mov word ptr [SectorCount], 16 ; read boot area + mov ax, NewSeg ; read it at NewSeg. + mov es, ax + sub bx, bx ; at NewSeg:0000. + call DoReadLL ; Call low-level DoRead routine + +; + push NewSeg ; we'll jump to NewSeg:0200h. + push offset mainboot ; (the second sector). + ret ; "return" to the second sector. +_ntfsboot endp + +;******************************************************************************* +; +; Low-level read routine that doesn't work across a 64k addr boundary. +; +; Read SectorCount sectors (starting at SectorBase) to es:bx. +; +; As a side effect, SectorBase is updated (but es:bx are not) +; and SectorCount is reduced to zero. +; +DoReadLL proc + push ax ; save important registers + push bx + push cx + push dx + push es + +DoRead$Loop: + +.386 + mov eax, SectorBase + add eax, HiddenSectors + xor edx,edx +;EDX:EAX = absolute sector number + movzx ecx,word ptr SectorsPerTrack ; get into 32 bit value + div ecx ; (EDX) = sector within track, (EAX)=track + inc dl ; sector numbers are 1-based, not 0 + mov CurrentSector, dl + mov edx,eax + shr edx,16 +.286 + div Heads ; (DX) = head no., (AX) = cylinder + mov CurrentHead, dl + mov CurrentTrack, ax + +; CurrentHead is the head for this next disk request +; CurrentTrack is the track for this next request +; CurrentSector is the beginning sector number for this request +; +; Compute the number of sectors that we may be able to read in a single ROM +; request. +; + mov ax, SectorsPerTrack ; could read up to this much + sub al, CurrentSector ; offset within this track + inc ax ; CurrentSector was 1-based +; +; AX is the number of sectors that we may read. +; + cmp ax, SectorCount ; do we need to read whole trk? + jbe DoRead$FullTrack ; yes we do. + mov ax, SectorCount ; no, read a partial track. +; +; AX is now the number of sectors that we SHOULD read. +; +DoRead$FullTrack: + push ax ; save sector count for later calc. + mov ah, 2 ; "read sectors" + mov dx, CurrentTrack ; at this cylinder + mov cl, 6 + shl dh, cl ; high 2 bits of DH = bits 8,9 of DX + or dh, CurrentSector ; (DH)=cyl bits | 6-bit sector no. + mov cx, dx ; (CX)=cylinder/sector no. combination + xchg ch, cl ; in the right order + mov dh, CurrentHead + mov dl, 80h ; should be DriveNumber, but... + + int 13h ; call BIOS. + + pop ax + jb BootErr$he ; If errors report + add SectorBase.lsw, ax ; increment logical sector position + adc SectorBase.msw, 0 + sub SectorCount, ax ; exhausted entire sector run? + jbe DoRead$Exit ; yes, we're all done. + shl ax, 9 - 4 ; (AX)=paragraphs read from last track + mov dx, es ; (DX)=segment we last read at + add dx, ax ; (DX)=segment right after last read + mov es, dx ; (ES)=segment to read next track at + jmp DoRead$Loop +; +DoRead$Exit: + pop es + pop dx + pop cx + pop bx + pop ax + ret + +DoReadLL endp + + +;**************************************************************************** +; +; BootErr - print error message and hang the system. +; +BootErr proc +BootErr$fnf: + mov si,offset TXT_MSG_SYSINIT_FILE_NOT_FD +2 + jmp short BootErr2 +BootErr$ntc: + mov si,offset TXT_MSG_SYSINIT_NTLDR_CMPRS +2 + jmp short BootErr2 +BootErr$he: + mov si,offset TXT_MSG_SYSINIT_BOOT_ERROR +2 +BootErr2: + call BootErr$print + mov si,offset TXT_MSG_SYSINIT_INSER_DK +2 + call BootErr$print + sti + jmp $ ;Wait forever +BootErr$print: + lodsb ; Get next character + cmp al, 0 + je BootErr$Done + mov ah,14 ; Write teletype + mov bx,7 ; Attribute + int 10h ; Print it + jmp BootErr$print +BootErr$Done: + ret +BootErr endp + +;**************************************************************************** + include ntfsboot.inc ;suck in the message text + + +ReservedForFuture DB 2 dup(?) ;reserve remaining bytes to prevent NLS + ;messages from using them + + .errnz ($-_ntfsboot) GT (512-2),<FATAL PROBLEM: first sector is too large> + + org 512-2 + db 55h,0aah + +; Name we look for. ntldr_length is the number of characters, +; ntldr_name is the name itself. Note that it is not NULL +; terminated, and doesn't need to be. +; +ntldr_name_length dw 5 +ntldr_name dw 'N', 'T', 'L', 'D', 'R' + +; Predefined name for index-related attributes associated with an +; index over $FILE_NAME +; +index_name_length dw 4 +index_name dw '$', 'I', '3', '0' + +; Global variables. These offsets are all relative to NewSeg. +; +AttrList dd 0e000h; Offset of buffer to hold attribute list +MftFrs dd 3000h; Offset of first MFT FRS +SegmentsInMft dd ? ; number of FRS's with MFT Data attribute records +RootIndexFrs dd ? ; Offset of Root Index FRS +AllocationIndexFrs dd ? ; Offset of Allocation Index FRS ; KPeery +BitmapIndexFrs dd ? ; Offset of Bitmap Index FRS ; KPeery +IndexRoot dd ? ; Offset of Root Index $INDEX_ROOT attribute +IndexAllocation dd ? ; Offset of Root Index $INDEX_ALLOCATION attribute +IndexBitmap dd ? ; Offset of Root Index $BITMAP attribute +NtldrFrs dd ? ; Offset of NTLDR FRS +NtldrData dd ? ; Offset of NTLDR $DATA attribute +IndexBlockBuffer dd ? ; Offset of current index buffer +IndexBitmapBuffer dd ? ; Offset of index bitmap buffer +NextBuffer dd ? ; Offset of next free byte in buffer space + +BytesPerCluster dd ? ; Bytes per cluster +BytesPerFrs dd ? ; Bytes per File Record Segment +SectorsPerFrs dd ? ; Sectors per File Record Segment +BytesPerIndexBlock dd ? ; Bytes per index alloc block in root index +ClustersPerIndexBlock dd ? ; Clusters per index alloc block in root index +SectorsPerIndexBlock dd ? ; Sectors per index block in root index + +.386 + +SAVE_ALL macro + + push es + push ds + pushad + +endm + +RESTORE_ALL macro + + popad + nop + pop ds + pop es + +endm + + +;**************************************************************************** +; +; mainboot - +; +; +mainboot proc far + +; Get the new ds and the new stack. Note that ss is zero. +; + mov ax, cs ; Set DS to CS + mov ds, ax + + shl ax, 4 ; convert to an offset. + cli + mov sp, ax ; load new stack, just before boot code. + sti + +; Set up the FRS buffers. The MFT buffer is in a fixed +; location, and the other three come right after it. The +; buffer for index allocation blocks comes after that. +; + +; Compute the useful constants associated with the volume +; + movzx eax, BytesPerSector ; eax = Bytes per Sector + movzx ebx, SectorsPerCluster ; ebx = Sectors Per Cluster + mul ebx ; eax = Bytes per Cluster + mov BytesPerCluster, eax + + mov ecx, ClustersPerFrs ; ecx = clusters per frs + cmp cl, 0 ; is ClustersPerFrs less than zero? + jg mainboot$1 + +; If the ClustersPerFrs field is negative, we calculate the number +; of bytes per FRS by negating the value and using that as a shif count. +; + + neg cl + mov eax, 1 + shl eax, cl ; eax = bytes per frs + jmp mainboot$2 + +mainboot$1: + +; Otherwise if ClustersPerFrs was positive, we multiply by bytes +; per cluster. + + mov eax, BytesPerCluster + mul ecx ; eax = bytes per frs + +mainboot$2: + + mov BytesPerFrs, eax + movzx ebx, BytesPerSector + xor edx, edx ; zero high part of dividend + div ebx ; eax = sectors per frs + mov SectorsPerFrs, eax + + +; Set up the MFT FRS's---this will read all the $DATA attribute +; records for the MFT. +; + + call SetupMft + +; Set up the remaining FRS buffers. The RootIndex FRS comes +; directly after the last MFT FRS, followed by the NTLdr FRS +; and the Index Block buffer. +; + mov ecx, NextBuffer + mov RootIndexFrs, ecx + + add ecx, BytesPerFrs ; AllocationFrs may be different + mov AllocationIndexFrs, ecx ; from RootIndexFrs - KPeery + + add ecx, BytesPerFrs ; BitmapFrs may be different + mov BitmapIndexFrs, ecx ; from RootIndexFrs - KPeery + + add ecx, BytesPerFrs + mov NtldrFrs, ecx + + add ecx, BytesPerFrs + mov IndexBlockBuffer, ecx + +; +; Read the root index, allocation index and bitmap FRS's and locate +; the interesting attributes. +; + + mov eax, $INDEX_ROOT + mov ecx, RootIndexFrs + call LoadIndexFrs + + or eax, eax + jz BootErr$he + + mov IndexRoot, eax ; offset in Frs buffer + + mov eax, $INDEX_ALLOCATION ; Attribute type code + mov ecx, AllocationIndexFrs ; FRS to search + call LoadIndexFrs + + mov IndexAllocation, eax + + mov eax, $BITMAP ; Attribute type code + mov ecx, BitmapIndexFrs ; FRS to search + call LoadIndexFrs + + mov IndexBitmap, eax + +; Consistency check: the index root must exist, and it +; must be resident. +; + mov eax, IndexRoot + or eax, eax + jz BootErr$he + + + cmp [eax].ATTR_FormCode, RESIDENT_FORM + jne BootErr$he + + +; Determine the size of the index allocation buffer based +; on information in the $INDEX_ROOT attribute. The index +; bitmap buffer comes immediately after the index block buffer. +; +; eax -> $INDEX_ROOT attribute record +; + lea edx, [eax].ATTR_FormUnion ; edx -> resident info + add ax, [edx].RES_ValueOffset ; eax -> value of $INDEX_ROOT + + movzx ecx, [eax].IR_ClustersPerBuffer + mov ClustersPerIndexBlock, ecx + + mov ecx, [eax].IR_BytesPerBuffer + mov BytesPerIndexBlock, ecx + + mov eax, BytesPerIndexBlock + movzx ecx, BytesPerSector + xor edx, edx + div ecx ; eax = sectors per index block + mov SectorsPerIndexBlock, eax + + mov eax, IndexBlockBuffer + add eax, BytesPerIndexBlock + mov IndexBitmapBuffer, eax + +; Next consistency check: if the $INDEX_ALLOCATION attribute +; exists, the $INDEX_BITMAP attribute must also exist. +; + cmp IndexAllocation, 0 + je mainboot30 + + cmp IndexBitmap, 0 ; since IndexAllocation exists, the + je BootErr$he ; bitmap must exist, too. + +; Since the bitmap exists, we need to read it into the bitmap +; buffer. If it's resident, we can just copy the data. +; + + mov ebx, IndexBitmap ; ebx -> index bitmap attribute + push ds + pop es + mov edi, IndexBitmapBuffer ; es:edi -> index bitmap buffer + + call ReadWholeAttribute + +mainboot30: +; +; OK, we've got the index-related attributes. +; + movzx ecx, ntldr_name_length ; ecx = name length in characters + mov eax, offset ntldr_name ; eax -> name + + call FindFile + + or eax, eax + jz BootErr$fnf + +; Read the FRS for NTLDR and find its data attribute. +; +; eax -> Index Entry for NTLDR. +; + mov eax, [eax].IE_FileReference.REF_LowPart + + + push ds + pop es ; es:edi = target buffer + mov edi, NtldrFrs + + call ReadFrs + + mov eax, NtldrFrs ; pointer to FRS + mov ebx, $DATA ; requested attribute type + mov ecx, 0 ; attribute name length in characters + mov edx, 0 ; attribute name (NULL if none) + + call LocateAttributeRecord + +; eax -> $DATA attribute for NTLDR +; + or eax, eax ; if eax is zero, attribute not found. + jz BootErr$fnf + +; Get the attribute record header flags, and make sure none of the +; `compressed' bits are set + + movzx ebx, [eax].ATTR_Flags + and ebx, ATTRIBUTE_FLAG_COMPRESSION_MASK + jnz BootErr$ntc + + mov ebx, eax ; ebx -> $DATA attribute for NTLDR + + push LdrSeg + pop es ; es = segment addres to read into + sub edi, edi ; es:edi = buffer address + + call ReadWholeAttribute + +; +; We've loaded NTLDR--jump to it. +; +; Before we go to NTLDR, set up the registers the way it wants them: +; DL = INT 13 drive number we booted from +; + mov dl, DriveNumber + mov ax,1000 + mov es, ax ; we don't really need this + lea si, BPB + sub ax,ax + push LdrSeg + push ax + retf ; "return" to NTLDR. + + +mainboot endp + +;**************************************************************************** +; +; DoRead - read SectorCount sectors into ES:BX starting from sector +; SectorBase. +; +; NOTE: This code WILL NOT WORK if ES:BX does not point to an address whose +; physical address (ES * 16 + BX) MOD 512 != 0. +; +; DoRead adds to ES rather than BX in the main loop so that runs longer than +; 64K can be read with a single call to DoRead. +; +; Note that DoRead (unlike DoReadLL) saves and restores SectorCount +; and SectorBase +; +.286 +DoRead proc + push ax ; save important registers + push bx + push cx + push dx + push es + push SectorCount ; save state variables too + push SectorBase.lsw + push SectorBase.msw +; +; Calculate how much we can read into what's left of the current 64k +; physical address block, and read it. +; +; + mov ax,bx + + shr ax,4 + mov cx,es + add ax,cx ; ax = paragraph addr + +; +; Now calc maximum number of paragraphs that we can read safely: +; 4k - ( ax mod 4k ) +; + + and ax,0fffh + sub ax,1000h + neg ax + +; +; Calc CX = number of paragraphs to be read +; + mov cx,SectorCount ; convert SectorCount to paragraph cnt + shl cx,9-4 + +DoRead$Loop64: + push cx ; save cpRead + + cmp ax,cx ; ax = min(cpReadSafely, cpRead) + jbe @F + mov ax,cx +@@: + push ax +; +; Calculate new SectorCount from amount we can read +; + shr ax,9-4 + mov SectorCount,ax + + call DoReadLL + + pop ax ; ax = cpActuallyRead + pop cx ; cx = cpRead + + sub cx,ax ; Any more to read? + jbe DoRead$Exit64 ; Nope. +; +; Adjust ES:BX by amount read +; + mov dx,es + add dx,ax + mov es,dx +; +; Since we're now reading on a 64k byte boundary, cpReadSafely == 4k. +; + mov ax,01000h ; 16k paragraphs per 64k segment + jmp short DoRead$Loop64 ; and go read some more. + +DoRead$Exit64: + pop SectorBase.msw ; restore all this crap + pop SectorBase.lsw + pop SectorCount + pop es + pop dx + pop cx + pop bx + pop ax + ret +DoRead endp + +.386 +;**************************************************************************** +; +; ReadClusters - Reads a run of clusters from the disk. +; +; ENTRY: eax == LCN to read +; edx == clusters to read +; es:edi -> Target buffer +; +; USES: none (preserves all registers) +; +ReadClusters proc near + + SAVE_ALL + + mov ebx, edx ; ebx = clusters to read. + movzx ecx, SectorsPerCluster ; ecx = cluster factor + + mul ecx ; Convert LCN to sectors (wipes out edx!) + mov SectorBase, eax ; Store starting sector in SectorBase + + mov eax, ebx ; eax = number of clusters + mul ecx ; Convert EAX to sectors (wipes out edx!) + mov SectorCount, ax ; Store number of sectors in SectorCount + + +; Note that ReadClusters gets its target buffer in es:edi but calls +; the DoRead worker function that takes a target in es:bx--we need +; to normalize es:edi so that we don't overflow bx. +; + mov bx, di + and bx, 0Fh + mov ax, es + shr edi, 4 + add ax, di ; ax:bx -> target buffer + + push ax + pop es ; es:bx -> target buffer + + call DoRead + + RESTORE_ALL + ret + +ReadClusters endp + +; +;**************************************************************************** +; +; LocateAttributeRecord -- Find an attribute record in an FRS. +; +; ENTRY: EAX -- pointer to FRS +; EBX -- desired attribute type code +; ECX -- length of attribute name in characters +; EDX -- pointer to attribute name +; +; EXIT: EAX points at attribute record (0 indicates not found) +; +; USES: All +; +LocateAttributeRecord proc near + +; get the first attribute record. +; + add ax, word ptr[eax].FRS_FirstAttribute + +; eax -> next attribute record to investigate. +; ebx == desired type +; ecx == name length +; edx -> pointer to name +; +lar10: + cmp [eax].ATTR_TypeCode, 0ffffffffh + je lar99 + + cmp dword ptr[eax].ATTR_TypeCode, ebx + jne lar80 + +; this record is a potential match. Compare the names: +; +; eax -> candidate record +; ebx == desired type +; ecx == name length +; edx -> pointer to name +; + or ecx, ecx ; Did the caller pass in a name length? + jnz lar20 + +; We want an attribute with no name--the current record is +; a match if and only if it has no name. +; + cmp [eax].ATTR_NameLength, 0 + jne lar80 ; Not a match. + +; It's a match, and eax is set up correctly, so return. +; + ret + +; We want a named attribute. +; +; eax -> candidate record +; ebx == desired type +; ecx == name length +; edx -> pointer to name +; +lar20: + cmp cl, [eax].ATTR_NameLength + jne lar80 ; Not a match. + +; Convert name in current record to uppercase. +; + mov esi, eax + add si, word ptr[eax].ATTR_NameOffset + + call UpcaseName + +; eax -> candidate record +; ebx == desired type +; ecx == name length +; edx -> pointer to name +; esi -> Name in current record (upcased) +; + push ecx ; save cx + + push ds ; Copy data segment into es + pop es + mov edi, edx ; note that esi is already set up. + + repe cmpsw ; zero flag is set if equal + + pop ecx ; restore cx + + jnz lar80 ; not a match + +; eax points at a matching record. +; + ret + +; +; This record doesn't match; go on to the next. +; +; eax -> rejected candidate attribute record +; ebx == desired type +; ecx == Name length +; edx -> desired name +; +lar80: cmp [eax].ATTR_RecordLength, 0 ; if the record length is zero + je lar99 ; the FRS is corrupt. + + add eax, [eax].ATTR_RecordLength; Go to next record + jmp lar10 ; and try again + +; Didn't find it. +; +lar99: sub eax, eax + ret + +LocateAttributeRecord endp + +;**************************************************************************** +; +; LocateIndexEntry -- Find an index entry in a file name index +; +; ENTRY: EAX -> pointer to index header +; EBX -> file name to find +; ECX == length of file name in characters +; +; EXIT: EAX points at index entry. NULL to indicate failure. +; +; USES: All +; +LocateIndexEntry proc near + +; Convert the input name to upper-case +; + + mov esi, ebx + call UpcaseName + +; DEBUG CODE +; +; call PrintName +; call Debug2 +; +; END DEBUG CODE + + add eax, [eax].IH_FirstIndexEntry + +; EAX -> current entry +; EBX -> file name to find +; ECX == length of file name in characters +; +lie10: test [eax].IE_Flags, INDEX_ENTRY_END ; Is it the end entry? + jnz lie99 + + lea edx, [eax].IE_Value ; edx -> FILE_NAME attribute value + +; DEBUG CODE -- list file names as they are examined +; +; SAVE_ALL +; +; call Debug3 +; movzx ecx, [edx].FN_FileNameLength ; ecx = chars in name +; lea esi, [edx].FN_FileName ; esi -> name +; call PrintName +; +; RESTORE_ALL +; +; END DEBUG CODE + +; EAX -> current entry +; EBX -> file name to find +; ECX == length of file name in characters +; EDX -> FILE_NAME attribute +; + cmp cl, [edx].FN_FileNameLength ; Is name the right length? + jne lie80 + + lea esi, [edx].FN_FileName ; Get name from FILE_NAME structure + + call UpcaseName + + push ecx ; save ecx + + push ds + pop es ; copy data segment into es for cmpsw + mov edi, ebx ; edi->search name (esi already set up) + repe cmpsw ; zero flag is set if they're equal + + pop ecx ; restore ecx + + jnz lie80 + +; the current entry matches the search name, and eax points at it. +; + ret + +; The current entry is not a match--get the next one. +; EAX -> current entry +; EBX -> file name to find +; ECX == length of file name in characters +; +lie80: cmp [eax].IE_Length, 0 ; If the entry length is zero + je lie99 ; then the index block is corrupt. + + add ax, [eax].IE_Length ; Get the next entry. + + jmp lie10 + + +; Name not found in this block. Set eax to zero and return +; +lie99: xor eax, eax + ret + +LocateIndexEntry endp + +;**************************************************************************** +; +; ReadWholeAttribute - Read an entire attribute value +; +; ENTRY: ebx -> attribute +; es:edi -> target buffer +; +; USES: ALL +; +ReadWholeAttribute proc near + + cmp [ebx].ATTR_FormCode, RESIDENT_FORM + jne rwa10 + +; The attribute is resident. +; ebx -> attribute +; es:edi -> target buffer +; + + SAVE_ALL + + lea edx, [ebx].ATTR_FormUnion ; edx -> resident form info + mov ecx, [edx].RES_ValueLength ; ecx = bytes in value + mov esi, ebx ; esi -> attribute + add si, [edx].RES_ValueOffset ; esi -> attribute value + + rep movsb ; copy bytes from value to buffer + + RESTORE_ALL + + ret ; That's all! + +rwa10: +; +; The attribute type is non-resident. Just call +; ReadNonresidentAttribute starting at VCN 0 and +; asking for the whole thing. +; +; ebx -> attribute +; es:edi -> target buffer +; + lea edx, [ebx].ATTR_FormUnion ; edx -> nonresident form info + mov ecx, [edx].NONRES_HighestVcn.LowPart; ecx = HighestVcn + inc ecx ; ecx = clusters in attribute + + sub eax, eax ; eax = 0 (first VCN to read) + + call ReadNonresidentAttribute + + ret + +ReadWholeAttribute endp + +;**************************************************************************** +; +; ReadNonresidentAttribute - Read clusters from a nonresident attribute +; +; ENTRY: EAX == First VCN to read +; EBX -> Attribute +; ECX == Number of clusters to read +; ES:EDI == Target of read +; +; EXIT: None. +; +; USES: None (preserves all registers with SAVE_ALL/RESTORE_ALL) +; +ReadNonresidentAttribute proc near + + SAVE_ALL + + cmp [ebx].ATTR_FormCode, NONRESIDENT_FORM + je ReadNR10 + +; This attribute is not resident--the disk is corrupt. + + jmp BootErr$he + + +ReadNR10: +; eax == Next VCN to read +; ebx -> Attribute +; ecx -> Remaining clusters to read +; es:edi -> Target of read +; + + cmp ecx, 0 + jne ReadNR20 + +; Nothing left to read--return success. +; + RESTORE_ALL + ret + +ReadNR20: + push ebx ; pointer to attribute + push eax ; Current VCN + + push ecx + push edi + push es + + call ComputeLcn ; eax = LCN to read, ecx = run length + mov edx, ecx ; edx = remaining run length + + pop es + pop edi + pop ecx + + +; eax == LCN to read +; ecx == remaining clusters to read +; edx == remaining clusters in current run +; es:edi == Target of read +; TOS == Current VCN +; TOS + 4 == pointer to attribute +; + cmp ecx, edx + jge ReadNR30 + +; Run length is greater than remaining request; only read +; remaining request. +; + mov edx, ecx ; edx = Remaining request + +ReadNR30: +; eax == LCN to read +; ecx == remaining clusters to read +; edx == clusters to read in current run +; es:edi == Target of read +; TOS == Current VCN +; TOS + == pointer to attribute +; + + call ReadClusters + + sub ecx, edx ; Decrement clusters remaining in request + mov ebx, edx ; ebx = clusters read + + mov eax, edx ; eax = clusters read + movzx edx, SectorsPerCluster + mul edx ; eax = sectors read (wipes out edx!) + movzx edx, BytesPerSector + mul edx ; eax = bytes read (wipes out edx!) + + add edi, eax ; Update target of read + + pop eax ; eax = previous VCN + add eax, ebx ; update VCN to read + + pop ebx ; ebx -> attribute + jmp ReadNR10 + + +ReadNonresidentAttribute endp + +;**************************************************************************** +; +; ReadIndexBlockSectors - Read sectors from an index allocation attribute +; +; ENTRY: EAX == First VBN to read +; EBX -> Attribute +; ECX == Number of sectors to read +; ES:EDI == Target of read +; +; EXIT: None. +; +; USES: None (preserves all registers with SAVE_ALL/RESTORE_ALL) +; +ReadIndexBlockSectors proc near + + SAVE_ALL + + cmp [ebx].ATTR_FormCode, NONRESIDENT_FORM + je ReadIBS_10 + +; This attribute is resident--the disk is corrupt. + + jmp BootErr$he + + +ReadIBS_10: +; eax == Next VBN to read +; ebx -> Attribute +; ecx -> Remaining sectors to read +; es:edi -> Target of read +; + + cmp ecx, 0 + jne ReadIBS_20 + +; Nothing left to read--return success. +; + + + RESTORE_ALL + ret + +ReadIBS_20: + push ebx ; pointer to attribute + push eax ; Current VBN + + push ecx + push edi + push es + + ; Convert eax from a VBN back to a VCN by dividing by SectorsPerCluster. + ; The remainder of this division is the sector offset in the cluster we + ; want. Then use the mapping information to get the LCN for this VCN, + ; then multiply to get back to LBN. + ; + + push ecx ; save remaining sectors in request + + xor edx, edx ; zero high part of dividend + movzx ecx, SectorsPerCluster + div ecx ; edx = remainder + push edx ; save remainder + + call ComputeLcn ; eax = LCN to read, ecx = remaining run length + + movzx ebx, SectorsPerCluster + mul ebx ; eax = LBN of cluster, edx = 0 + pop edx ; edx = remainder + add eax, edx ; eax = LBN we want + push eax ; save LBN + + movzx eax, SectorsPerCluster + mul ecx ; eax = remaining run length in sectors, edx = 0 + mov edx, eax ; edx = remaining run length + + pop eax ; eax = LBN + pop ecx ; ecx = remaining sectors in request + + pop es + pop edi + pop ecx + + +; eax == LBN to read +; ecx == remaining sectors to read +; edx == remaining sectors in current run +; es:edi == Target of read +; TOS == Current VCN +; TOS + 4 == pointer to attribute +; + cmp ecx, edx + jge ReadIBS_30 + +; Run length is greater than remaining request; only read +; remaining request. +; + mov edx, ecx ; edx = Remaining request + +ReadIBS_30: +; eax == LBN to read +; ecx == remaining sectors to read +; edx == sectors to read in current run +; es:edi == Target of read +; TOS == Current VCN +; TOS + == pointer to attribute +; + + mov SectorBase, eax + mov SectorCount, dx + +; We have a pointer to the target buffer in es:edi, but we want that +; in es:bx for DoRead. +; + + SAVE_ALL + + mov bx, di + and bx, 0Fh + mov ax, es + shr edi, 4 + add ax, di ; ax:bx -> target buffer + + push ax + pop es ; es:bx -> target buffer + + call DoRead + + RESTORE_ALL + + sub ecx, edx ; Decrement sectors remaining in request + mov ebx, edx ; ebx = sectors read + + mov eax, edx ; eax = sectors read + movzx edx, BytesPerSector + mul edx ; eax = bytes read (wipes out edx!) + + add edi, eax ; Update target of read + + pop eax ; eax = previous VBN + add eax, ebx ; update VBN to read + + pop ebx ; ebx -> attribute + jmp ReadIBS_10 + + +ReadIndexBlockSectors endp + + +;**************************************************************************** +; +; MultiSectorFixup - fixup a structure read off the disk +; to reflect Update Sequence Array. +; +; ENTRY: ES:EDI = Target buffer +; +; USES: none (preserves all registers with SAVE_ALL/RESTORE_ALL) +; +; Note: ES:EDI must point at a structure which is protected +; by an update sequence array, and which begins with +; a multi-sector-header structure. +; +MultiSectorFixup proc near + + SAVE_ALL + + movzx ebx, es:[edi].MSH_UpdateArrayOfs ; ebx = update array offset + movzx ecx, es:[edi].MSH_UpdateArraySize ; ecx = update array size + + or ecx, ecx ; if the size of the update sequence array + jz BootErr$he ; is zero, this structure is corrupt. + + add ebx, edi ; es:ebx -> update sequence array count word + add ebx, 2 ; es:ebx -> 1st entry of update array + + add edi, SEQUENCE_NUMBER_STRIDE - 2 ; es:edi->last word of first chunk + dec ecx ; decrement to reflect count word + +MSF10: + +; ecx = number of entries remaining in update sequence array +; es:ebx -> next entry in update sequence array +; es:edi -> next target word for update sequence array + + or ecx, ecx + jz MSF30 + + mov ax, word ptr es:[ebx] ; copy next update sequence array entry + mov word ptr es:[edi], ax ; to next target word + + add ebx, 2 ; go on to next entry + add edi, SEQUENCE_NUMBER_STRIDE ; go on to next target + + dec ecx + + + jmp MSF10 + +MSF30: + + RESTORE_ALL + + ret + +MultiSectorFixup endp + +;**************************************************************************** +; +; SetupMft - Reads MFT File Record Segments into memory. +; +; ENTRY: none. +; +; EXIT: NextBuffer is set to the free byte after the last MFT FRS +; SegmentsInMft is initialized +; +; +SetupMft proc near + + SAVE_ALL + +; Initialize SegmentsInMft and NextBuffer as if the MFT +; had only one FRS. +; + mov eax, 1 + mov SegmentsInMft, eax + + mov eax, MftFrs + add eax, BytesPerFrs + mov NextBuffer, eax + +; Read FRS 0 into the first MFT FRS buffer, being sure +; to resolve the Update Sequence Array. +; + + mov eax, MftStartLcn.LowPart + movzx ebx, SectorsPerCluster + mul ebx ; eax = mft starting sector + mov SectorBase, eax ; SectorBase = mft starting sector + + mov eax, SectorsPerFrs + mov SectorCount, ax ; SectorCount = SectorsPerFrs + + mov ebx, MftFrs + + push ds + pop es + + call DoRead + movzx edi, bx ; es:edi = buffer + call MultiSectorFixup + +; Determine whether the MFT has an Attribute List attribute + + mov eax, MftFrs + mov ebx, $ATTRIBUTE_LIST + mov ecx, 0 + mov edx, 0 + + call LocateAttributeRecord + + or eax, eax ; If there's no Attribute list, + jz SetupMft99 ; we're done! + +; Read the attribute list. +; eax -> attribute list attribute +; + mov ebx, eax ; ebx -> attribute list attribute + push ds + pop es ; copy ds into es + mov edi, AttrList ; ds:edi->attribute list buffer + + call ReadWholeAttribute + + mov ebx, AttrList ; ebx -> first attribute list entry + +; Now, traverse the attribute list looking for the first +; entry for the $DATA type. We know it must have at least +; one. +; +; ebx -> first attribute list entry +; +SetupMft10: + cmp [ebx].ATTRLIST_TypeCode, $DATA + je SetupMft20 + + add bx,[ebx].ATTRLIST_Length + jmp SetupMft10 + + +SetupMft20: +; Scan forward through the attribute list entries for the +; $DATA attribute, reading each referenced FRS. Note that +; there will be at least one non-$DATA entry after the entries +; for the $DATA attribute, since there's a $BITMAP. +; +; ebx -> Next attribute list entry +; NextBuffer -> Target for next read +; SegmentsInMft == number of MFT segments read so far +; + cmp [ebx].ATTRLIST_TypeCode, $DATA + jne SetupMft99 + +; Read the FRS referred to by this attribute list entry into +; the next buffer, and increment NextBuffer and SegmentsInMft. +; + push ebx + + mov eax, [ebx].ATTRLIST_SegmentReference.REF_LowPart + mov edi, NextBuffer + push ds + pop es ; copy ds into es + + call ReadFrs + + pop ebx + +; Increment NextBuffer and SegmentsInMft + + mov eax, BytesPerFrs + add NextBuffer, eax + + inc SegmentsInMft + +; Go on to the next attribute list entry + + add bx, [ebx].ATTRLIST_Length + jmp SetupMft20 + +SetupMft99: + + RESTORE_ALL + ret + +SetupMft endp + +;**************************************************************************** +; +; ComputeMftLcn -- Computes the LCN for a cluster of the MFT +; +; +; ENTRY: EAX == VCN +; +; EXIT: EAX == LCN +; +; USES: ALL +; +ComputeMftLcn proc near + + mov edx, eax ; edx = VCN + + mov ecx, SegmentsInMft ; ecx = # of FRS's to search + mov eax, MftFrs ; eax -> first FRS to search + +MftLcn10: +; EAX -> Next FRS to search +; ECX == number of remaining FRS's to search +; EDX == VCN +; + push edx + push eax + push ecx + push edx ; Yes, I meant to push it twice + + mov ebx, $DATA + mov ecx, 0 + mov edx, 0 + + call LocateAttributeRecord + +; EAX -> $DATA attribute +; TOS == VCN +; TOS + 4 == number of remaining FRS's to search +; TOS + 8 -> FRS being searched +; TOS +12 == VCN + + or eax, eax + jz BootErr$he ; No $DATA attribute in this FRS! + + mov ebx, eax ; ebx -> attribute + pop eax ; eax = VCN + +; EAX == VCN +; EBX -> $DATA attribute +; TOS number of remaining FRS's to search +; TOS + 4 == FRS being searched +; TOS + 8 == VCN + + call ComputeLcn + + or eax, eax + jz MftLcn20 + +; Found our LCN. Clean up the stack and return. +; +; EAX == LCN +; TOS number of remaining FRS's to search +; TOS + 4 == FRS being searched +; TOS + 8 == VCN +; + pop ebx + pop ebx + pop ebx ; clean up the stack + + ret + +MftLcn20: +; +; Didn't find the VCN in this FRS; try the next one. +; +; TOS number of remaining FRS's to search +; TOS + 4 -> FRS being searched +; TOS + 8 == VCN +; + pop ecx ; ecx = number of FRS's remaining, including current + pop eax ; eax -> current FRS + pop edx ; edx = VCN + + add eax, BytesPerFrs ; eax -> next FRS + loop MftLcn10 ; decrement ecx and try next FRS + +; This VCN was not found. +; + xor eax, eax + ret + + +ComputeMftLcn endp + +;**************************************************************************** +; +; ReadMftSectors - Read sectors from the MFT +; +; ENTRY: EAX == starting VBN +; ECX == number of sectors to read +; ES:EDI == Target buffer +; +; USES: none (preserves all registers with SAVE_ALL/RESTORE_ALL) +; +ReadMftSectors proc near + + SAVE_ALL + +RMS$Again: + + push eax ; save starting VBN + push ecx ; save sector count + + +; Divide the VBN by SectorsPerCluster to get the VCN + + xor edx, edx ; zero high part of dividend + movzx ebx, SectorsPerCluster + div ebx ; eax = VCN + push edx ; save remainder + + call ComputeMftLcn ; eax = LCN + + or eax, eax ; LCN equal to zero? + jz BootErr$he ; zero is not a possible LCN + +; Change the LCN back into a LBN and add the remainder back in to get +; the sector we want to read, which goes into SectorBase. +; + + movzx ebx, SectorsPerCluster + mul ebx ; eax = cluster first LBN + pop edx ; edx = sector remainder + add eax, edx ; eax = desired LBN + + mov SectorBase, eax + +; +; Figure out how many sectors to read this time; we never attempt +; to read more than one cluster at a time. +; + + pop ecx ; ecx = sectors to read + + movzx ebx, SectorsPerCluster + cmp ecx,ebx + jle RMS10 + +; +; Read only a single cluster at a time, to avoid problems with fragmented +; runs in the mft. +; + + mov SectorCount, bx ; this time read 1 cluster + sub ecx, ebx ; ecx = sectors remaining to read + + pop eax ; eax = VBN + add eax, ebx ; VBN += sectors this read + + + push eax ; save next VBN + push ecx ; save remaining sector count + + + + jmp RMS20 + +RMS10: + + pop eax ; eax = VBN + add eax, ecx ; VBN += sectors this read + push eax ; save next VBN + + mov SectorCount, cx + mov ecx, 0 + push ecx ; save remaining sector count (0) + +RMS20: + + +; The target buffer was passed in es:edi, but we want it in es:bx. +; Do the conversion. +; + + push es ; save buffer pointer + push edi + + mov bx, di + and bx, 0Fh + mov ax, es + shr edi, 4 + add ax, di ; ax:bx -> target buffer + + push ax + pop es ; es:bx -> target buffer + + call DoRead + + pop edi ; restore buffer pointer + pop es + + add edi, BytesPerCluster ; increment buf ptr by one cluster + + pop ecx ; restore remaining sector count + pop eax ; restore starting VBN + + cmp ecx, 0 ; are we done? + jg RMS$Again ; repeat until desired == 0 + + + RESTORE_ALL + ret + +ReadMftSectors endp + + +;**************************************************************************** +; +; ReadFrs - Read an FRS +; +; ENTRY: EAX == FRS number +; ES:EDI == Target buffer +; +; USES: none (preserves all registers with SAVE_ALL/RESTORE_ALL) +; +ReadFrs proc near + + SAVE_ALL + + mul SectorsPerFrs ; eax = sector number in MFT DATA attribute + ; (note that mul wipes out edx!) + + mov ecx, SectorsPerFrs ; number of sectors to read + + call ReadMftSectors + call MultiSectorFixup + + RESTORE_ALL + ret + +ReadFrs endp + +;**************************************************************************** +; +; ReadIndexBlock - read an index block from the root index. +; +; ENTRY: EAX == Block number +; +; USES: none (preserves all registers with SAVE_ALL/RESTORE_ALL) +; +ReadIndexBlock proc near + + SAVE_ALL + + mul SectorsPerIndexBlock ; eax = first VBN to read + ; (note that mul wipes out edx!) + mov ebx, IndexAllocation ; ebx -> $INDEX_ALLOCATION attribute + mov ecx, SectorsPerIndexBlock ; ecx == Sectors to read + + push ds + pop es + mov edi, IndexBlockBuffer ; es:edi -> index block buffer + + call ReadIndexBlockSectors + call MultiSectorFixup + + RESTORE_ALL + ret + +ReadIndexBlock endp + +;**************************************************************************** +; +; IsBlockInUse - Checks the index bitmap to see if an index +; allocation block is in use. +; +; ENTRY: EAX == block number +; +; EXIT: Carry flag clear if block is in use +; Carry flag set if block is not in use. +; +IsBlockInUse proc near + + push eax + push ebx + push ecx + + mov ebx, IndexBitmapBuffer + + mov ecx, eax ; ecx = block number + shr eax, 3 ; eax = byte number + and ecx, 7 ; ecx = bit number in byte + + add ebx, eax ; ebx -> byte to test + + mov eax, 1 + shl eax, cl ; eax = mask + + test byte ptr[ebx], al + + jz IBU10 + + clc ; Block is not in use. + jmp IBU20 + +IBU10: stc ; Block is in use. + +IBU20: + pop ecx + pop ebx + pop eax ; restore registers + + ret + +IsBlockInUse endp + +;**************************************************************************** +; +; ComputeLcn - Converts a VCN into an LCN +; +; ENTRY: EAX -> VCN +; EBX -> Attribute +; +; EXIT: EAX -> LCN (zero indicates not found) +; ECX -> Remaining run length +; +; USES: ALL. +; +ComputeLcn proc near + + cmp [ebx].ATTR_FormCode, NONRESIDENT_FORM + je clcn10 + + sub eax, eax ; This is a resident attribute. + ret + +clcn10: lea esi, [ebx].ATTR_FormUnion ; esi -> nonresident info of attrib + +; eax -> VCN +; ebx -> Attribute +; esi -> Nonresident information of attribute record +; +; See if the desired VCN is in range. + + mov edx, [esi].NONRES_HighestVcn.LowPart ; edx = HighestVcn + cmp eax, edx + ja clcn15 ; VCN is greater than HighestVcn + + mov edx, [esi].NONRES_LowestVcn.LowPart ; edx = LowestVcn + cmp eax, edx + jae clcn20 + +clcn15: + sub eax, eax ; VCN is not in range + ret + +clcn20: +; eax -> VCN +; ebx -> Attribute +; esi -> Nonresident information of attribute record +; edx -> LowestVcn +; + add bx, [esi].NONRES_MappingPairOffset ; ebx -> mapping pairs + sub esi, esi ; esi = 0 + +clcn30: +; eax == VCN to find +; ebx -> Current mapping pair count byte +; edx == Current VCN +; esi == Current LCN +; + cmp byte ptr[ebx], 0 ; if count byte is zero... + je clcn99 ; ... we're done (and didn't find it) + +; Update CurrentLcn +; + call LcnFromMappingPair + add esi, ecx ; esi = current lcn for this mapping pair + + call VcnFromMappingPair + +; eax == VCN to find +; ebx -> Current mapping pair count byte +; ecx == DeltaVcn for current mapping pair +; edx == Current VCN +; esi == Current LCN +; + add ecx, edx ; ecx = NextVcn + + cmp eax, ecx ; If target < NextVcn ... + jl clcn80 ; ... we found the right mapping pair. + +; Go on to next mapping pair. +; + mov edx, ecx ; CurrentVcn = NextVcn + + push eax + + movzx ecx, byte ptr[ebx] ; ecx = count byte + mov eax, ecx ; eax = count byte + and eax, 0fh ; eax = number of vcn bytes + shr ecx, 4 ; ecx = number of lcn bytes + + add ebx, ecx + add ebx, eax + inc ebx ; ebx -> next count byte + + pop eax + jmp clcn30 + +clcn80: +; We found the mapping pair we want. +; +; eax == target VCN +; ebx -> mapping pair count byte +; edx == Starting VCN of run +; ecx == Next VCN (ie. start of next run) +; esi == starting LCN of run +; + sub ecx, eax ; ecx = remaining run length + sub eax, edx ; eax = offset into run + add eax, esi ; eax = LCN to return + + ret + +; The target VCN is not in this attribute. + +clcn99: sub eax, eax ; Not found. + ret + + +ComputeLcn endp + +;**************************************************************************** +; +; VcnFromMappingPair +; +; ENTRY: EBX -> Mapping Pair count byte +; +; EXIT: ECX == DeltaVcn from mapping pair +; +; USES: ECX +; +VcnFromMappingPair proc near + + sub ecx, ecx ; ecx = 0 + mov cl, byte ptr[ebx] ; ecx = count byte + and cl, 0fh ; ecx = v + + cmp ecx, 0 ; if ecx is zero, volume is corrupt. + jne VFMP5 + + sub ecx, ecx + ret + +VFMP5: + push ebx + push edx + + add ebx, ecx ; ebx -> last byte of compressed vcn + + movsx edx, byte ptr[ebx] + dec ecx + dec ebx + +; ebx -> Next byte to add in +; ecx == Number of bytes remaining +; edx == Accumulated value +; +VFMP10: cmp ecx, 0 ; When ecx == 0, we're done. + je VFMP20 + + shl edx, 8 + mov dl, byte ptr[ebx] + + dec ebx ; Back up through bytes to process. + dec ecx ; One less byte to process. + + jmp VFMP10 + +VFMP20: +; edx == Accumulated value to return + + mov ecx, edx + + pop edx + pop ebx + + ret + +VcnFromMappingPair endp + + +;**************************************************************************** +; +; LcnFromMappingPair +; +; ENTRY: EBX -> Mapping Pair count byte +; +; EXIT: ECX == DeltaLcn from mapping pair +; +; USES: ECX +; +LcnFromMappingPair proc near + + push ebx + push edx + + sub edx, edx ; edx = 0 + mov dl, byte ptr[ebx] ; edx = count byte + and edx, 0fh ; edx = v + + sub ecx, ecx ; ecx = 0 + mov cl, byte ptr[ebx] ; ecx = count byte + shr cl, 4 ; ecx = l + + cmp ecx, 0 ; if ecx is zero, volume is corrupt. + jne LFMP5 + + sub ecx, ecx + + pop edx + pop ebx + ret + +LFMP5: +; ebx -> count byte +; ecx == l +; edx == v +; + + add ebx, edx ; ebx -> last byte of compressed vcn + add ebx, ecx ; ebx -> last byte of compressed lcn + + movsx edx, byte ptr[ebx] + dec ecx + dec ebx + +; ebx -> Next byte to add in +; ecx == Number of bytes remaining +; edx == Accumulated value +; +LFMP10: cmp ecx, 0 ; When ecx == 0, we're done. + je LFMP20 + + shl edx, 8 + mov dl, byte ptr[ebx] + + dec ebx ; Back up through bytes to process. + dec ecx ; One less byte to process. + + jmp LFMP10 + +LFMP20: +; edx == Accumulated value to return + + mov ecx, edx + + pop edx + pop ebx + + ret + +LcnFromMappingPair endp + +;**************************************************************************** +; +; UpcaseName - Converts the name of the file to all upper-case +; +; ENTRY: ESI -> Name +; ECX -> Length of name +; +; USES: none +; +UpcaseName proc near + + + or ecx, ecx + jnz UN5 + + ret + +UN5: + push ecx + push esi + +UN10: + cmp word ptr[esi], 'a' ; if it's less than 'a' + jl UN20 ; leave it alone + + cmp word ptr[esi], 'z' ; if it's greater than 'z' + jg UN20 ; leave it alone. + + sub word ptr[esi], 'a'-'A' ; the letter is lower-case--convert it. +UN20: + add esi, 2 ; move on to next unicode character + loop UN10 + + pop esi + pop ecx + + ret +UpcaseName endp + +;**************************************************************************** +; +; FindFile - Locates the index entry for a file in the root index. +; +; ENTRY: EAX -> name to find +; ECX == length of file name in characters +; +; EXIT: EAX -> Index Entry. NULL to indicate failure. +; +; USES: ALL +; +FindFile proc near + + push eax ; name address + push ecx ; name length + +; First, search the index root. +; +; eax -> name to find +; ecx == name length +; TOS == name length +; TOS+4 -> name to find +; + mov edx, eax ; edx -> name to find + mov eax, IndexRoot ; eax -> &INDEX_ROOT attribute + lea ebx, [eax].ATTR_FormUnion ; ebx -> resident info + add ax, [ebx].RES_ValueOffset ; eax -> Index Root value + + lea eax, [eax].IR_IndexHeader ; eax -> Index Header + + mov ebx, edx ; ebx -> name to find + + call LocateIndexEntry + + or eax, eax + jz FindFile20 + +; Found it in the root! The result is already in eax. +; Clean up the stack and return. +; + pop ecx + pop ecx + ret + +FindFile20: +; +; We didn't find the index entry we want in the root, so we have to +; crawl through the index allocation buffers. +; +; TOS == name length +; TOS+4 -> name to find +; + mov eax, IndexAllocation + or eax, eax + jnz FindFile30 + +; There is no index allocation attribute; clean up +; the stack and return failure. +; + pop ecx + pop ecx + xor eax, eax + ret + +FindFile30: +; +; Search the index allocation blocks for the name we want. +; Instead of searching in tree order, we'll just start with +; the last one and work our way backwards. +; +; TOS == name length +; TOS+4 -> name to find +; + mov edx, IndexAllocation ; edx -> index allocation attr. + lea edx, [edx].ATTR_FormUnion ; edx -> nonresident form info + mov eax, [edx].NONRES_HighestVcn.LowPart; eax = HighestVcn + inc eax ; eax = clusters in attribute + + mov ebx, BytesPerCluster + mul ebx ; eax = bytes in attribute + + xor edx, edx + div BytesPerIndexBlock ; convert bytes to index blocks + + push eax ; number of blocks to process + +FindFile40: +; +; TOS == remaining index blocks to search +; TOS + 4 == name length +; TOS + 8 -> name to find +; + pop eax ; eax == number of remaining blocks + + or eax, eax + jz FindFile90 + + dec eax ; eax == number of next block to process + ; and number of remaining blocks + + push eax + +; eax == block number to process +; TOS == remaining index blocks to search +; TOS + 4 == name length +; TOS + 8 -> name to find +; +; See if the block is in use; if not, go on to next. + + call IsBlockInUse + jc FindFile40 ; c set if not in use + +; eax == block number to process +; TOS == remaining index blocks to search +; TOS + 4 == name length +; TOS + 8 -> name to find +; + + call ReadIndexBlock + + pop edx ; edx == remaining buffers to search + pop ecx ; ecx == name length + pop ebx ; ebx -> name + + push ebx + push ecx + push edx + +; ebx -> name to find +; ecx == name length in characters +; TOS == remaining blocks to process +; TOS + 4 == name length +; TOS + 8 -> name +; +; Index buffer to search is in index allocation block buffer. +; + mov eax, IndexBlockBuffer ; eax -> Index allocation block + lea eax, [eax].IB_IndexHeader ; eax -> Index Header + + call LocateIndexEntry ; eax -> found entry + + or eax, eax + jz FindFile40 + +; Found it! +; +; eax -> Found entry +; TOS == remaining blocks to process +; TOS + 4 == name length +; TOS + 8 -> name +; + pop ecx + pop ecx + pop ecx ; clean up stack + ret + +FindFile90: +; +; Name not found. +; +; TOS == name length +; TOS + 4 -> name to find +; + pop ecx + pop ecx ; clean up stack. + xor eax, eax ; zero out eax. + ret + + +FindFile endp + +;**************************************************************************** +; +; DumpIndexBlock - dumps the index block buffer +; +DumpIndexBlock proc near + + SAVE_ALL + + mov esi, IndexBlockBuffer + + mov ecx, 20h ; dwords to dump + +DIB10: + + test ecx, 3 + jnz DIB20 + call DebugNewLine + +DIB20: + + lodsd + call PrintNumber + loop DIB10 + + RESTORE_ALL + ret + +DumpIndexBlock endp + +;**************************************************************************** +; +; DebugNewLine +; +DebugNewLine proc near + + SAVE_ALL + + xor eax, eax + xor ebx, ebx + + mov al, 0dh + mov ah, 14 + mov bx, 7 + int 10h + + mov al, 0ah + mov ah, 14 + mov bx, 7 + int 10h + + RESTORE_ALL + ret + +DebugNewLine endp + + +;**************************************************************************** +; +; PrintName - Display a unicode name +; +; ENTRY: DS:ESI -> null-terminated string +; ECX == characters in string +; +; USES: None. +; +PrintName proc near + + + SAVE_ALL + + or ecx, ecx + jnz PrintName10 + + call DebugNewLine + + RESTORE_ALL + + ret + +PrintName10: + + xor eax, eax + xor ebx, ebx + + lodsw + + mov ah, 14 ; write teletype + mov bx, 7 ; attribute + int 10h ; print it + loop PrintName10 + + call DebugNewLine + + RESTORE_ALL + ret + +PrintName endp + +;**************************************************************************** +; +; DebugPrint - Display a debug string. +; +; ENTRY: DS:SI -> null-terminated string +; +; USES: None. +; +.286 +DebugPrint proc near + + pusha + +DbgPr20: + + lodsb + cmp al, 0 + je DbgPr30 + + mov ah, 14 ; write teletype + mov bx, 7 ; attribute + int 10h ; print it + jmp DbgPr20 + +DbgPr30: + + popa + nop + ret + +DebugPrint endp + +;**************************************************************************** +; +; +; PrintNumber +; +; ENTRY: EAX == number to print +; +; PRESERVES ALL REGISTERS +; +.386 +PrintNumber proc near + + + SAVE_ALL + + mov ecx, 8 ; number of digits in a DWORD + +PrintNumber10: + + mov edx, eax + and edx, 0fh ; edx = lowest-order digit + push edx ; put it on the stack + shr eax, 4 ; drop low-order digit + loop PrintNumber10 + + mov ecx, 8 ; number of digits on stack. + +PrintNumber20: + + pop eax ; eax = next digit to print + cmp eax, 9 + jg PrintNumber22 + + add eax, '0' + jmp PrintNumber25 + +PrintNumber22: + + sub eax, 10 + add eax, 'A' + +PrintNumber25: + + xor ebx, ebx + + mov ah, 14 + mov bx, 7 + int 10h + loop PrintNumber20 + +; Print a space to separate numbers + + mov al, ' ' + mov ah, 14 + mov bx, 7 + int 10h + + RESTORE_ALL + + call Pause + + ret + +PrintNumber endp + + +;**************************************************************************** +; +; Debug0 - Print debug string 0 -- used for checkpoints in mainboot +; +Debug0 proc near + + SAVE_ALL + + mov esi, offset DbgString0 + call BootErr$Print + + RESTORE_ALL + + ret + +Debug0 endp + +;**************************************************************************** +; +; Debug1 - Print debug string 1 -- +; +Debug1 proc near + + SAVE_ALL + + mov esi, offset DbgString1 + call BootErr$Print + + RESTORE_ALL + + ret + +Debug1 endp + +;**************************************************************************** +; +; Debug2 - Print debug string 2 +; +Debug2 proc near + + SAVE_ALL + + mov esi, offset DbgString2 + call BootErr$Print + + RESTORE_ALL + + ret + +Debug2 endp + +;**************************************************************************** +; +; Debug3 - Print debug string 3 -- +; +Debug3 proc near + + SAVE_ALL + + mov esi, offset DbgString3 + call BootErr$Print + + RESTORE_ALL + + ret + +Debug3 endp + +;**************************************************************************** +; +; Debug4 - Print debug string 4 +; +Debug4 proc near + + SAVE_ALL + + mov esi, offset DbgString4 + call BootErr$Print + + RESTORE_ALL + + ret + +Debug4 endp + +;**************************************************************************** +; +; Pause - Pause for about 1/2 a second. Simply count until you overlap +; to zero. +; +Pause proc near + + push eax + mov eax, 0fff50000h + +PauseLoopy: + inc eax + + or eax, eax + jnz PauseLoopy + + pop eax + ret + +Pause endp + + +;************************************************************************* +; +; LoadIndexFrs - For the requested index type code locate and +; load the associated Frs. +; +; ENTRY: EAX - requested index type code +; ECX - Points to empty Frs buffer +; +; EXIT: EAX - points to offset in Frs buffer of requested index type +; code or Zero if not found. +; USES: All +; +LoadIndexFrs proc near + + push ecx ; save FRS buffer for later + push eax ; save index type code for later + + mov eax, ROOT_FILE_NAME_INDEX_NUMBER + push ds + pop es + mov edi, ecx ; es:edi = target buffer + + call ReadFrs + + mov eax, ecx ; FRS to search + + pop ebx ; Attribute type code + push ebx + movzx ecx, index_name_length ; Attribute name length + mov edx, offset index_name ; Attribute name + + call LocateAttributeRecord + + pop ebx + pop ecx + + or eax, eax + jnz LoadIndexFrs$Exit ; if found in root return + +; +; if not found in current Frs, search in attribute list +; + ; EBX - holds Attribute type code + mov eax, ecx ; FRS to search + mov ecx, ebx ; type code + push eax ; save Frs + push ebx ; save type code + + call SearchAttrList ; search attribute list for FRN + ; of specified ($INDEX_ROOT, + ; $INDEX_ALLOCATION, or $BITMAP) + + ; EAX - holds FRN for Frs, or Zero + + pop ebx ; Attribute type code (used later) + pop edi ; es:edi = target buffer + + or eax, eax ; if we cann't find it in attribute + jz LoadIndexFrs$Exit ; list then we are hosed + + +; We should now have the File Record Number where the index for the +; specified type code we are searching for is, load this into the +; Frs target buffer. +; +; EAX - holds FRN +; EBX - holds type code +; EDI - holds target buffer + + push ds + pop es + + call ReadFrs + +; +; Now determine the offset in the Frs of the index +; + +; EBX - holds type code + + mov eax, edi ; Frs to search + movzx ecx, index_name_length ; Attribute name length + mov edx, offset index_name ; Attribute name + + call LocateAttributeRecord + +; EAX - holds offset or Zero. + + +LoadIndexFrs$Exit: + ret + +LoadIndexFrs endp + + +;**************************************************************************** +; +; SearchAttrList +; +; Search the Frs for the attribute list. Then search the attribute list +; for the specifed type code. When you find it return the FRN in the +; attribute list entry found or Zero if no match found. +; +; ENTRY: ECX - type code to search attrib list for +; EAX - Frs buffer holding head of attribute list +; EXIT: EAX - FRN file record number to load, Zero if none. +; +; USES: All +; +SearchAttrList proc near + + push ecx ; type code to search for in + ; attrib list + + ; EAX - holds Frs to search + mov ebx, $ATTRIBUTE_LIST ; Attribute type code + mov ecx, 0 ; Attribute name length + mov edx, 0 ; Attribute name + + call LocateAttributeRecord + + or eax, eax ; If there's no Attribute list, + jz SearchAttrList$NotFoundIndex1 ; We are done + +; Read the attribute list. +; eax -> attribute list attribute + + mov ebx, eax ; ebx -> attribute list attribute + push ds + pop es ; copy ds into es + mov edi, AttrList ; ds:edi->attribute list buffer + + call ReadWholeAttribute + + push ds + pop es + mov ebx, AttrList ; es:ebx -> first attribute list entry + +; Now, traverse the attribute list looking for the entry for +; the Index type code. +; +; ebx -> first attribute list entry +; + + pop ecx ; Get Index Type code + + +SearchAttrList$LookingForIndex: + +; DEBUG CODE +; SAVE_ALL +; +; mov eax, es:[bx].ATTRLIST_TypeCode +; call PrintNumber +; movzx eax, es:[bx].ATTRLIST_Length +; call PrintNumber +; mov eax, es +; call PrintNumber +; mov eax, ebx +; call PrintNumber +; push es +; pop ds +; movzx ecx, es:[bx].ATTRLIST_NameLength ; ecx = chars in name +; lea esi, es:[bx].ATTRLIST_Name ; esi -> name +; call PrintName +; +; RESTORE_ALL +; END DEBUG CODE + + cmp es:[bx].ATTRLIST_TypeCode, ecx + je SearchAttrList$FoundIndex + + cmp es:[bx].ATTRLIST_TypeCode, $END ; reached invalid attribute + je SearchAttrList$NotFoundIndex2 ; so must be at end + + cmp es:[bx].ATTRLIST_Length, 0 + je SearchAttrList$NotFoundIndex2 ; reached end of list and + ; nothing found + movzx eax, es:[bx].ATTRLIST_Length + add bx, ax + + mov ax, bx + and ax, 08000h ; test for roll over + jz SearchAttrList$LookingForIndex + + ; If we rolled over then increment to the next es 32K segment and + ; zero off the high bits of bx + + mov ax, es + add ax, 800h + mov es, ax + + and bx, 07fffh + + jmp SearchAttrList$LookingForIndex + +SearchAttrList$FoundIndex: + + ; found the index, return the FRN + + mov eax, es:[bx].ATTRLIST_SegmentReference.REF_LowPart + ret + + +SearchAttrList$NotFoundIndex1: + pop ecx +SearchAttrList$NotFoundIndex2: + xor eax, eax + ret + +SearchAttrList endp + + +DbgString0 db "Debug Point 0", 0Dh, 0Ah, 0 +DbgString1 db "Debug Point 1", 0Dh, 0Ah, 0 +DbgString2 db "Debug Point 2", 0Dh, 0Ah, 0 +DbgString3 db "Debug Point 3", 0Dh, 0Ah, 0 +DbgString4 db "Debug Point 4", 0Dh, 0Ah, 0 + + .errnz ($-_ntfsboot) GT 8192,<FATAL PROBLEM: main boot record exceeds available space> + + org 8192 + +BootCode ends + + end _ntfsboot |