mirror of
https://github.com/reactos/reactos.git
synced 2024-12-29 10:35:28 +00:00
f0d73e0f7f
Implement NTFS boot sector that loads FreeLdr from a NTFS partition. CORE-17474 - Able to find/parse root directory; - Handle fixups in FILE MFT record; - Implement directory tree search; - Implement loading found file from disk; - Handle fixups in INDX records; - Fail if compressed or sparse; - Attempt to support 64-bit disks. Some TO-DOs for later: - Handle "weird" NTFS partition with uncommon sector sizes / sectors per cluster / sectors per index record; - Better implementation for file loading; - 64-bit LCN support.
893 lines
25 KiB
ArmAsm
893 lines
25 KiB
ArmAsm
/*
|
|
* PROJECT: ReactOS Bootsector
|
|
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
|
|
* FILE: boot/freeldr/bootsect/fat32.S
|
|
* PURPOSE: Implementing boot sector for NTFS
|
|
* COPYRIGHT: Copyright 2020 Sylvain Deverre (deverre.sylv@gmail.com)
|
|
*/
|
|
|
|
/* INCLUDES ******************************************************************/
|
|
|
|
#include <asm.inc>
|
|
#include <freeldr/include/arch/pc/x86common.h>
|
|
|
|
.code16
|
|
|
|
//ORG HEX(7c00)
|
|
|
|
start:
|
|
jmp short main
|
|
nop
|
|
OEMName:
|
|
.ASCII "NTFS " // NTFS signature
|
|
BytesPerSector:
|
|
.word 512
|
|
SectsPerCluster:
|
|
.byte 8
|
|
ReservedSectors:
|
|
.word 0 // Unused by NTFS
|
|
NumberOfFats:
|
|
.byte 0 // Unused by NTFS
|
|
MaxRootEntries:
|
|
.word 0 // Unused by NTFS
|
|
TotalSectors:
|
|
.word 0 // Unused by NTFS
|
|
MediaDescriptor:
|
|
.byte HEX(0f8)
|
|
SectorsPerFat:
|
|
.word 0 // Unused by NTFS
|
|
SectorsPerTrack:
|
|
.word 0 // Should contain disk geometry
|
|
NumberOfHeads:
|
|
.word 0 // Should contain disk geometry
|
|
HiddenSectors:
|
|
.long 0 // Filled by format program
|
|
TotalSectorsBig:
|
|
.long 0 // Unused by NTFS
|
|
// NTFS inserted info
|
|
BootDrive:
|
|
.byte HEX(80)
|
|
CurrentHead:
|
|
.byte 0
|
|
BootSignature:
|
|
.byte HEX(80)
|
|
Unused:
|
|
.byte 0
|
|
VolumeSectorCount:
|
|
.quad 0 // Must be patched by format program !
|
|
MftLocation:
|
|
.quad 0 // Must be patched by format program !
|
|
MftMirrorLocation:
|
|
.quad 0 // Must be patched by format program !
|
|
ClustersPerMftRecord:
|
|
.long 0
|
|
ClustersPerIndexRecord:
|
|
.long 0
|
|
VolumeSerialNumber:
|
|
.quad 0
|
|
Checksum:
|
|
.long 0
|
|
|
|
main:
|
|
xor ax,ax // Setup segment registers
|
|
mov ds,ax // Make DS correct
|
|
mov es,ax // Make ES correct
|
|
mov ss,ax // Make SS correct
|
|
mov sp, HEX(7c00)
|
|
mov bp, sp // Setup a stack
|
|
|
|
cmp byte ptr [BootDrive], HEX(0ff) // If they have specified a boot drive then use it
|
|
jne GetDriveParameters
|
|
|
|
mov byte ptr [BootDrive], dl // Save the boot drive
|
|
|
|
GetDriveParameters:
|
|
mov ah, 8
|
|
mov dl, byte ptr [BootDrive] // Get boot drive in dl
|
|
int HEX(13) // Request drive parameters from the bios
|
|
jnc CalcDriveSize // If the call succeeded then calculate the drive size
|
|
|
|
// If we get here then the call to the BIOS failed
|
|
// so just set CHS equal to the maximum addressable
|
|
// size
|
|
mov cx, HEX(0ffff)
|
|
mov dh, cl
|
|
|
|
CalcDriveSize:
|
|
// Now that we have the drive geometry
|
|
// lets calculate the drive size
|
|
mov bl, ch // Put the low 8-bits of the cylinder count into BL
|
|
mov bh, cl // Put the high 2-bits in BH
|
|
shr bh, 6 // Shift them into position, now BX contains the cylinder count
|
|
and cl, HEX(3f) // Mask off cylinder bits from sector count
|
|
// CL now contains sectors per track and DH contains head count
|
|
movzx eax, dh // Move the heads into EAX
|
|
movzx ebx, bx // Move the cylinders into EBX
|
|
movzx ecx, cl // Move the sectors per track into ECX
|
|
inc eax // Make it one based because the bios returns it zero based
|
|
inc ebx // Make the cylinder count one based also
|
|
mul ecx // Multiply heads with the sectors per track, result in edx:eax
|
|
mul ebx // Multiply the cylinders with (heads * sectors) [stored in edx:eax already]
|
|
|
|
// We now have the total number of sectors as reported
|
|
// by the bios in eax, so store it in our variable
|
|
mov dword ptr ds:[BiosCHSDriveSize], eax
|
|
|
|
LoadExtraBootCode:
|
|
// First we have to load our extra boot code at
|
|
// next sector into memory at [0000:7e00h]
|
|
mov eax, HEX(1)
|
|
xor edx, edx
|
|
mov cx, 4
|
|
xor bx, bx
|
|
mov es, bx // Read sector to [0000:7e00h]
|
|
mov bx, HEX(7e00)
|
|
call ReadSectors
|
|
jmp StartSearch
|
|
|
|
|
|
// Reads logical sectors into [ES:BX]
|
|
// EDX:EAX has logical sector number to read
|
|
// CX has number of sectors to read
|
|
ReadSectors:
|
|
push es
|
|
add eax, dword ptr [HiddenSectors] // Add offset from the disk beginning
|
|
test edx, edx
|
|
jnz ReadSectorsLBA
|
|
cmp eax, dword ptr ds:[BiosCHSDriveSize] // Check if they are reading a sector outside CHS range
|
|
jae ReadSectorsLBA // Yes - go to the LBA routine
|
|
// If at all possible we want to use LBA routines because
|
|
// They are optimized to read more than 1 sector per read
|
|
|
|
pushad // Save logical sector number & sector count
|
|
|
|
CheckInt13hExtensions: // Now check if this computer supports extended reads
|
|
mov ah, HEX(41) // AH = 41h
|
|
mov bx, HEX(55aa) // BX = 55AAh
|
|
mov dl, byte ptr [BootDrive] // DL = drive (80h-FFh)
|
|
int HEX(13) // IBM/MS INT 13 Extensions - INSTALLATION CHECK
|
|
jc ReadSectorsCHS // CF set on error (extensions not supported)
|
|
cmp bx, HEX(0aa55) // BX = AA55h if installed
|
|
jne ReadSectorsCHS
|
|
test cl,1 // CX = API subset support bitmap
|
|
jz ReadSectorsCHS // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
|
|
|
|
popad // Restore sector count & logical sector number
|
|
|
|
ReadSectorsLBA:
|
|
pushad // Save logical sector number & sector count
|
|
|
|
cmp cx, 64 // Since the LBA calls only support 0x7F sectors at a time we will limit ourselves to 64
|
|
jbe ReadSectorsSetupDiskAddressPacket // If we are reading less than 65 sectors then just do the read
|
|
mov cx, 64 // Otherwise read only 64 sectors on this loop iteration
|
|
|
|
ReadSectorsSetupDiskAddressPacket:
|
|
mov word ptr ds:[LBASectorsRead],cx
|
|
push edx
|
|
push eax // Put 64-bit logical block address on stack
|
|
push es // Put transfer segment on stack
|
|
push bx // Put transfer offset on stack
|
|
push cx // Set transfer count
|
|
push 16 // Set size of packet to 10h
|
|
mov si, sp // Setup disk address packet on stack
|
|
|
|
mov dl, byte ptr [BootDrive] // Drive number
|
|
mov ah, HEX(42) // Int 13h, AH = 42h - Extended Read
|
|
int HEX(13) // Call BIOS
|
|
jc PrintDiskError // If the read failed then abort
|
|
|
|
add sp, 16 // Remove disk address packet from stack
|
|
|
|
popad // Restore sector count & logical sector number
|
|
|
|
push bx
|
|
mov ebx, dword ptr ds:[LBASectorsRead]
|
|
add eax, ebx // Increment sector to read
|
|
adc edx, 0
|
|
shl ebx, 5
|
|
mov dx, es
|
|
add dx, bx // Setup read buffer for next sector
|
|
mov es, dx
|
|
pop bx
|
|
|
|
sub cx, word ptr ds:[LBASectorsRead]
|
|
jnz ReadSectorsLBA // Read next sector
|
|
|
|
pop es
|
|
ret
|
|
|
|
LBASectorsRead:
|
|
.long 0
|
|
|
|
|
|
// Reads logical sectors into [ES:BX]
|
|
// EAX has logical sector number to read
|
|
// CX has number of sectors to read
|
|
ReadSectorsCHS:
|
|
popad // Get logical sector number & sector count off stack
|
|
|
|
ReadSectorsCHSLoop:
|
|
pushad
|
|
xor edx, edx
|
|
movzx ecx, word ptr [SectorsPerTrack]
|
|
div ecx // Divide logical by SectorsPerTrack
|
|
inc dl // Sectors numbering starts at 1 not 0
|
|
mov cl, dl // Sector in CL
|
|
mov edx, eax
|
|
shr edx, 16
|
|
div word ptr [NumberOfHeads] // Divide logical by number of heads
|
|
mov dh, dl // Head in DH
|
|
mov dl, byte ptr [BootDrive] // Drive number in DL
|
|
mov ch, al // Cylinder in CX
|
|
ror ah, 2 // Low 8 bits of cylinder in CH, high 2 bits
|
|
// in CL shifted to bits 6 & 7
|
|
or cl, ah // Or with sector number
|
|
mov ax, HEX(0201)
|
|
int HEX(13) // DISK - READ SECTORS INTO MEMORY
|
|
// AL = number of sectors to read, CH = track, CL = sector
|
|
// DH = head, DL = drive, ES:BX -> buffer to fill
|
|
// Return: CF set on error, AH = status (see AH=01h), AL = number of sectors read
|
|
|
|
jc PrintDiskError // If the read failed then abort
|
|
|
|
popad
|
|
|
|
inc eax // Increment Sector to Read
|
|
|
|
mov dx, es
|
|
add dx, 32 // Increment read buffer for next sector
|
|
mov es, dx
|
|
|
|
loop ReadSectorsCHSLoop // Read next sector
|
|
|
|
pop es
|
|
ret
|
|
|
|
// Displays a disk error message
|
|
// And reboots
|
|
PrintDiskError:
|
|
mov si, offset msgDiskError // Bad boot disk message
|
|
call PutChars // Display it
|
|
|
|
jmp Reboot
|
|
|
|
// Displays a file system error message
|
|
// And reboots
|
|
PrintFileSystemError:
|
|
mov si, offset msgFileSystemError // FreeLdr not found message
|
|
call PutChars // Display it
|
|
|
|
Reboot:
|
|
mov si, offset msgAnyKey // Press any key message
|
|
call PutChars // Display it
|
|
xor ax, ax
|
|
int HEX(16) // Wait for a keypress
|
|
int HEX(19) // Reboot
|
|
|
|
PutChars:
|
|
lodsb
|
|
or al, al
|
|
jz short Done
|
|
mov ah, HEX(0e)
|
|
mov bx, 7
|
|
int HEX(10)
|
|
jmp short PutChars
|
|
Done:
|
|
ret
|
|
|
|
msgDiskError:
|
|
.ascii "Disk error", CR, LF, NUL
|
|
msgFileSystemError:
|
|
.ascii "File system error", CR, LF, NUL
|
|
msgAnyKey:
|
|
.ascii "Press any key to restart", CR, LF, NUL
|
|
|
|
SectsPerMFT:
|
|
.word 0
|
|
|
|
MFTStartSector:
|
|
.quad 0
|
|
|
|
BiosCHSDriveSize:
|
|
.long 0
|
|
|
|
.org 509 // Pad to 509 bytes
|
|
BootPartition:
|
|
.byte 1
|
|
|
|
BootFlagSignature:
|
|
.word HEX(0aa55) // BootSector signature
|
|
|
|
// End of Bootsector
|
|
// Next sector starts as sector 2, since boot sector is
|
|
// as a file under NTFS ($Boot, which has inode number 7)
|
|
// and takes the two first clusters of the volume (which means
|
|
// 16 sectors, checked on Windows and mkfs.ntfs from ntfsutils)
|
|
|
|
#define ATTRIBUTE_DATA HEX(80)
|
|
#define ATTRIBUTE_INDEX_ROOT HEX(90)
|
|
#define ATTRIBUTE_INDEX_ALLOCATION HEX(A0)
|
|
#define FILE_MAGIC HEX(454c4946)
|
|
#define INDX_MAGIC HEX(58444e49)
|
|
#define NTFS_FILE_ROOT 5
|
|
StartSearch:
|
|
// Compute MFT start sector
|
|
mov eax, dword ptr [MftLocation]
|
|
mov cl, byte ptr [SectsPerCluster]
|
|
movzx ecx, cx
|
|
mul ecx
|
|
mov dword ptr [MFTStartSector], eax
|
|
|
|
// Compute size of MFT entry in sectors
|
|
xor ax, ax
|
|
mov al, byte ptr [ClustersPerMftRecord]
|
|
test al, al
|
|
js NegativeOffset
|
|
mov cx, word ptr [SectsPerCluster]
|
|
mul cx
|
|
mov word ptr [SectsPerMFT], cx
|
|
jmp SearchNext
|
|
NegativeOffset:
|
|
// ClustersPerMftRecord is negative, so we need to perform 1 << (-ClustersPerMftRecord)
|
|
// to get the number of bytes needed for a MFT entry
|
|
not al
|
|
inc al
|
|
mov cl, al
|
|
xor ax, ax
|
|
inc ax
|
|
shl ax, cl
|
|
|
|
// But here want to store sectors, so we need to divide by BytesPerSector
|
|
xor dx, dx
|
|
mov cx, word ptr [BytesPerSector]
|
|
div cx
|
|
mov word ptr [SectsPerMFT], ax
|
|
|
|
SearchNext:
|
|
mov bp, sp
|
|
sub sp, HEX(10)
|
|
|
|
// Read Root Directory MFT into [2000:0]
|
|
mov ax, HEX(2000)
|
|
mov es, ax
|
|
xor bx, bx
|
|
xor edx, edx
|
|
mov eax, NTFS_FILE_ROOT
|
|
call ReadInode
|
|
|
|
// Finds freeldr.sys into index root's B-Tree
|
|
// and return its MFT index
|
|
xor ax, ax
|
|
call ExploreIndexRoot
|
|
|
|
// Read the MFT entry of freeldr.sys into [A00:0]
|
|
push eax
|
|
mov ax, HEX(A00)
|
|
mov es, ax
|
|
pop eax
|
|
call ReadInode
|
|
|
|
xor ax, ax
|
|
mov ebx, HEX(30)
|
|
call FindAttributeHdr
|
|
mov si, ax
|
|
// Move to the attribute data
|
|
add si, word ptr es:[si + HEX(14)]
|
|
|
|
// We don't support compressed, sparse or encrypted Freeldr
|
|
test dword ptr es:[si + HEX(38)], HEX(4a00)
|
|
jnz CompressedFreeldr
|
|
|
|
// Compute size in clusters
|
|
mov eax, dword ptr es:[si + HEX(28)]
|
|
mov cl, byte ptr [SectsPerCluster]
|
|
xor edx, edx
|
|
div ecx
|
|
mov cx, word ptr [BytesPerSector]
|
|
movzx ecx, cx
|
|
xor edx, edx
|
|
div ecx
|
|
mov edx, eax
|
|
|
|
push ax
|
|
mov si, offset msgLoading
|
|
call PutChars
|
|
pop ax
|
|
|
|
xor ax, ax
|
|
mov ebx, HEX(80)
|
|
call FindAttributeHdr
|
|
mov si, ax
|
|
add ax, word ptr es:[si + HEX(20)]
|
|
xor ecx, ecx
|
|
xor bx, bx
|
|
push FREELDR_BASE / 16
|
|
pop fs
|
|
FreeLdrLoad:
|
|
pushad
|
|
call ReadNonResidentAttribute
|
|
popad
|
|
mov bx, fs
|
|
add bx, HEX(100)
|
|
mov fs, bx
|
|
xor bx, bx
|
|
inc ecx
|
|
cmp ecx, edx
|
|
jbe FreeLdrLoad
|
|
|
|
mov dl, byte ptr [BootDrive]
|
|
mov dh, byte ptr [BootPartition]
|
|
ljmp16 0, FREELDR_BASE
|
|
|
|
// Error message if Freeldr is compressed, encrypted or sparse
|
|
CompressedFreeldr:
|
|
mov si, offset msgFreeldrCompressed
|
|
call PutChars
|
|
jmp Reboot
|
|
|
|
// Finds Freeldr.sys into the directory tree and returns the
|
|
// inode index
|
|
// INPUT:
|
|
// - ES:[AX] address of the MFT record
|
|
// OUTPUT:
|
|
// - EDX:EAX MFT number of the found file
|
|
ExploreIndexRoot:
|
|
#define fileRecordBase 2
|
|
#define fileRecord 4
|
|
#define indexRootData 6
|
|
#define allocationRunList 8
|
|
|
|
push bp
|
|
mov bp, sp
|
|
push bx
|
|
push si
|
|
push di
|
|
sub sp, HEX(10)
|
|
mov word ptr [bp - fileRecordBase], es
|
|
mov word ptr [bp - fileRecord], ax
|
|
|
|
mov ebx, ATTRIBUTE_INDEX_ROOT
|
|
call FindAttributeHdr
|
|
test ax, ax
|
|
jz PrintFileSystemError // fail if no INDEX_ROOT
|
|
|
|
mov si, ax
|
|
cmp byte ptr es:[si + 8], HEX(0)
|
|
jnz PrintFileSystemError // fail if attribute is non-resident
|
|
|
|
add si, word ptr es:[si + HEX(14)]
|
|
cmp dword ptr es:[si], HEX(30)
|
|
jnz PrintFileSystemError // fail if FILE_NAME attribute isn't indexed
|
|
|
|
mov word ptr [bp - indexRootData], si
|
|
mov ax, word ptr [bp - fileRecord]
|
|
|
|
test byte ptr es:[si + HEX(0c)], 1
|
|
jz ExploreNext // Skip index allocation lookup if we don't have children
|
|
|
|
mov ebx, ATTRIBUTE_INDEX_ALLOCATION
|
|
call FindAttributeHdr
|
|
test ax, ax
|
|
jz PrintFileSystemError // No INDEX_ALLOCATION found
|
|
|
|
mov si, ax
|
|
cmp byte ptr es:[si + 8], 1
|
|
jnz PrintFileSystemError // Fail if attribute is resident (shouldn't be)
|
|
|
|
add si, word ptr es:[si + HEX(20)]
|
|
mov word ptr [bp - allocationRunList], si // save run list
|
|
|
|
ExploreNext:
|
|
mov si, word ptr [bp - indexRootData]
|
|
lea si, [si + 32] // get the first INDEX_ENTRY
|
|
|
|
// Main search loop. We browse the B-Tree which contains directory entries
|
|
// SI contains the current index entry.
|
|
NodeCheck:
|
|
test word ptr es:[si + HEX(0C)], 2
|
|
jnz NodeCheckLastNode
|
|
|
|
mov cl, byte ptr es:[si + HEX(50)]
|
|
movzx cx, cl
|
|
lea si, [si + HEX(52)]
|
|
mov di, offset FreeLdr
|
|
call CompareWideStrInsensitive
|
|
lea si, [si - HEX(52)]
|
|
test ax, ax
|
|
jz RootIndexFound
|
|
test word ptr es:[si + HEX(0C)], 1
|
|
jz ContinueSearch
|
|
|
|
test ax, HEX(f000)
|
|
jnz LookupChildNode // if result < 0 then explore child node
|
|
|
|
ContinueSearch:
|
|
add si, word ptr es:[si + 8]
|
|
jmp NodeCheck
|
|
|
|
RootIndexFound:
|
|
mov eax, dword ptr es:[si] // We found root entry, return with its MFT number
|
|
mov dx, word ptr es:[si + 4] // Return high part into edx.
|
|
// We take only the first word, the high word contains the
|
|
// sequence number
|
|
movzx edx, dx
|
|
jmp ExitIndexTree
|
|
|
|
NodeCheckLastNode:
|
|
test word ptr es:[si + HEX(0C)], 1
|
|
jz PrintFreeldrError
|
|
|
|
LookupChildNode:
|
|
// Take the right LCN at the end of the index record
|
|
add si, word ptr es:[si + 8]
|
|
mov ecx, dword ptr es:[si - 8]
|
|
|
|
// Read the fixed up LCN
|
|
mov bx, word ptr [bp - allocationRunList]
|
|
mov ax, word ptr [bp - fileRecordBase]
|
|
mov es, ax
|
|
mov ax, HEX(9000)
|
|
mov fs, ax
|
|
mov ax, bx
|
|
xor bx, bx
|
|
call ReadINDX
|
|
|
|
// Go again to the loop but with the child node list
|
|
mov ax, HEX(9000)
|
|
mov es, ax
|
|
mov si, 0
|
|
add si, es:[si + HEX(18)] // Go to the first node
|
|
lea si, [si + HEX(18)]
|
|
jmp NodeCheck
|
|
|
|
ExitIndexTree:
|
|
pop di
|
|
pop si
|
|
mov sp, bp
|
|
pop bp
|
|
ret
|
|
|
|
// 64-bit multiplication
|
|
// EDX:EAX operand
|
|
// ECX operator
|
|
Multiply64:
|
|
push bp
|
|
mov bp, sp
|
|
sub sp, 12
|
|
// Save the high part of the multiplication
|
|
mov dword ptr [bp - 4], edx
|
|
|
|
// Multiply the low part and save the result
|
|
mul ecx
|
|
mov dword ptr[bp - 8], eax
|
|
mov dword ptr[bp - 12], edx
|
|
|
|
// Multiply the high part and add the carry
|
|
mov eax, dword ptr [bp - 4]
|
|
mul ecx
|
|
add eax, dword ptr [bp - 12]
|
|
|
|
// Format correctly the number
|
|
mov edx, eax
|
|
mov eax, dword ptr [bp - 8]
|
|
mov sp, bp
|
|
pop bp
|
|
ret
|
|
|
|
// Compare case-insensitive strings
|
|
// [ES:SI] - the source file name
|
|
// [DS:DI] - the destination file name
|
|
// CX - compare length
|
|
CompareWideStrInsensitive:
|
|
push bx
|
|
push si
|
|
push di
|
|
movzx cx, cl
|
|
CmpLoop:
|
|
mov ax, word ptr es:[si]
|
|
cmp ax, 'a'
|
|
jl NoUpper
|
|
cmp ax, 'z'
|
|
jg NoUpper
|
|
sub ax, HEX(20)
|
|
NoUpper:
|
|
mov bx, word ptr ds:[di]
|
|
sub bx, ax
|
|
test bx, bx
|
|
jnz CompareFail
|
|
add si, 2
|
|
add di, 2
|
|
dec cx
|
|
jnz CmpLoop
|
|
CompareFail:
|
|
mov ax, bx
|
|
pop di
|
|
pop si
|
|
pop bx
|
|
ret
|
|
|
|
// Reads a NTFS cluster
|
|
// INPUT:
|
|
// - EDX:EAX : cluster number to read
|
|
// OUTPUT:
|
|
// - ES:BX : address to read
|
|
ReadCluster:
|
|
push ecx
|
|
mov cl, byte ptr [SectsPerCluster] // Convert clusters to sectors
|
|
movzx ecx, cx
|
|
call Multiply64
|
|
call ReadSectors
|
|
pop ecx
|
|
ret
|
|
|
|
// Reads a MFT entry
|
|
// INPUT:
|
|
// - EDX:EAX : MFT inode
|
|
// OUTPUT:
|
|
// - ES:[BX] : address to read
|
|
ReadInode:
|
|
push ecx
|
|
push si
|
|
push di
|
|
|
|
push edx
|
|
mov cx, word ptr [SectsPerMFT] // Get the correct number of sectors for the FILE entry
|
|
movzx ecx, cx
|
|
mul ecx
|
|
movzx eax, ax
|
|
add eax, dword ptr [MFTStartSector] // Add it to the start of the MFT
|
|
mov cx, word ptr [SectsPerMFT]
|
|
movzx ecx, cx
|
|
pop edx
|
|
call ReadSectors
|
|
|
|
cmp dword ptr es:[bx], FILE_MAGIC // Ensure we get a valid FILE record
|
|
jnz PrintFileSystemError
|
|
|
|
call ApplyFixups
|
|
|
|
pop di
|
|
pop si
|
|
pop ecx
|
|
ret
|
|
|
|
#define UpdateSequenceOffset 4
|
|
#define UpdateSequenceLen 6
|
|
// Apply fixups to INDX and FILE records
|
|
// INPUT:
|
|
// - ES:[BX] - pointer to the record to fixup
|
|
ApplyFixups:
|
|
push si
|
|
push di
|
|
mov si, bx
|
|
add si, word ptr es:[bx + UpdateSequenceOffset]
|
|
xor cx, cx
|
|
inc cx
|
|
FixupLoop:
|
|
cmp cx, word ptr es:[bx + UpdateSequenceLen]
|
|
jz EndFixupLoop
|
|
mov si, bx
|
|
add si, word ptr es:[bx + UpdateSequenceOffset]
|
|
mov ax, word ptr es:[si] // Get first fixup value
|
|
|
|
mov di, cx
|
|
shl di, 9
|
|
add di, bx
|
|
sub di, 2
|
|
cmp ax, word ptr es:[di] // Check fixup value
|
|
jnz PrintFileSystemError // Fixup is corrupted, so print error
|
|
inc cx
|
|
add si, cx
|
|
mov ax, word ptr es:[si] // Apply fixup
|
|
mov word ptr es:[di], ax
|
|
|
|
jmp FixupLoop
|
|
EndFixupLoop:
|
|
|
|
pop di
|
|
pop si
|
|
ret
|
|
|
|
// Reads a non-resident attribute
|
|
// INPUT:
|
|
// - ES:[AX] : Address of the data runs
|
|
// - ECX : LCN to read
|
|
// OUTPUT:
|
|
// - FS:[BX] : Address to write to
|
|
ReadNonResidentAttribute:
|
|
#define currentLCN 4
|
|
#define offsetWrite 8
|
|
#define startReadLCN HEX(0C)
|
|
push bp
|
|
mov bp, sp
|
|
sub sp, HEX(10)
|
|
push edx
|
|
push si
|
|
mov dword ptr [bp - currentLCN], 0 // Store the current LCN
|
|
mov word ptr [bp - offsetWrite], bx
|
|
mov dword ptr [bp - startReadLCN], ecx
|
|
mov si, ax
|
|
xor edx, edx
|
|
RunLoop:
|
|
mov al, byte ptr es:[si]
|
|
test al, al
|
|
jz NotFound
|
|
call UnpackRun
|
|
add dword ptr [bp - currentLCN], eax
|
|
cmp dword ptr [bp - startReadLCN], ecx
|
|
jb FoundRun
|
|
sub dword ptr [bp - startReadLCN], ecx // Decrement the cluster
|
|
jmp RunLoop
|
|
FoundRun:
|
|
mov ebx, dword ptr [bp - currentLCN]
|
|
mov ecx, dword ptr [bp - startReadLCN]
|
|
add ebx, ecx
|
|
|
|
push es
|
|
mov ax, fs
|
|
mov es, ax
|
|
mov eax, ebx
|
|
mov bx, word ptr [bp - offsetWrite]
|
|
call ReadCluster
|
|
pop es
|
|
jmp RunSearchEnd
|
|
NotFound:
|
|
xor ax, ax
|
|
RunSearchEnd:
|
|
pop si
|
|
pop edx
|
|
mov sp, bp
|
|
pop bp
|
|
ret
|
|
|
|
// Decodes a run in the runlist
|
|
// INPUT:
|
|
// - ES:[SI] : address of the run
|
|
// OUTPUT:
|
|
// - EAX : Unpacked LCN
|
|
// - ECX : Unpacked run length (in sectors)
|
|
// - SI : Next run in the run list
|
|
UnpackRun:
|
|
push bp
|
|
mov bp, sp
|
|
sub sp, HEX(10)
|
|
push ebx
|
|
|
|
// Unpack run header
|
|
mov bl, byte ptr es:[si]
|
|
inc si
|
|
|
|
mov bh, bl
|
|
shr bh, 4
|
|
and bl, 7
|
|
mov byte ptr [bp-2], bh // BH contains the LCN length
|
|
mov byte ptr [bp-1], bl // BL contains the number of cluster length
|
|
|
|
mov al, bl
|
|
call UnpackLen
|
|
mov dword ptr [bp - 8], ebx
|
|
|
|
mov al, byte ptr [bp-2]
|
|
call UnpackLen
|
|
mov cl, byte ptr es:[si-1] // Fixup sign if last byte is > 255
|
|
test cl, HEX(80)
|
|
jz NoSign
|
|
not eax
|
|
add ebx, eax
|
|
|
|
NoSign:
|
|
mov eax, ebx
|
|
mov ecx, dword ptr [bp - 8]
|
|
pop ebx
|
|
mov sp, bp
|
|
pop bp
|
|
ret
|
|
|
|
|
|
// Auxiliary function that unpacks n bytes in the memory
|
|
// INPUT:
|
|
// - AL : size to unpack (max 4 bytes)
|
|
// OUTPUT:
|
|
// - EAX : the mask used to unpack (for negative number fixup)
|
|
// - EBX : the unpacked number
|
|
// - SI : Next byte to read
|
|
UnpackLen:
|
|
push cx
|
|
movzx ax, al
|
|
|
|
// Read the whole DWORD and then compute a mask to remove
|
|
// unneeded bytes to get correct size
|
|
xor ebx, ebx
|
|
mov ebx, dword ptr es:[si]
|
|
add si, ax
|
|
|
|
cmp al, 4
|
|
jnz UnpackLen_not4
|
|
xor eax, eax
|
|
dec eax
|
|
jmp UnpackLen_mask
|
|
|
|
UnpackLen_not4:
|
|
mov cl, al // Compute mask (2^(8*len) - 1)
|
|
shl cl, 3
|
|
xor eax, eax
|
|
inc eax
|
|
shl eax, cl
|
|
dec eax
|
|
|
|
UnpackLen_mask:
|
|
and ebx, eax // Apply mask
|
|
pop cx
|
|
ret
|
|
|
|
// Reads an INDX sector and applies fixups
|
|
// INPUT:
|
|
// - ES:[AX] : Address of the data runs
|
|
// - ECX : LCN to read
|
|
// OUTPUT:
|
|
// - FS:[BX] : Address to write to
|
|
ReadINDX:
|
|
push es
|
|
push bx
|
|
call ReadNonResidentAttribute
|
|
test ax, ax
|
|
jz PrintFileSystemError
|
|
cmp dword ptr fs:[0], INDX_MAGIC
|
|
jnz PrintFileSystemError // jump if not valid
|
|
pop bx
|
|
|
|
mov ax, fs
|
|
mov es, ax
|
|
call ApplyFixups
|
|
pop es
|
|
ret
|
|
|
|
// Finds an attribute header into the MFT
|
|
// INPUT:
|
|
// - ES:[AX] : pointer to the MFT entry
|
|
// - EBX : type to find
|
|
// OUTPUT:
|
|
// - ES:[AX] : Pointer to the attribute header in the MFT entry
|
|
FindAttributeHdr:
|
|
push cx
|
|
push si
|
|
push edx
|
|
mov si, ax
|
|
mov cx, word ptr es:[si+HEX(14)] // Get offset attribute
|
|
add si, cx
|
|
FindAttributeHdrLoop:
|
|
mov edx, dword ptr es:[si] // Get attribute type
|
|
cmp edx, ebx
|
|
jz AttrFound
|
|
cmp edx, HEX(ffffffff)
|
|
jz AttrNotFound
|
|
add cx, word ptr es:[si + 4] // Add size of the attribute
|
|
add si, word ptr es:[si + 4]
|
|
jmp FindAttributeHdrLoop
|
|
|
|
AttrNotFound:
|
|
// Attribute not found, reset the offset
|
|
xor cx, cx
|
|
AttrFound:
|
|
mov ax, cx
|
|
pop edx
|
|
pop si
|
|
pop cx
|
|
ret
|
|
|
|
PrintFreeldrError:
|
|
mov si, offset msgFreeldr
|
|
call PutChars
|
|
jmp Reboot
|
|
|
|
FreeLdr:
|
|
.word 'F', 'R', 'E', 'E', 'L', 'D', 'R', '.', 'S', 'Y', 'S'
|
|
msgFreeldr:
|
|
.ascii "FreeLdr not found, cannot boot", CR, LF, NUL
|
|
msgLoading:
|
|
.ascii "Loading FreeLoader...", CR, LF, NUL
|
|
msgFreeldrCompressed:
|
|
.ascii "freeldr.sys is a sparse, compressed or encrypted file, cannot boot", CR, LF, NUL
|
|
.endcode16
|
|
|
|
END
|