; FAT.ASM ; FAT12/16 Boot Sector ; Copyright (c) 1998, 2001, 2002 Brian Palmer ; This is a FAT12/16 file system boot sector ; that searches the entire root directory ; for the file freeldr.sys and loads it into ; memory. ; ; The stack is set to 0000:7BF2 so that the first ; WORD pushed will be placed at 0000:7BF0 ; ; The DWORD at 0000:7BFC or BP-04h is the logical ; sector number of the start of the data area. ; ; The DWORD at 0000:7BF8 or BP-08h is the total ; sector count of the boot drive as reported by ; the computers bios. ; ; The WORD at 0000:7BF6 or BP-0ah is the offset ; of the ReadSectors function in the boot sector. ; ; The WORD at 0000:7BF4 or BP-0ch is the offset ; of the ReadCluster function in the boot sector. ; ; The WORD at 0000:7BF2 or BP-0eh is the offset ; of the PutChars function in the boot sector. ; ; When it locates freeldr.sys on the disk it will ; load the first sector of the file to 0000:8000 ; With the help of this sector we should be able ; to load the entire file off the disk, no matter ; how fragmented it is. ; ; We load the entire FAT table into memory at ; 7000:0000. This improves the speed of floppy disk ; boots dramatically. BootSectorStackTop equ 0x7bf2 DataAreaStartHigh equ 0x2 DataAreaStartLow equ 0x4 BiosCHSDriveSizeHigh equ 0x6 BiosCHSDriveSizeLow equ 0x8 BiosCHSDriveSize equ 0x8 ReadSectorsOffset equ 0xa ReadClusterOffset equ 0xc PutCharsOffset equ 0xe org 7c00h segment .text bits 16 start: jmp short main nop OEMName db 'FrLdr1.0' BytesPerSector dw 512 SectsPerCluster db 1 ReservedSectors dw 1 NumberOfFats db 2 MaxRootEntries dw 224 TotalSectors dw 2880 MediaDescriptor db 0f0h SectorsPerFat dw 9 SectorsPerTrack dw 18 NumberOfHeads dw 2 HiddenSectors dd 0 TotalSectorsBig dd 0 BootDrive db 0xff Reserved db 0 ExtendSig db 29h SerialNumber dd 00000000h VolumeLabel db 'NO NAME ' FileSystem db 'FAT12 ' main: xor ax,ax mov ss,ax mov bp,7c00h mov sp,BootSectorStackTop ; Setup a stack mov ds,ax ; Make DS correct mov es,ax ; Make ES correct cmp BYTE [BYTE bp+BootDrive],BYTE 0xff ; If they have specified a boot drive then use it jne GetDriveParameters mov [BYTE bp+BootDrive],dl ; Save the boot drive GetDriveParameters: mov ah,08h mov dl,[BYTE bp+BootDrive] ; Get boot drive in dl int 13h ; 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,0ffffh 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,3fh ; 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 [BYTE bp-BiosCHSDriveSize],eax ; Now we must find our way to the first sector of the root directory xor ax,ax xor cx,cx mov al,[BYTE bp+NumberOfFats] ; Number of fats mul WORD [BYTE bp+SectorsPerFat] ; Times sectors per fat add ax,WORD [BYTE bp+HiddenSectors] adc dx,WORD [BYTE bp+HiddenSectors+2] ; Add the number of hidden sectors add ax,WORD [BYTE bp+ReservedSectors] ; Add the number of reserved sectors adc dx,cx ; Add carry bit mov WORD [BYTE bp-DataAreaStartLow],ax ; Save the starting sector of the root directory mov WORD [BYTE bp-DataAreaStartHigh],dx ; Save it in the first 4 bytes before the boot sector mov si,WORD [BYTE bp+MaxRootEntries] ; Get number of root dir entries in SI pusha ; Save 32-bit logical start sector of root dir ; DX:AX now has the number of the starting sector of the root directory ; Now calculate the size of the root directory xor dx,dx mov ax,0020h ; Size of dir entry mul si ; Times the number of entries mov bx,[BYTE bp+BytesPerSector] add ax,bx dec ax div bx ; Divided by the size of a sector ; AX now has the number of root directory sectors add [BYTE bp-DataAreaStartLow],ax ; Add the number of sectors of the root directory to our other value adc [BYTE bp-DataAreaStartHigh],cx ; Now the first 4 bytes before the boot sector contain the starting sector of the data area popa ; Restore root dir logical sector start to DX:AX LoadRootDirSector: mov bx,7e0h ; We will load the root directory sector mov es,bx ; Right after the boot sector in memory xor bx,bx ; We will load it to [0000:7e00h] xor cx,cx ; Zero out CX inc cx ; Now increment it to 1, we are reading one sector xor di,di ; Zero out di push es ; Save ES because it will get incremented by 20h call ReadSectors ; Read the first sector of the root directory pop es ; Restore ES (ES:DI = 07E0:0000) SearchRootDirSector: cmp [es:di],ch ; If the first byte of the directory entry is zero then we have jz ErrBoot ; reached the end of the directory and FREELDR.SYS is not here so reboot pusha ; Save all registers mov cl,0xb ; Put 11 in cl (length of filename in directory entry) mov si,filename ; Put offset of filename string in DS:SI repe cmpsb ; Compare this directory entry against 'FREELDR SYS' popa ; Restore all the registers jz FoundFreeLoader ; If we found it then jump dec si ; SI holds MaxRootEntries, subtract one jz ErrBoot ; If we are out of root dir entries then reboot add di,BYTE +0x20 ; Increment DI by the size of a directory entry cmp di,0200h ; Compare DI to 512 (DI has offset to next dir entry, make sure we haven't gone over one sector) jc SearchRootDirSector ; If DI is less than 512 loop again jmp short LoadRootDirSector ; Didn't find FREELDR.SYS in this directory sector, try again FoundFreeLoader: ; We found freeldr.sys on the disk ; so we need to load the first 512 ; bytes of it to 0000:8000 ; ES:DI has dir entry (ES:DI == 07E0:XXXX) mov ax,WORD [es:di+1ah] ; Get start cluster push ax ; Save start cluster push WORD 800h ; Put 800h on the stack and load it pop es ; Into ES so that we load the cluster at 0000:8000 call ReadCluster ; Read the cluster pop ax ; Restore start cluster of FreeLoader ; Save the addresses of needed functions so ; the helper code will know where to call them. mov WORD [BYTE bp-ReadSectorsOffset],ReadSectors ; Save the address of ReadSectors mov WORD [BYTE bp-ReadClusterOffset],ReadCluster ; Save the address of ReadCluster mov WORD [BYTE bp-PutCharsOffset],PutChars ; Save the address of PutChars ; Now AX has start cluster of FreeLoader and we ; have loaded the helper code in the first 512 bytes ; of FreeLoader to 0000:8000. Now transfer control ; to the helper code. Skip the first three bytes ; because they contain a jump instruction to skip ; over the helper code in the FreeLoader image. ;jmp 0000:8003h jmp 8003h ; Displays an error message ; And reboots ErrBoot: mov si,msgFreeLdr ; FreeLdr not found message call PutChars ; Display it Reboot: mov si,msgAnyKey ; Press any key message call PutChars ; Display it xor ax,ax int 16h ; Wait for a keypress int 19h ; Reboot PutChars: lodsb or al,al jz short Done mov ah,0eh mov bx,07h int 10h jmp short PutChars Done: retn ; Displays a bad boot message ; And reboots BadBoot: mov si,msgDiskError ; Bad boot disk message call PutChars ; Display it jmp short Reboot ; Reads cluster number in AX into [ES:0000] ReadCluster: ; StartSector = ((Cluster - 2) * SectorsPerCluster) + ReservedSectors + HiddenSectors; dec ax ; Adjust start cluster by 2 dec ax ; Because the data area starts on cluster 2 xor ch,ch mov cl,BYTE [BYTE bp+SectsPerCluster] mul cx ; Times sectors per cluster add ax,[BYTE bp-DataAreaStartLow] ; Add start of data area adc dx,[BYTE bp-DataAreaStartHigh] ; Now we have DX:AX with the logical start sector of OSLOADER.SYS xor bx,bx ; We will load it to [ES:0000], ES loaded before function call ;mov cl,BYTE [BYTE bp+SectsPerCluster]; Sectors per cluster still in CX ;call ReadSectors ;ret ; Reads logical sectors into [ES:BX] ; DX:AX has logical sector number to read ; CX has number of sectors to read ReadSectors: ; We can't just check if the start sector is ; in the BIOS CHS range. We have to check if ; the start sector + length is in that range. pusha dec cx add ax,cx adc dx,byte 0 cmp dx,WORD [BYTE bp-BiosCHSDriveSizeHigh] ; Check if they are reading a sector within CHS range ja ReadSectorsLBA ; No - go to the LBA routine jb ReadSectorsCHS ; Yes - go to the old CHS routine cmp ax,WORD [BYTE bp-BiosCHSDriveSizeLow] ; Check if they are reading a sector within CHS range jbe ReadSectorsCHS ; Yes - go to the old CHS routine ReadSectorsLBA: popa ReadSectorsLBALoop: pusha ; Save logical sector number & sector count o32 push byte 0 push dx ; Put 64-bit logical push ax ; block address on stack push es ; Put transfer segment on stack push bx ; Put transfer offset on stack push byte 1 ; Set transfer count to 1 sector push byte 0x10 ; Set size of packet to 10h mov si,sp ; Setup disk address packet on stack ; We are so totally out of space here that I am forced to ; comment out this very beautifully written piece of code ; It would have been nice to have had this check... ;CheckInt13hExtensions: ; Now make sure this computer supports extended reads ; mov ah,0x41 ; AH = 41h ; mov bx,0x55aa ; BX = 55AAh ; mov dl,[BYTE bp+BootDrive] ; DL = drive (80h-FFh) ; int 13h ; IBM/MS INT 13 Extensions - INSTALLATION CHECK ; jc PrintDiskError ; CF set on error (extensions not supported) ; cmp bx,0xaa55 ; BX = AA55h if installed ; jne PrintDiskError ; test cl,1 ; CX = API subset support bitmap ; jz PrintDiskError ; Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported ; Good, we're here so the computer supports LBA disk access ; So finish the extended read mov dl,[BYTE bp+BootDrive] ; Drive number mov ah,42h ; Int 13h, AH = 42h - Extended Read int 13h ; Call BIOS jc BadBoot ; If the read failed then abort add sp,byte 0x10 ; Remove disk address packet from stack popa ; Restore sector count & logical sector number inc ax ; Increment Sector to Read adc dx,byte 0 push bx mov bx,es add bx,byte 20h ; Increment read buffer for next sector mov es,bx pop bx loop ReadSectorsLBALoop ; Read next sector ret ; Reads logical sectors into [ES:BX] ; DX:AX has logical sector number to read ; CX has number of sectors to read ; CarryFlag set on error ReadSectorsCHS: popa ReadSectorsCHSLoop: pusha xchg ax,cx xchg ax,dx xor dx,dx div WORD [BYTE bp+SectorsPerTrack] xchg ax,cx div WORD [BYTE bp+SectorsPerTrack] ; Divide logical by SectorsPerTrack inc dx ; Sectors numbering starts at 1 not 0 xchg cx,dx div WORD [BYTE bp+NumberOfHeads] ; Number of heads mov dh,dl ; Head to DH, drive to DL mov dl,[BYTE bp+BootDrive] ; Drive number 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,0201h int 13h ; 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 BadBoot popa inc ax ;Increment Sector to Read jnz NoCarryCHS inc dx NoCarryCHS: push bx mov bx,es add bx,byte 20h mov es,bx pop bx ; Increment read buffer for next sector loop ReadSectorsCHSLoop ; Read next sector ret msgDiskError db 'Disk error',0dh,0ah,0 msgFreeLdr db 'freeldr.sys not found',0dh,0ah,0 ; Sorry, need the space... ;msgAnyKey db 'Press any key to restart',0dh,0ah,0 msgAnyKey db 'Press any key',0dh,0ah,0 filename db 'FREELDR SYS' times 509-($-$$) db 0 ; Pad to 509 bytes BootPartition: db 0 BootSignature: dw 0aa55h ; BootSector signature