xref: /reactos/boot/freeldr/bootsect/ntfs.S (revision 84344399)
1/*
2 * PROJECT:         ReactOS Bootsector
3 * LICENSE:         GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * FILE:            boot/freeldr/bootsect/fat32.S
5 * PURPOSE:         Implementing boot sector for NTFS
6 * COPYRIGHT:       Copyright 2020 Sylvain Deverre (deverre.sylv@gmail.com)
7 */
8
9/* INCLUDES ******************************************************************/
10
11#include <asm.inc>
12#include <freeldr/include/arch/pc/x86common.h>
13
14.code16
15
16//ORG HEX(7c00)
17
18start:
19    jmp short main
20    nop
21OEMName:
22    .ASCII "NTFS    "   // NTFS signature
23BytesPerSector:
24    .word 512
25SectsPerCluster:
26    .byte 8
27ReservedSectors:
28    .word 0             // Unused by NTFS
29NumberOfFats:
30    .byte 0             // Unused by NTFS
31MaxRootEntries:
32    .word 0             // Unused by NTFS
33TotalSectors:
34    .word 0             // Unused by NTFS
35MediaDescriptor:
36    .byte HEX(0f8)
37SectorsPerFat:
38    .word 0             // Unused by NTFS
39SectorsPerTrack:
40    .word 0             // Should contain disk geometry
41NumberOfHeads:
42    .word 0             // Should contain disk geometry
43HiddenSectors:
44    .long 0             // Filled by format program
45TotalSectorsBig:
46    .long 0             // Unused by NTFS
47// NTFS inserted info
48BootDrive:
49    .byte HEX(80)
50CurrentHead:
51    .byte 0
52BootSignature:
53    .byte HEX(80)
54Unused:
55    .byte 0
56VolumeSectorCount:
57    .quad 0            // Must be patched by format program !
58MftLocation:
59    .quad 0            // Must be patched by format program !
60MftMirrorLocation:
61    .quad 0            // Must be patched by format program !
62ClustersPerMftRecord:
63    .long 0
64ClustersPerIndexRecord:
65    .long 0
66VolumeSerialNumber:
67    .quad 0
68Checksum:
69    .long 0
70
71main:
72    xor ax,ax               // Setup segment registers
73    mov ds,ax               // Make DS correct
74    mov es,ax               // Make ES correct
75    mov ss,ax               // Make SS correct
76    mov sp, HEX(7c00)
77    mov bp, sp              // Setup a stack
78
79    cmp byte ptr [BootDrive], HEX(0ff)    // If they have specified a boot drive then use it
80    jne GetDriveParameters
81
82    mov byte ptr [BootDrive], dl          // Save the boot drive
83
84GetDriveParameters:
85    mov  ah, 8
86    mov  dl, byte ptr [BootDrive]               // Get boot drive in dl
87    int  HEX(13)                                // Request drive parameters from the bios
88    jnc  CalcDriveSize                          // If the call succeeded then calculate the drive size
89
90    // If we get here then the call to the BIOS failed
91    // so just set CHS equal to the maximum addressable
92    // size
93    mov  cx, HEX(0ffff)
94    mov  dh, cl
95
96CalcDriveSize:
97    // Now that we have the drive geometry
98    // lets calculate the drive size
99    mov  bl, ch         // Put the low 8-bits of the cylinder count into BL
100    mov  bh, cl         // Put the high 2-bits in BH
101    shr  bh, 6          // Shift them into position, now BX contains the cylinder count
102    and  cl, HEX(3f)    // Mask off cylinder bits from sector count
103    // CL now contains sectors per track and DH contains head count
104    movzx eax, dh       // Move the heads into EAX
105    movzx ebx, bx       // Move the cylinders into EBX
106    movzx ecx, cl       // Move the sectors per track into ECX
107    inc   eax           // Make it one based because the bios returns it zero based
108    inc   ebx           // Make the cylinder count one based also
109    mul   ecx           // Multiply heads with the sectors per track, result in edx:eax
110    mul   ebx           // Multiply the cylinders with (heads * sectors) [stored in edx:eax already]
111
112    // We now have the total number of sectors as reported
113    // by the bios in eax, so store it in our variable
114    mov dword ptr ds:[BiosCHSDriveSize], eax
115
116LoadExtraBootCode:
117    // First we have to load our extra boot code at
118    // next sector into memory at [0000:7e00h]
119    mov  eax, HEX(1)
120    xor  edx, edx
121    mov  cx, 4
122    xor  bx, bx
123    mov  es, bx                                 // Read sector to [0000:7e00h]
124    mov  bx, HEX(7e00)
125    call ReadSectors
126    jmp  StartSearch
127
128
129// Reads logical sectors into [ES:BX]
130// EDX:EAX has logical sector number to read
131// CX has number of sectors to read
132ReadSectors:
133    push es
134    add  eax, dword ptr [HiddenSectors]   // Add offset from the disk beginning
135    test edx, edx
136    jnz  ReadSectorsLBA
137    cmp  eax, dword ptr ds:[BiosCHSDriveSize]   // Check if they are reading a sector outside CHS range
138    jae  ReadSectorsLBA                         // Yes - go to the LBA routine
139                                                // If at all possible we want to use LBA routines because
140                                                // They are optimized to read more than 1 sector per read
141
142    pushad                                      // Save logical sector number & sector count
143
144CheckInt13hExtensions:                          // Now check if this computer supports extended reads
145    mov  ah, HEX(41)                            // AH = 41h
146    mov  bx, HEX(55aa)                          // BX = 55AAh
147    mov  dl, byte ptr [BootDrive]               // DL = drive (80h-FFh)
148    int  HEX(13)                                // IBM/MS INT 13 Extensions - INSTALLATION CHECK
149    jc   ReadSectorsCHS                         // CF set on error (extensions not supported)
150    cmp  bx, HEX(0aa55)                         // BX = AA55h if installed
151    jne  ReadSectorsCHS
152    test cl,1                                   // CX = API subset support bitmap
153    jz   ReadSectorsCHS                         // Bit 0, extended disk access functions (AH=42h-44h,47h,48h) supported
154
155    popad                                       // Restore sector count & logical sector number
156
157ReadSectorsLBA:
158    pushad                                      // Save logical sector number & sector count
159
160    cmp  cx, 64                                 // Since the LBA calls only support 0x7F sectors at a time we will limit ourselves to 64
161    jbe  ReadSectorsSetupDiskAddressPacket      // If we are reading less than 65 sectors then just do the read
162    mov  cx, 64                                 // Otherwise read only 64 sectors on this loop iteration
163
164ReadSectorsSetupDiskAddressPacket:
165    mov word ptr ds:[LBASectorsRead],cx
166    push edx
167    push eax                                // Put 64-bit logical block address on stack
168    push es                                 // Put transfer segment on stack
169    push bx                                 // Put transfer offset on stack
170    push cx                                 // Set transfer count
171    push 16                                 // Set size of packet to 10h
172    mov  si, sp                             // Setup disk address packet on stack
173
174    mov  dl, byte ptr [BootDrive]           // Drive number
175    mov  ah, HEX(42)                        // Int 13h, AH = 42h - Extended Read
176    int  HEX(13)                            // Call BIOS
177    jc   PrintDiskError                     // If the read failed then abort
178
179    add  sp, 16                             // Remove disk address packet from stack
180
181    popad                                   // Restore sector count & logical sector number
182
183    push bx
184    mov  ebx, dword ptr ds:[LBASectorsRead]
185    add  eax, ebx                           // Increment sector to read
186    adc  edx, 0
187    shl  ebx, 5
188    mov  dx, es
189    add  dx, bx                             // Setup read buffer for next sector
190    mov  es, dx
191    pop  bx
192
193    sub  cx, word ptr ds:[LBASectorsRead]
194    jnz  ReadSectorsLBA                     // Read next sector
195
196    pop es
197    ret
198
199LBASectorsRead:
200    .long    0
201
202
203// Reads logical sectors into [ES:BX]
204// EAX has logical sector number to read
205// CX has number of sectors to read
206ReadSectorsCHS:
207    popad                                        // Get logical sector number & sector count off stack
208
209ReadSectorsCHSLoop:
210    pushad
211    xor  edx, edx
212    movzx ecx, word ptr [SectorsPerTrack]
213    div  ecx                                    // Divide logical by SectorsPerTrack
214    inc  dl                                     // Sectors numbering starts at 1 not 0
215    mov  cl, dl                                 // Sector in CL
216    mov  edx, eax
217    shr  edx, 16
218    div  word ptr [NumberOfHeads]               // Divide logical by number of heads
219    mov  dh, dl                                 // Head in DH
220    mov  dl, byte ptr [BootDrive]               // Drive number in DL
221    mov  ch, al                                 // Cylinder in CX
222    ror  ah, 2                                  // Low 8 bits of cylinder in CH, high 2 bits
223                                                //  in CL shifted to bits 6 & 7
224    or   cl, ah                                 // Or with sector number
225    mov  ax, HEX(0201)
226    int  HEX(13)    // DISK - READ SECTORS INTO MEMORY
227                     // AL = number of sectors to read, CH = track, CL = sector
228                     // DH = head, DL = drive, ES:BX -> buffer to fill
229                     // Return: CF set on error, AH = status (see AH=01h), AL = number of sectors read
230
231    jc   PrintDiskError                         // If the read failed then abort
232
233    popad
234
235    inc  eax                                    // Increment Sector to Read
236
237    mov  dx, es
238    add  dx, 32                                 // Increment read buffer for next sector
239    mov  es, dx
240
241    loop ReadSectorsCHSLoop                     // Read next sector
242
243    pop es
244    ret
245
246// Displays a disk error message
247// And reboots
248PrintDiskError:
249    mov  si, offset msgDiskError        // Bad boot disk message
250    call PutChars                       // Display it
251
252    jmp  Reboot
253
254// Displays a file system error message
255// And reboots
256PrintFileSystemError:
257    mov  si, offset msgFileSystemError  // FreeLdr not found message
258    call PutChars                       // Display it
259
260Reboot:
261    mov  si, offset msgAnyKey           // Press any key message
262    call PutChars                       // Display it
263    xor  ax, ax
264    int  HEX(16)                        // Wait for a keypress
265    int  HEX(19)                        // Reboot
266
267PutChars:
268    lodsb
269    or   al, al
270    jz   short Done
271    mov  ah, HEX(0e)
272    mov  bx, 7
273    int  HEX(10)
274    jmp  short PutChars
275Done:
276    ret
277
278msgDiskError:
279    .ascii "Disk error", CR, LF, NUL
280msgFileSystemError:
281    .ascii "File system error", CR, LF, NUL
282msgAnyKey:
283    .ascii "Press any key to restart", CR, LF, NUL
284
285SectsPerMFT:
286    .word 0
287
288MFTStartSector:
289    .quad 0
290
291BiosCHSDriveSize:
292    .long 0
293
294.org 509 // Pad to 509 bytes
295BootPartition:
296    .byte 1
297
298BootFlagSignature:
299    .word HEX(0aa55)    // BootSector signature
300
301// End of Bootsector
302// Next sector starts as sector 2, since boot sector is
303// as a file under NTFS ($Boot, which has inode number 7)
304// and takes the two first clusters of the volume (which means
305// 16 sectors, checked on Windows and mkfs.ntfs from ntfsutils)
306
307#define ATTRIBUTE_DATA HEX(80)
308#define ATTRIBUTE_INDEX_ROOT HEX(90)
309#define ATTRIBUTE_INDEX_ALLOCATION HEX(A0)
310#define FILE_MAGIC HEX(454c4946)
311#define INDX_MAGIC HEX(58444e49)
312#define NTFS_FILE_ROOT 5
313StartSearch:
314    // Compute MFT start sector
315    mov   eax, dword ptr [MftLocation]
316    mov   cl, byte ptr [SectsPerCluster]
317    movzx ecx, cx
318    mul   ecx
319    mov   dword ptr [MFTStartSector], eax
320
321    // Compute size of MFT entry in sectors
322    xor   ax, ax
323    mov   al, byte ptr [ClustersPerMftRecord]
324    test  al, al
325    js    NegativeOffset
326    mov   cx, word ptr [SectsPerCluster]
327    mul   cx
328    mov   word ptr [SectsPerMFT], cx
329    jmp   SearchNext
330    NegativeOffset:
331    // ClustersPerMftRecord is negative, so we need to perform 1 << (-ClustersPerMftRecord)
332    // to get the number of bytes needed for a MFT entry
333    not   al
334    inc   al
335    mov   cl, al
336    xor   ax, ax
337    inc   ax
338    shl   ax, cl
339
340    // But here want to store sectors, so we need to divide by BytesPerSector
341    xor   dx, dx
342    mov   cx, word ptr [BytesPerSector]
343    div   cx
344    mov   word ptr [SectsPerMFT], ax
345
346    SearchNext:
347    mov   bp, sp
348    sub   sp, HEX(10)
349
350    // Read Root Directory MFT into [2000:0]
351    mov   ax, HEX(2000)
352    mov   es, ax
353    xor   bx, bx
354    xor   edx, edx
355    mov   eax, NTFS_FILE_ROOT
356    call  ReadInode
357
358    // Finds freeldr.sys into index root's B-Tree
359    // and return its MFT index
360    xor   ax, ax
361    call  ExploreIndexRoot
362
363    // Read the MFT entry of freeldr.sys into [A00:0]
364    push  eax
365    mov   ax, HEX(A00)
366    mov   es, ax
367    pop   eax
368    call  ReadInode
369
370    xor   ax, ax
371    mov   ebx, HEX(30)
372    call  FindAttributeHdr
373    mov   si, ax
374    // Move to the attribute data
375    add   si, word ptr es:[si + HEX(14)]
376
377    // We don't support compressed, sparse or encrypted Freeldr
378    test  dword ptr es:[si + HEX(38)], HEX(4a00)
379    jnz   CompressedFreeldr
380
381    // Compute size in clusters
382    mov   eax, dword ptr es:[si + HEX(28)]
383    mov   cl, byte ptr [SectsPerCluster]
384    xor   edx, edx
385    div   ecx
386    mov   cx, word ptr [BytesPerSector]
387    movzx ecx, cx
388    xor   edx, edx
389    div   ecx
390    mov   edx, eax
391
392    push  ax
393    mov   si, offset msgLoading
394    call  PutChars
395    pop   ax
396
397    xor   ax, ax
398    mov   ebx, HEX(80)
399    call  FindAttributeHdr
400    mov   si, ax
401    add   ax, word ptr es:[si + HEX(20)]
402    xor   ecx, ecx
403    xor   bx, bx
404    push  FREELDR_BASE / 16
405    pop   fs
406    FreeLdrLoad:
407        pushad
408        call  ReadNonResidentAttribute
409        popad
410        mov   bx, fs
411        add   bx, HEX(100)
412        mov   fs, bx
413        xor   bx, bx
414        inc   ecx
415        cmp   ecx, edx
416        jbe   FreeLdrLoad
417
418    mov    dl, byte ptr [BootDrive]
419    mov    dh, byte ptr [BootPartition]
420    ljmp16 0, FREELDR_BASE
421
422// Error message if Freeldr is compressed, encrypted or sparse
423CompressedFreeldr:
424    mov   si, offset msgFreeldrCompressed
425    call  PutChars
426    jmp   Reboot
427
428// Finds Freeldr.sys into the directory tree and returns the
429// inode index
430// INPUT:
431//  - ES:[AX] address of the MFT record
432// OUTPUT:
433//  - EDX:EAX MFT number of the found file
434ExploreIndexRoot:
435    #define fileRecordBase 2
436    #define fileRecord 4
437    #define indexRootData 6
438    #define allocationRunList 8
439
440    push  bp
441    mov   bp, sp
442    push  bx
443    push  si
444    push  di
445    sub   sp, HEX(10)
446    mov   word ptr [bp - fileRecordBase], es
447    mov   word ptr [bp - fileRecord], ax
448
449    mov   ebx, ATTRIBUTE_INDEX_ROOT
450    call  FindAttributeHdr
451    test  ax, ax
452    jz    PrintFileSystemError         // fail if no INDEX_ROOT
453
454    mov   si, ax
455    cmp   byte ptr es:[si + 8], HEX(0)
456    jnz   PrintFileSystemError         // fail if attribute is non-resident
457
458    add   si, word ptr es:[si + HEX(14)]
459    cmp   dword ptr es:[si], HEX(30)
460    jnz   PrintFileSystemError                  // fail if FILE_NAME attribute isn't indexed
461
462    mov   word ptr [bp - indexRootData], si
463    mov   ax, word ptr [bp - fileRecord]
464
465    test  byte ptr es:[si + HEX(0c)], 1
466    jz    ExploreNext                          // Skip index allocation lookup if we don't have children
467
468    mov   ebx, ATTRIBUTE_INDEX_ALLOCATION
469    call  FindAttributeHdr
470    test  ax, ax
471    jz    PrintFileSystemError                  // No INDEX_ALLOCATION found
472
473    mov   si, ax
474    cmp   byte ptr es:[si + 8], 1
475    jnz   PrintFileSystemError                  // Fail if attribute is resident (shouldn't be)
476
477    add   si, word ptr es:[si + HEX(20)]
478    mov   word ptr [bp - allocationRunList], si // save run list
479
480    ExploreNext:
481    mov   si, word ptr [bp - indexRootData]
482    lea   si, [si + 32]                         // get the first INDEX_ENTRY
483
484    // Main search loop. We browse the B-Tree which contains directory entries
485    // SI contains the current index entry.
486    NodeCheck:
487        test  word ptr es:[si + HEX(0C)], 2
488        jnz   NodeCheckLastNode
489
490        mov   cl, byte ptr es:[si + HEX(50)]
491        movzx cx, cl
492        lea   si, [si + HEX(52)]
493        mov   di, offset FreeLdr
494        call  CompareWideStrInsensitive
495        lea   si, [si - HEX(52)]
496        test  ax, ax
497        jz    RootIndexFound
498        test  word ptr es:[si + HEX(0C)], 1
499        jz    ContinueSearch
500
501        test  ax, HEX(f000)
502        jnz   LookupChildNode                  // if result < 0 then explore child node
503
504        ContinueSearch:
505        add   si, word ptr es:[si + 8]
506        jmp   NodeCheck
507
508        RootIndexFound:
509        mov   eax, dword ptr es:[si]           // We found root entry, return with its MFT number
510        mov   dx, word ptr es:[si + 4]         // Return high part into edx.
511                                               // We take only the first word, the high word contains the
512                                               // sequence number
513        movzx edx, dx
514        jmp   ExitIndexTree
515
516        NodeCheckLastNode:
517        test  word ptr es:[si + HEX(0C)], 1
518        jz    PrintFreeldrError
519
520        LookupChildNode:
521        // Take the right LCN at the end of the index record
522        add   si, word ptr es:[si + 8]
523        mov   ecx, dword ptr es:[si - 8]
524
525        // Read the fixed up LCN
526        mov   bx, word ptr [bp - allocationRunList]
527        mov   ax, word ptr [bp - fileRecordBase]
528        mov   es, ax
529        mov   ax, HEX(9000)
530        mov   fs, ax
531        mov   ax, bx
532        xor   bx, bx
533        call  ReadINDX
534
535        // Go again to the loop but with the child node list
536        mov   ax, HEX(9000)
537        mov   es, ax
538        mov   si, 0
539        add   si, es:[si + HEX(18)] // Go to the first node
540        lea   si, [si + HEX(18)]
541        jmp   NodeCheck
542
543    ExitIndexTree:
544    pop   di
545    pop   si
546    mov   sp, bp
547    pop   bp
548    ret
549
550// 64-bit multiplication
551// EDX:EAX operand
552// ECX operator
553Multiply64:
554    push bp
555    mov  bp, sp
556    sub  sp, 12
557    // Save the high part of the multiplication
558    mov  dword ptr [bp - 4], edx
559
560    // Multiply the low part and save the result
561    mul  ecx
562    mov  dword ptr[bp - 8], eax
563    mov  dword ptr[bp - 12], edx
564
565    // Multiply the high part and add the carry
566    mov  eax, dword ptr [bp - 4]
567    mul  ecx
568    add  eax, dword ptr [bp - 12]
569
570    // Format correctly the number
571    mov  edx, eax
572    mov  eax, dword ptr [bp - 8]
573    mov  sp, bp
574    pop  bp
575    ret
576
577// Compare case-insensitive strings
578// [ES:SI] - the source file name
579// [DS:DI] - the destination file name
580// CX - compare length
581CompareWideStrInsensitive:
582    push bx
583    push si
584    push di
585    movzx cx, cl
586    CmpLoop:
587        mov ax, word ptr es:[si]
588        cmp ax, 'a'
589        jl NoUpper
590        cmp ax, 'z'
591        jg NoUpper
592        sub ax, HEX(20)
593        NoUpper:
594        mov bx, word ptr ds:[di]
595        sub bx, ax
596        test bx, bx
597        jnz  CompareFail
598        add  si, 2
599        add  di, 2
600        dec cx
601        jnz CmpLoop
602    CompareFail:
603    mov ax, bx
604    pop di
605    pop si
606    pop bx
607    ret
608
609// Reads a NTFS cluster
610// INPUT:
611//  - EDX:EAX     : cluster number to read
612// OUTPUT:
613//  - ES:BX  : address to read
614ReadCluster:
615    push  ecx
616    mov   cl, byte ptr [SectsPerCluster]   // Convert clusters to sectors
617    movzx ecx, cx
618    call  Multiply64
619    call  ReadSectors
620    pop   ecx
621    ret
622
623// Reads a MFT entry
624// INPUT:
625//  - EDX:EAX      : MFT inode
626// OUTPUT:
627//  - ES:[BX] : address to read
628ReadInode:
629    push  ecx
630    push  si
631    push  di
632
633    push  edx
634    mov   cx, word ptr [SectsPerMFT]       // Get the correct number of sectors for the FILE entry
635    movzx ecx, cx
636    mul   ecx
637    movzx eax, ax
638    add   eax, dword ptr [MFTStartSector]  // Add it to the start of the MFT
639    mov   cx, word ptr [SectsPerMFT]
640    movzx ecx, cx
641    pop   edx
642    call  ReadSectors
643
644    cmp   dword ptr es:[bx], FILE_MAGIC // Ensure we get a valid FILE record
645    jnz   PrintFileSystemError
646
647    call  ApplyFixups
648
649    pop   di
650    pop   si
651    pop   ecx
652    ret
653
654#define UpdateSequenceOffset 4
655#define UpdateSequenceLen 6
656// Apply fixups to INDX and FILE records
657// INPUT:
658//  - ES:[BX] - pointer to the record to fixup
659ApplyFixups:
660    push  si
661    push  di
662    mov   si, bx
663    add   si, word ptr es:[bx + UpdateSequenceOffset]
664    xor   cx, cx
665    inc   cx
666    FixupLoop:
667        cmp  cx, word ptr es:[bx + UpdateSequenceLen]
668        jz  EndFixupLoop
669        mov   si, bx
670        add   si, word ptr es:[bx + UpdateSequenceOffset]
671        mov   ax, word ptr es:[si]     // Get first fixup value
672
673        mov   di, cx
674        shl   di, 9
675        add   di, bx
676        sub   di, 2
677        cmp   ax, word ptr es:[di]     // Check fixup value
678        jnz   PrintFileSystemError     // Fixup is corrupted, so print error
679        inc   cx
680        add   si, cx
681        mov   ax, word ptr es:[si]     // Apply fixup
682        mov   word ptr es:[di], ax
683
684        jmp   FixupLoop
685    EndFixupLoop:
686
687    pop di
688    pop si
689    ret
690
691// Reads a non-resident attribute
692// INPUT:
693//  - ES:[AX] : Address of the data runs
694//  - ECX     : LCN to read
695// OUTPUT:
696//  - FS:[BX] : Address to write to
697ReadNonResidentAttribute:
698    #define currentLCN 4
699    #define offsetWrite 8
700    #define startReadLCN HEX(0C)
701    push  bp
702    mov   bp, sp
703    sub   sp, HEX(10)
704    push  edx
705    push  si
706    mov   dword ptr [bp - currentLCN], 0          // Store the current LCN
707    mov   word ptr [bp - offsetWrite], bx
708    mov   dword ptr [bp - startReadLCN], ecx
709    mov   si, ax
710    xor   edx, edx
711    RunLoop:
712        mov   al, byte ptr es:[si]
713        test  al, al
714        jz    NotFound
715        call  UnpackRun
716        add   dword ptr [bp - currentLCN], eax
717        cmp   dword ptr [bp - startReadLCN], ecx
718        jb    FoundRun
719        sub   dword ptr [bp - startReadLCN], ecx  // Decrement the cluster
720        jmp   RunLoop
721    FoundRun:
722    mov   ebx, dword ptr [bp - currentLCN]
723    mov   ecx, dword ptr [bp - startReadLCN]
724    add   ebx, ecx
725
726    push es
727    mov  ax, fs
728    mov  es, ax
729    mov  eax, ebx
730    mov  bx, word ptr [bp - offsetWrite]
731    call ReadCluster
732    pop  es
733    jmp  RunSearchEnd
734    NotFound:
735    xor  ax, ax
736    RunSearchEnd:
737    pop  si
738    pop  edx
739    mov  sp, bp
740    pop  bp
741    ret
742
743// Decodes a run in the runlist
744// INPUT:
745//  - ES:[SI] : address of the run
746// OUTPUT:
747//  - EAX : Unpacked LCN
748//  - ECX : Unpacked run length (in sectors)
749//  - SI  : Next run in the run list
750UnpackRun:
751    push bp
752    mov  bp, sp
753    sub  sp, HEX(10)
754    push ebx
755
756    // Unpack run header
757    mov  bl, byte ptr es:[si]
758    inc  si
759
760    mov  bh, bl
761    shr  bh, 4
762    and  bl, 7
763    mov  byte ptr [bp-2], bh     // BH contains the LCN length
764    mov  byte ptr [bp-1], bl     // BL contains the number of cluster length
765
766    mov  al, bl
767    call UnpackLen
768    mov  dword ptr [bp - 8], ebx
769
770    mov  al, byte ptr [bp-2]
771    call UnpackLen
772    mov  cl, byte ptr es:[si-1]  // Fixup sign if last byte is > 255
773    test cl, HEX(80)
774    jz   NoSign
775    not  eax
776    add  ebx, eax
777
778    NoSign:
779    mov  eax, ebx
780    mov  ecx, dword ptr [bp - 8]
781    pop  ebx
782    mov  sp, bp
783    pop  bp
784    ret
785
786
787// Auxiliary function that unpacks n bytes in the memory
788// INPUT:
789//  - AL  : size to unpack (max 4 bytes)
790// OUTPUT:
791//  - EAX : the mask used to unpack (for negative number fixup)
792//  - EBX : the unpacked number
793//  - SI  : Next byte to read
794UnpackLen:
795    push  cx
796    movzx ax, al
797
798    // Read the whole DWORD and then compute a mask to remove
799    // unneeded bytes to get correct size
800    xor   ebx, ebx
801    mov   ebx, dword ptr es:[si]
802    add   si, ax
803
804    cmp   al, 4
805    jnz   UnpackLen_not4
806    xor   eax, eax
807    dec   eax
808    jmp   UnpackLen_mask
809
810UnpackLen_not4:
811    mov   cl, al    // Compute mask (2^(8*len) - 1)
812    shl   cl, 3
813    xor   eax, eax
814    inc   eax
815    shl   eax, cl
816    dec   eax
817
818UnpackLen_mask:
819    and   ebx, eax // Apply mask
820    pop   cx
821    ret
822
823// Reads an INDX sector and applies fixups
824// INPUT:
825//  - ES:[AX] : Address of the data runs
826//  - ECX     : LCN to read
827// OUTPUT:
828//  - FS:[BX] : Address to write to
829ReadINDX:
830    push  es
831    push  bx
832    call  ReadNonResidentAttribute
833    test  ax, ax
834    jz    PrintFileSystemError
835    cmp   dword ptr fs:[0], INDX_MAGIC
836    jnz   PrintFileSystemError  // jump if not valid
837    pop   bx
838
839    mov   ax, fs
840    mov   es, ax
841    call  ApplyFixups
842    pop   es
843    ret
844
845// Finds an attribute header into the MFT
846// INPUT:
847//  - ES:[AX] : pointer to the MFT entry
848//  - EBX     : type to find
849// OUTPUT:
850//  - ES:[AX] : Pointer to the attribute header in the MFT entry
851FindAttributeHdr:
852    push  cx
853    push  si
854    push  edx
855    mov   si, ax
856    mov   cx, word ptr es:[si+HEX(14)]  // Get offset attribute
857    add   si, cx
858    FindAttributeHdrLoop:
859        mov   edx, dword ptr es:[si]    // Get attribute type
860        cmp   edx, ebx
861        jz    AttrFound
862        cmp   edx, HEX(ffffffff)
863        jz    AttrNotFound
864        add   cx, word ptr es:[si + 4]  // Add size of the attribute
865        add   si, word ptr es:[si + 4]
866        jmp   FindAttributeHdrLoop
867
868    AttrNotFound:
869    // Attribute not found, reset the offset
870    xor   cx, cx
871    AttrFound:
872    mov   ax, cx
873    pop   edx
874    pop   si
875    pop   cx
876    ret
877
878PrintFreeldrError:
879    mov  si, offset msgFreeldr
880    call PutChars
881    jmp  Reboot
882
883FreeLdr:
884    .word 'F', 'R', 'E', 'E', 'L', 'D', 'R', '.', 'S', 'Y', 'S'
885msgFreeldr:
886    .ascii "FreeLdr not found, cannot boot", CR, LF, NUL
887msgLoading:
888    .ascii "Loading FreeLoader...", CR, LF, NUL
889msgFreeldrCompressed:
890    .ascii "freeldr.sys is a sparse, compressed or encrypted file, cannot boot", CR, LF, NUL
891.endcode16
892
893END
894