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. ; ; DEBUG EQU 1 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 MBR 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 */ ;/* */ ;/*********************** 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 label near jmp start .errnz ($-_ntfsboot) GT (3) ; org 3 Version db "NTFS " ; Signature, must be "NTFS " BPB label byte BytesPerSector dw 512 ; Size of a physical sector SectorsPerCluster db 1 ; Sectors per allocation unit ; ; Traditionally the next 7 bytes were the reserved sector count, fat count, ; root dir entry count, and the small volume sector count. However all of ; these fields must be 0 on NTFS volumes. ; ; We use this space to store some temporary variables used by the boot code, ; which avoids the need for separate space in sector 0 to store them. ; We also take advantage of the free 0-initialization to save some space ; by avoiding the code to initialize them. ; ; Note that ideally we'd want to use an unused field for the SectorCount ; and initialize it to 16. This would let us save a few bytes by avoiding ; code to explicitly initialize this value before we read the 16 boot sectors. ; However setup and other code tends to preserve the entire bpb area when ; it updates boot code, so we avoid a dependency here and initialize ; the value explicitly to 16 in the first part of the boot code. ; SectorCount dw 0 ; number of sectors to read SectorBase dd 0 ; start sector for read request HaveXInt13 db 0 ; extended int13 available flag Media db 0f8h ; Media byte FatSectors dw 0 ; (always 0 on NTFS) SectorsPerTrack dw 0 ; Sectors per track Heads dw 0 ; Number of surfaces HiddenSectors dd 0 ; partition start LBA ; ; The field below is traditionally the large sector count and is ; always 0 on NTFS. We use it here for a value the boot code calculates, ; namely the number of sectors visible on the drive via conventional int13. ; Int13Sectors dd 0 DriveNumber db 80h ; int13 unit number db 3 dup (?) ; alignment filler ; ; The following is the rest of the NTFS Sector Zero information. ; The offsets of most of these fields cannot be changed without changing ; all code that validates, formats, recognizes, etc, NTFS volumes. ; In other words, don't change it. ; SectorsOnVolume db (size LARGE_INTEGER) dup (?) MftStartLcn db (size LARGE_INTEGER) dup (?) Mft2StartLcn db (size LARGE_INTEGER) dup (?) ClustersPerFrs dd ? DefClustersPerBuf dd ? SerialNumber db (size LARGE_INTEGER) dup (?) CheckSum dd ? ; ; Make sure size of fields matches what fs_rec.sys thinks is should be ; .errnz ($-_ntfsboot) NE (54h) ;**************************************************************************** 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 BPB with DS. mov ds,ax assume ds:BootCode call Int13SecCnt ; determine range of regular int13 ; ; Read bootcode and jump to code in sector 1. ; Assumes HaveXInt13 and SectorBase are initialized to 0, ; which they are since they are stored in areas of the BPB ; that must be 0 on NTFS volumes. ; mov ax,NewSeg mov es,ax xor bx,bx ; es:bx = transfer address mov byte ptr SectorCount,16 ; word field is 0 on-disk so byte init is OK call ReadSectors push NewSeg push offset mainboot retf ; ; Determine the number of sectors addressable via ; conventional int13. If we can't get drive params for some reason ; then something is very wrong -- we'll try to force the caller ; to use conventional int13 by maxing out the sector count. ; ; Input: ds addresses start of first sector ; ; Output: in eax; also stored in Int13Sectors variable. ; ; Preserves: assume none. ; Int13SecCnt proc near mov dl,DriveNumber ; int13 unit number mov ah,8 ; get drive params int 13h ; call BIOS jnc @f ; no error, procede mov cx,-1 ; strange case, fake registers to force mov dh,cl ; use of standard int13 (set all vals to max) @@: .386 movzx eax,dh ; eax = max head # (0-255) inc ax ; eax = heads (1-256) movzx edx,cl ; edx = sectors per track + cyl bits and dl,3fh ; edx = sectors per track (1-63) mul dx ; eax = sectors per cylinder, edx = 0 xchg cl,ch shr ch,6 ; cx = max cylinder # (0-1023) inc cx ; cx = cylinders (1-1024) movzx ecx,cx ; ecx = cylinders (1-1024) mul ecx ; eax = sectors visible via int13, edx = 0 mov Int13Sectors,eax ; save # sectors addressable via int13 .286 ret Int13SecCnt endp ; ; Determine whether extended int13 services are available on the boot drive. ; ; Stores result (boolean) in HaveXInt13 variable. ; ; Preserves: assume none. ; HaveInt13Ext proc near mov ah,41h mov bx,055aah mov dl,DriveNumber int 13h jc @f ; error from int13 means no xint13 cmp bx,0aa55h ; absence of sig means no xint13 jne @f test cl,1 ; bit 0 off means no xint13 jz @f inc byte ptr HaveXInt13 ; xint13 is available @@: ret HaveInt13Ext endp ; ; Read SectorCount sectors starting at logical sector SectorBase, ; into es:bx, using extended int13 if necessary. ; ; Preserves all ; ReadSectors proc near .386 pushad ; save registers push ds push es read_loop: mov eax,SectorBase ; logical starting sector add eax,HiddenSectors ; eax = physical starting sector cmp eax,Int13Sectors ; determine if standard int13 is ok jb stdint13 push ds ; preserve ds db 66h ; hand-coded 32-bit push of 8-bit immediate push 0 ; high 32 bits of sector # push eax ; low 32 bits of sector # push es push bx ; transfer address push dword ptr 10010h ; transfer 1 sector, packet size = 16 cmp byte ptr HaveXInt13,0 jne @f ; already know we have xint13 available call HaveInt13Ext ; see if we have it cmp byte ptr HaveXInt13,0 je BootErr$he ; need it but don't have it @@: mov ah,42h ; extended read mov dl,DriveNumber ; dl = int13 unit # push ss pop ds mov si,sp ; ds:si -> param packet int 13h pop eax ; throw away first 4 bytes of param packet pop bx ; restore es:bx from param packet pop es pop eax ; throw away last 8 bytes of param packet pop eax ; without clobbering carry flag pop ds ; restore ds jmp short did_read stdint13: xor edx,edx ; edx:eax = absolute sector number movzx ecx,SectorsPerTrack ; ecx = sectors per track div ecx ; eax = track, edx = sector within track (0-62) inc dl ; dl = sector within track (1-63) mov cl,dl ; cl = sector within track mov edx,eax shr edx,16 ; dx:ax = track div Heads ; ax = cylinder (0-1023), dx = head (0-255) xchg dl,dh ; dh = head mov dl,DriveNumber ; dl = int13 unit # mov ch,al ; ch = bits 0-7 of cylinder shl ah,6 or cl,ah ; bits 6-7 of cl = bits 8-9 of cylinder mov ax,201h ; read 1 sector int 13h did_read: jc BootErr$he read_next: mov ax,es ; advance transfer address add ax,20h ; by moving segment register along mov es,ax ; thus no 64K limit on transfer length inc SectorBase ; advance sector number dec SectorCount ; see if done jnz read_loop ; not done pop es pop ds popad ; restore registers .286 ret ReadSectors endp BootErr$he: mov al,byte ptr TXT_MSG_SYSINIT_BOOT_ERROR BootErr2: call BootErr$print mov al,byte ptr TXT_MSG_SYSINIT_REBOOT call BootErr$print sti jmp $ ; Wait forever BootErr$print: ; ; al is offset - 256 of message. Adjust to form real offset ; and stick in si so lodsb below will work. ; mov ah,1 mov si,ax BootErr$print1: 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$print1 BootErr$Done: ret ;**************************************************************************** ; ; Message table. ; ; We put English messages here as a placeholder only, so that in case ; anyone uses bootntfs.h without patching new messages in, things will ; still be correct (in English, but at least functional). ; MSG_SYSINIT_BOOT_ERROR: DB 13,10,'A disk read error occurred',0 MSG_SYSINIT_FILE_NOT_FD: DB 13,10,'NTLDR is missing',0 MSG_SYSINIT_NTLDR_CMPRS: DB 13,10,'NTLDR is compressed',0 MSG_SYSINIT_REBOOT: DB 13,10,'Press Ctrl+Alt+Del to restart',13,10,0 ; ; Now build a table with the low byte of the offset to each message. ; Code that patches the boot sector messages updates this table. ; .errnz ($-_ntfsboot) GT (512-8) ORG 512 - 8 TXT_MSG_SYSINIT_BOOT_ERROR: db OFFSET (MSG_SYSINIT_BOOT_ERROR - _ntfsboot) - 256 TXT_MSG_SYSINIT_FILE_NOT_FD: db OFFSET (MSG_SYSINIT_FILE_NOT_FD - _ntfsboot) - 256 TXT_MSG_SYSINIT_NTLDR_CMPRS: db OFFSET (MSG_SYSINIT_NTLDR_CMPRS - _ntfsboot) - 256 TXT_MSG_SYSINIT_REBOOT: db OFFSET (MSG_SYSINIT_REBOOT - _ntfsboot) - 256 .errnz ($-_ntfsboot) NE (512-4) db 0,0,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 general scratch buffer for 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 ; ; For floppyless booting, winnt32.exe creates c:\$win_nt$.~bt\bootsec.dat and ; places an entry in boot.ini for it (the boot selection says something ; like "Windows NT Setup or Upgrade"). When that is selected, the boot loader ; loads 16 sectors worth of data from bootsect.dat into d000 (which is where ; the first sector of this code would have loaded it) and jumps into it at ; a known location of 256h. That was correct in earlier versions of NT ; but is not correct now because the 4 fields below were added to this sector. ; ; Note that 0000 is "add [bx+si],al" which because of the way the boot loader ; is written happens to be a benign add of 0 to something in segment 7c0, ; which doesn't seem to hose anything but is still somewhat random. ; ; We code in a jump here so as this new code proliferates we get this ; cleaned up. ; .errnz $-_ntfsboot ne 256h SectorsPerFrs label dword ; Sectors per File Record Segment jmp short mainboot nop nop .errnz $-_ntfsboot ne 25ah MftLcnFrs dd ? ; Offset scratch FRS buffer for LookupMftLcn 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 - entry point after 16 boot sectors have been read in ; ; 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 ; ; Reinitialize xint13-related variables ; call Int13SecCnt ; determine range of regular int13 ; 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 mov eax, BitmapIndexFrs ; FRS containing attribute 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 ; ; See if the loader FRS has an attribute list. ; mov eax, NtldrFrs ; FRS buffer mov ebx, $ATTRIBUTE_LIST ; Type code mov ecx, 0 ; Attribute name length mov edx, 0 ; Attribute name call LocateAttributeRecord test eax, eax ; Check for attribute list. jnz mainboot$AttribList ; If attribute list present, use it. ; ; No attribute list was found. Just search the base FRS. ; 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. jnz mainboot$FoundData jmp BootErr$fnf ; Data was not found. mainboot$AttribList: ; ; The ntldr $DATA segment is fragmented. Search the attribute list ; for the $DATA member. And load it from there. ; xor edx, edx ; VCN zero mov ecx, $DATA ; Attribute type code mov eax, NtldrFrs ; FRS to search call SearchAttrList ; search attribute list for FRN ; of specified ($DATA) or eax, eax ; if eax is zero, attribute not found. jz BootErr$fnf ; ; We found the FRN of the $DATA attribute; load that into memory. ; push ds pop es ; es:edi = target buffer mov edi, NtldrFrs call ReadFrs ; ; Determine the beginning offset of the $DATA in the FRS ; 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 mainboot$FoundData: ; 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 mov eax, NtldrFrs ; FRS containing attribute 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 .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 ReadSectors 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 ReadSectors 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 ifdef DEBUG call PrintName call Debug2 endif ; DEBUG 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 ifdef DEBUG ; 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 endif ; DEBUG ; 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 ; eax -> FRS containing attribute (NULL means use highest VCN in this attribute record) ; ; 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 ; push eax ; Save FRS. lea edx, [ebx].ATTR_FormUnion ; edx -> nonresident form info test eax, eax ; See if caller gave us an FRS. jnz rwa20 ; If FRS was provided, calculate the highest VCN. mov ecx, [edx].NONRES_HighestVcn.LowPart; ecx = HighestVcn inc ecx ; ecx = clusters in attribute jmp rwa30 rwa20: mov eax, [edx].NONRES_AllocatedLength.REF_LowPart ; Size of attribute in bytes xor edx, edx ; Zero high order of dividend. div BytesPerCluster ; Highest VCN mov ecx, eax rwa30: sub eax, eax ; eax = 0 (first VCN to read) pop esi ; Restore FRS. 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 ; ESI == FRS contianing attribute (can be NULL if requested data is in passed in attribute) ; 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 esi 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 esi pop ecx test eax, eax ; See if it found the LCN. jz ReadNR40 ; If not found, get the next piece of the attribute. ; 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 ReadNR40: ; ; Get the next chunk of the attribute from the attribute list. If ; there are no more chunks, something is wrong. ; test esi, esi ; Make sure we have an FRS buffer. jz BootErr$he ; If not, something is wrong. push ecx ; Save remaining clusters. push edi ; Save read buffer. push es ; Save read buffer segment. ; ; As soon as we read in a new FRS, the attribute we currently have is ; useless. So, save off the information we need to the stack. ; First save off the name, if there is one. ; movzx eax, [ebx].ATTR_NameLength ; Get the name length test eax, eax ; See if there is a name. jz ReadNR50 ; If no name, store NULL for the name length and buffer. shl eax, 1 ; Double the length, since it's unicode. sub esp, eax ; Reserve space for the name. mov edi, esp ; Setup the destination buffer. push esp ; Store the name buffer. push esi ; Save the FRS buffer. movzx esi, [ebx].ATTR_NameOffset ; Setup the source buffer. add esi, ebx ; It is at an offset within the attribute. mov ecx, eax ; Setup the length. rep movsb ; Do the copy. pop esi ; Restore the FRS buffer. jmp ReadNR60 ReadNR50: push eax ; Name buffer (NULL, since the length is zero.) ReadNR60: ; ; The name buffer is done. Now save the name length, type code, and highest VCN. ; push eax ; Name length mov eax, [ebx].ATTR_TypeCode push eax mov eax, [ebx].ATTR_FormUnion.NONRES_HighestVcn.LowPart push eax ; ; At this point our stack looks like this: ; ; TOS = Highest VCN of attribute ; TOS + 4 = Type code of attribute ; TOS + 8 = Length of attriubte name (in bytes) ; TOS + c = Pointer to attribute name buffer ; TOS + 10 = Name buffer (if needed) ; TOS + ? = Buffer segment ; TOS + ? = Buffer address ; TOS + ? = Remaining clusters ; TOS + ? = Current VCB ; TOS + ? = Current attribute ; ; ; See if this is the base FRS. If not, get the base FRS. ; mov edx, [esi].FRS_BaseFRS.REF_LowPart ; Get the base FRS number. test edx, edx ; See if this is the base FRS. jz ReadNR70 ; ; Read in the base FRS. ; mov edi, esi ; FRS buffer push ds pop es mov eax, edx ; FRS number call ReadFrs ; Read in the base FRS. ReadNR70: ; ; The base FRS is in our buffer. Find the next chunk of the attribute. ; mov eax, esi ; Frs Buffer pop edx ; Highest VCN of current attribute pop ecx ; Type code inc edx ; VCN we will look for push ecx ; Save type code. push esi ; Save FRS buffer. call SearchAttrList test eax, eax ; If we didn't get back an FRS number, something is wrong. jz BootErr$he pop esi ; Restore FRS buffer. pop ecx ; Restore type code. ; EAX contains FRS number mov edi, esi ; FRS buffer push ds pop es call ReadFrs ; Read in the FRS. ; ; The FRS containing the next chunk is in memory. Find the next chunk and ; continue processing. ; mov eax, esi ; FRS buffer mov ebx, ecx ; Type code pop ecx ; Attribute name length (in bytes) pop edx ; Attribute name push ecx ; Save attribute name length (in bytes). push esi ; Save FRS buffer. shr ecx, 1 ; Convert name length back to characters. call LocateAttributeRecord test eax, eax ; Make sure we found the attribute. jz BootErr$he ; If not, something is wrong. pop esi ; Restore FRS buffer. pop ecx ; Restore attribute name length (in bytes). add esp, ecx ; Give back storage for name buffer. pop es ; Restore read buffer segment. pop edi ; Restore read buffer. pop ecx ; Restore remaining clusters. ; ; Switch the attribute chunk being looked at and continue. ; mov edx, eax ; Hold new attribute temporarily. pop eax ; Restore current VCN. pop ebx ; Restore old attribute. mov ebx, edx ; Switch to new attribute. jmp ReadNR20 ; Coninue. ReadNonresidentAttribute 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 the LBN array ; ; 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 ; this is the scratch mft buffer add eax, BytesPerFrs mov MftLcnFrs,eax ; this is the scratch mft buffer for lookup add eax, BytesPerFrs mov NextBuffer, eax ; Read FRS 0 into the first MFT FRS buffer, being sure ; to resolve the Update Sequence Array. Remember the physical ; location in the Lbn array. ; mov eax, MftStartLcn.LowPart movzx ebx, SectorsPerCluster mul ebx ; eax = mft starting sector mov ebx, NextBuffer ; Store this location in the Lbn array mov [bx], eax mov SectorBase, eax ; SectorBase = mft starting sector for read add bx, 4 mov eax, SectorsPerFrs mov [bx], eax ; Store the sector count in the Lcn array mov SectorCount, ax ; SectorCount = SectorsPerFrs add bx, 4 mov NextBuffer, ebx ; Remember the next Lbn array location mov ebx, MftFrs ; Read the sectors into the MftFrs scratch buffer push ds pop es call ReadSectors mov edi, ebx ; 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 xor eax, eax ; FRS Buffer is NULL since attribute lists can only be in one FRS. 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 [bx].ATTRLIST_TypeCode, $DATA je SetupMft30 add bx,[bx].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 mapping information ; MftFrs -> Target of next read ; SegmentsInMft == number of MFT segments read so far ; ; Find the physical sector and sector count for the runs for this ; file record (max 2 runs). The mapping for this must already ; be in a file record already visited. Find the Vcn and cluster ; offset for this FRS. Use LookupMftLcn to find the Lcn. push ebx ; Save the current position in the attribute list ; Convert from Frs to sectors, then to Vcn mov eax, [bx].ATTRLIST_SegmentReference.REF_LowPart mul SectorsPerFrs push eax ; Remember the VBN xor edx, edx movzx ebx, SectorsPerCluster div ebx ; eax = VCN push edx ; save remainder, this is cluster offset call ComputeMftLcn ; eax = LCN or eax, eax ; LCN equal to zero? jz BootErr$he ; zero is not a possible LCN mov ecx, SectorsPerFrs ; ecx = Number of sectors remaining for this file record ; Change the LCN back into an LBN and add the remainder back in to get ; the sector we want to read. movzx ebx, SectorsPerCluster mul ebx ; eax = cluster first LBN pop edx ; edx = sector remainder add eax, edx ; eax = desired LBN ; Store this in the current Lcn array slot mov ebx, NextBuffer mov [bx], eax ; Store the starting sector add bx, 4 movzx eax, SectorsPerCluster sub eax, edx cmp eax, ecx ; Check if we have too many sectors jbe SetupMft60 mov eax, ecx ; Limit ourselves to the sectors remaining SetupMft60: mov [bx], eax ; Store the sector count ; If we have a complete file record skip to process the attribute entry SetupMft70: sub ecx, eax ; Subtract these sectors from remaining sectors pop edx ; Get the previous starting VBN (restores stack also) jz SetupMft50 ; This may be a split file record. Go ahead and get the next piece. add eax, edx ; Add the sector count for the last run to the start Vbn for the run ; This is the next Vbn to read push eax ; Save the Vbn xor edx, edx ; Convert to Vcn, there should be no remainder this time movzx ebx, SectorsPerCluster div ebx ; eax = VCN push ecx ; Save the remaining sectors call ComputeMftLcn ; eax = LCN pop ecx ; Restore the remaining sectors or eax, eax ; LCN equal to zero? jz BootErr$he ; zero is not a possible LCN ; Change the LCN back into a LBN to get the starting sector we want to read. movzx ebx, SectorsPerCluster mul ebx ; eax = cluster first LBN ; If this sector is the contiguous with the other half of the run ; make it appear to be single longer run. mov ebx, NextBuffer ; Recover the last run mov edx, [bx] add bx, 4 add edx, [bx] ; This is the next potential LBN cmp edx, eax ; Check if we are at the contiguous LBN jne SetupMft80 ; Append this to the previous run. movzx eax, SectorsPerCluster cmp eax, ecx ; Check if have more sectors than we need jbe SetupMft90 mov eax, ecx SetupMft90: add [bx], eax jmp SetupMft70 ; Loop to see if there more work to do ; This is multiple runs. Update the next entry. SetupMft80: add bx, 4 mov NextBuffer, ebx ; advance our NextBuffer pointer mov [bx], eax ; fill in the next run start sector add bx, 4 movzx eax, SectorsPerCluster cmp eax, ecx ; Check if have more sectors than we need jbe SetupMft100 mov eax, ecx SetupMft100: mov [bx], eax ; and count jmp SetupMft70 ; Loop to see if there is more work to do SetupMft50: ; Advance the count of Frs segments and the NextBuffer pointer add bx, 4 inc SegmentsInMft mov NextBuffer, ebx pop ebx ; Go on to the next attribute list entry SetupMft30: add bx,[bx].ATTRLIST_Length cmp [bx].ATTRLIST_TypeCode, $DATA je 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 esi,MftLcnFrs add esi,BytesPerFrs ; si -> FRS LBN list MftLcn10: ; ECX == number of remaining FRS's to search ; EDX == VCN ; EBX == Buffer to read into ; ESI == LBN array ; EDI == Number of sectors to read ; push edx ; save VCN push ecx ; save MFT segment count push edx ; save VCN again ; Read the sectors for the given FRS mov ebx,MftLcnFrs mov edi,SectorsPerFrs ; Read these sectors MftLcn40: mov eax,[si] ; Get the start sector and sector count mov SectorBase,eax add si,4 mov eax,[si] mov SectorCount,ax add si,4 push ds pop es call ReadSectors ; Check if we have more data to read sub edi, eax je MftLcn30 ; Read the next run mul BytesPerSector ; move forward in the buffer, results in ax:dx add bx,ax jmp MftLcn40 MftLcn30: ; Do the multi sector fixup mov edi,MftLcnFrs push ds pop es call MultiSectorFixup mov eax, MftLcnFrs mov ebx, $DATA mov ecx, 0 mov edx, ecx 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 push esi call ComputeLcn pop esi 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 ; 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 edx ; edx = VCN loop MftLcn10 ; decrement cx 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 push edi ; save the target buffer call ComputeMftLcn ; eax = LCN pop edi ; recover the buffer 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 ReadSectors 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 ClustersPerIndexBlock ; First VCN to read ; (note that mul wipes out edx!) mov ebx, IndexAllocation ; ebx -> $INDEX_ALLOCATION attribute mov ecx, ClustersPerIndexBlock ; Clusters to read mov esi, AllocationIndexFrs ; FRS containing index allocation attribute push ds pop es mov edi, IndexBlockBuffer ; es:edi -> index block buffer call ReadNonresidentAttribute 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 first one and work our way forwards. ; ; 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_AllocatedLength.LowPart; Size of attribute in bytes xor edx, edx div BytesPerIndexBlock ; convert bytes to index blocks xor esi, esi ; Start at block zero. push eax ; number of blocks to process push esi ; Current block FindFile40: ; ; TOS == Next block to search ; TOS + 4 == Total blocks to search ; TOS + 8 == name length ; TOS + c -> name to find ; pop eax ; Block to process pop esi ; Total blocks cmp eax, esi je FindFile90 push esi ; Save Total. inc eax ; Save the next block to the stack, push eax ; but keep using the current block. dec eax ; eax == block number to process ; TOS == Next block to search ; TOS + 4 == Total blocks to search ; TOS + 8 == name length ; TOS + c -> 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 == Next block to search ; TOS + 4 == Total blocks to search ; TOS + 8 == name length ; TOS + c -> name to find ; call ReadIndexBlock pop edx ; Current block pop esi ; Total blocks pop ecx ; ecx == name length pop ebx ; ebx -> name push ebx push ecx push esi push edx ; ebx -> name to find ; ecx == name length in characters ; TOS == Next block to search ; TOS + 4 == Total blocks to search ; TOS + 8 == name length ; TOS + c -> name to find ; ; 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 == Next block to search ; TOS + 4 == Total blocks to search ; TOS + 8 == name length ; TOS + c -> name to find ; pop ecx 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 ifdef DEBUG ;**************************************************************************** ; ; 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 si, offset DbgString0 call BootErr$Print1 RESTORE_ALL ret Debug0 endp ;**************************************************************************** ; ; Debug1 - Print debug string 1 -- ; Debug1 proc near SAVE_ALL mov si, offset DbgString1 call BootErr$Print1 RESTORE_ALL ret Debug1 endp ;**************************************************************************** ; ; Debug2 - Print debug string 2 ; Debug2 proc near SAVE_ALL mov si, offset DbgString2 call BootErr$Print1 RESTORE_ALL ret Debug2 endp ;**************************************************************************** ; ; Debug3 - Print debug string 3 -- ; Debug3 proc near SAVE_ALL mov si, offset DbgString3 call BootErr$Print1 RESTORE_ALL ret Debug3 endp ;**************************************************************************** ; ; Debug4 - Print debug string 4 ; Debug4 proc near SAVE_ALL mov si, offset DbgString4 call BootErr$Print1 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, 0fff10000h PauseLoopy: inc eax or eax, eax jnz PauseLoopy pop eax ret Pause endp endif ; DEBUG ;************************************************************************* ; ; 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 ; ; See if an attribute list exists. ; mov eax, ecx ; Base FRS mov ebx, $ATTRIBUTE_LIST ; Attribute type code mov ecx, 0 ; Attribute name length mov edx, 0 ; Attribute name call LocateAttributeRecord pop ebx ; Restore type code. pop ecx ; Restore FRS. test eax, eax ; If attribute list exists, search it. jnz LoadIndexFrs$AttribList mov eax, ecx ; FRS to search ; EBX contains type code. movzx ecx, index_name_length ; Attribute name length mov edx, offset index_name ; Attribute name call LocateAttributeRecord jmp LoadIndexFrs$Exit ; Return with results from search above. LoadIndexFrs$AttribList: ; ; Search in attribute list. ; xor edx, edx ; VCN zero 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 ; EDX - Lowest VCN ; ; EXIT: EAX - FRN file record number to load, Zero if none. ; ; USES: All ; SearchAttrList proc near push edx ; Save VCN. 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 xor eax, eax ; FRS Buffer is NULL since attribute lists can only be in one FRS. 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 pop edx ; Restore VCN. SearchAttrList$LookingForIndex: ifdef DEBUG 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 endif ; DEBUG cmp es:[bx].ATTRLIST_TypeCode, ecx jne SearchAttrList$NotFoundIndex3 ; This is not the right attribute. cmp es:[bx].ATTRLIST_LowestVcn.LowPart, edx je SearchAttrList$FoundIndex ; Found it. jmp SearchAttrList$NotFoundIndex4 ; Wrong VCN. SearchAttrList$NotFoundIndex3: 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 SearchAttrList$NotFoundIndex4: 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 pop edx SearchAttrList$NotFoundIndex2: xor eax, eax ret SearchAttrList endp ; ; Boot message printing, relocated from sector 0 to sace space ; BootErr$fnf: mov al,byte ptr TXT_MSG_SYSINIT_FILE_NOT_FD jmp BootErr2 BootErr$ntc: mov al,byte ptr TXT_MSG_SYSINIT_NTLDR_CMPRS jmp BootErr2 ifdef DEBUG 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 endif ; DEBUG .errnz ($-_ntfsboot) GT 8192 ; org 8192 BootCode ends end _ntfsboot