NTFS文件系统启动扇区代码(简化版)

;====================================================================
;
; FlyingDragon OS Boot Sector FOR NTFS File System
;
; Author: Jack
; V0.01 2005-9-1 20:58
;
; Build : nasm -f bin NTFS.ASM -oNTFS.BIN
;     
;====================================================================================
;
; BIOS在启动中的角色:
;     (1) BIOS装载引导驱动器上的0扇区(CHS = 0:0:1)内容到内存线性地址7C00H处;
;     (2) BIOS检查所装载的扇区是否有启动标记(510、511字节分别为55H和AAH);
;     (3) CPU寄存器DL被设置为分配给引导驱动器的驱动器号,00H为软驱A,80H为硬盘C;
;     (4) BIOS跳转到其装载的扇区中的代码(即7C00H处),将控制权转交给引导代码。
;
; 引导代码应该初始化以下寄存器:
;     (1) DS:某些BIOS设置其值为0,某些设置其为40H,它应该被设置为(7C00H-BOOT_ORG)/16;
;         其中,BOOT_ORG为引导代码的ORG值,该值通常为7C00H(这意味着DS应设置为0);
;     (2) SS和SP(堆栈):这两个寄存器的初始值依赖于BIOS;
;     (3) CS个IP(通过JMP指令):大多数的BIOS进入启动代码的地址为0000:7C00H,但是某些
;         BIOS却跳转到07C0:0000H。由于短跳转和条件跳转是IP相关的,因此如果没有使用
;        远跳转或者绝对跳转,则不需要重置CS和IP;然而,DS仍旧必须是正确的值。
;
;=====================================================================================
;
; 常规内存( 0000 0000H - 000F FFFFH,即0-1MB )在系统启动时的使用情况
;
;=====================================================================================
;
;         ---------------------------------
;         |    0000 0000 - 0000 03FF          |    1024B      IDT  read only
;         |-------------------------------|
;         |    0000 0400 - 0000 04FF        |    256B     BIOS Data Area , read only    
;         |-------------------------------|
;         |    0000 0500 - 0000 7BFF         |*    30464B    Free Memory , read/write     (29.75KB)
;         |------------------------------    |
;         |    0000 7C00 - 0000 7DFF          |    512B    Boot Sector , read/write    
;         |------------------------------    |
;         |    0000 7E00 - 0000 7FFF        |    512B    Free Memory , read /write
;         |------------------------------    |
;         |    0000 8000 - 0009 FBFF         |    607KB    Free Memory , read / write( 32K - 639KB )
;         |------------------------------    |
;         |    0009 FC00 - 0009 FFFF          |**    1KB        EBDA extended BIOS data area          
;         |------------------------------    |
;         |    000A 0000 - 000A FFFF        |    64KB    Video Memory
;         |------------------------------    |
;         |    000B 0000 - 000B 7FFF         |    32KB    Mono Video Text Memory
;         |------------------------------    |
;         |    000B 8000 - 000B FFFF          |    32KB    Color Video Text Memory
;         |------------------------------    |
;         |    000C 0000 - 000C 7FFF        |    32KB    Video BIOS , read only
;         |------------------------------    |
;         |    000C 8000 - 000E FFFF         |    160KB    Adapter ROM,read only
;         |------------------------------    |
;         |    000F 0000 - 000F FFFF          |    64KB    System BIOS, read only
;         |------------------------------    |
;         |    0010 0000 - 0010 FFEF        |***64KB-16    High Memory Area,read/write  ( 1MB开始处 )
;         |------------------------------    |
;         |    0010 FFF0 -                  |            Free Extended Memory, read/write    
;         |------------------------------    |
;
;     *    空闲内存实际并非从 0000 0500处开始,BIOS数据区实际上会利用从0000 0500开始的少量字节,例如
;         00000500处保存的是打印屏幕状态,当按下打印屏幕(PrintScreen)键时,低级键盘BIOS初始化打印屏
;         幕功能,键盘BIOS触发中断5打印屏幕处理程序。正因为BIOS数据区越过了256B的界限,因此DOS实际
;         上是从0000 0522开始装载的。为保险起见,可从0000 0600开始利用空闲内存。(1.5K - 31K 29.5KB)
;
; **    有些机器上没有这段BIOS扩展数据区。
;
; ***    如果没有使用扩展高端内存区域程序(例如Emm386.exe),则从0010 0000 (1MB )开始的内存都是可用的。
;
;
;=====================================================================================
;

BITS         16            ; 生成16位代码而不是32位代码
SECTION        .TEXT         ; 代码段
ORG            0800H        ; 指定程序被装入内存的起始位置

;====================================================================
;
; NTFS启动扇区代码使用内存的情况:
;    0000 0000 - 0000 07FF         2K   IDT和BIOS数据
;    0000 0800 - 0000 2800         8K     保留给NTFS启动扇区代码,最多8K
;
;    ****:**** - 0000 7FFF        22K  堆栈区域          
;    0000:8000 - ****:****         480K 装载第二阶段程序FDOSLDR.BIN及数据的空间
;    0008 0000 - 0008 FFFF        64K  用于文件系统的缓冲区
;    0009 0000 - 0009 FFFF        64K  用于读取数据簇的缓冲区
;
;====================================================================
;
; 宏和常量定义
;
;====================================================================
?                 EQU     0        ; NASM不支持DW ?这样的语法,可以使用这样的定义
                                ; 模拟,以使代码的可读性更强
STACK_ADDR        EQU        7FD0H    ; 堆栈栈顶(注意:堆栈大小约为20K左右)
DATA_BUF_SEG    EQU        9000H    ; 用于读取根目录或文件内容的缓冲区(64K) 段地址
DATA_BUF_OFF    EQU        0000H    ; 数据缓冲区偏移
DATA_BUF_ADDR    EQU        90000H    ; 数据缓冲区线性地址
FILE_BUF_SEG    EQU        8000H    ; 文件记录缓冲区段地址
FILE_BUF_OFF    EQU        00000H    ; 文件记录缓冲区偏移
BOOT_SEC_NUM    EQU        16        ; NTFS启动扇区代码的总长度(16个扇区=8K)
BOOT_SEC_ADDR    EQU        0800H    ; NTFS启动扇区的重定位地址


; 第二阶段装载程序FDOSLDR.BIN
OSLOADER_ADDR    EQU        8000H    ; FDOSLDR.BIN放入内存中的起始位置
OSLOADER_SEG    EQU        0800H    ; 起始段地址


;====================================================================
; 用堆栈保存若干中间变量( SS = 0 BP = 7C00H )
;====================================================================
DISK_EXT_SUPPORT        EQU        1        ; BYTE    磁盘是否支持扩展BIOS    
DRIVE_NUMBER            EQU        2        ; BYTE    用于保存启动的磁盘驱动器号
BYTES_PER_FILE_RECORD    EQU        8        ; DWORD    用于保存NTFS每个文件记录的尺寸
BYTES_PER_INDEX_BLOCK    EQU        12        ; DWORD    用于保存默认的索引分配的尺寸
BYTES_PER_CLUSTER        EQU        16        ; DWORD 用于保存每簇字节数字节数


;====================================================================        
; 扩展磁盘服务所使用的地址包
;====================================================================
DAP_SECTOR_HIGH            EQU        24        ; 起始扇区号的高32位 ( 每次调用需要重置 )    DWORD
DAP_SECTOR_LOW            EQU        28        ; 起始扇区号的低32位 ( 每次调用需要重置 )    DWORD
DAP_BUFFER_SEG            EQU        30        ; 缓冲区段地址         ( 每次调用需要重置 )    WORD
DAP_BUFFER_OFF            EQU        32        ; 缓冲区偏移         ( 每次调用需要重置 )    WORD        
DAP_RESERVED2            EQU        33        ; 保留字节
DAP_READ_SECTORS        EQU        34        ; 要处理的扇区数(1 - 127 )
DAP_RESERVED1            EQU        35        ; 保留字节
DAP_PACKET_SIZE            EQU        36        ; 包的大小为16字节

;====================================================================
; NTFS系统常量
;====================================================================

; 记录类型
NTFS_RECORD_TYPE_NONE    EQU        0            ; 未知的类型
NTFS_RECORD_TYPE_FILE    EQU        0x454C4946    ; 文件记录($MFT)
NTFS_RECORD_TYPE_INDX    EQU        0x58444E49    ; 索引记录(Index Allocation)
NTFS_RECORD_TYPE_HOLE    EQU        0x454C4F48    ; 空洞记录
NTFS_RECORD_TYPE_RSTR    EQU        0x52545352    ; 重启记录($LogFile Restart Page )
NTFS_RECORD_TYPE_RCRD    EQU        0x44524352    ; 日志记录($LogFile Log Record Page)
NTFS_RECORD_TYPE_CHKD    EQU        0x424B4843    ; 检查记录($LogFile CHKDSK)
NTFS_RECORD_TYPE_BAAD    EQU        0x44414142    ; 多扇区数据写入错误(通常是由于系统断电引起)
NTFS_RECORD_TYPE_FREE    EQU        0xFFFFFFFF    ; 记录是空闲的,在使用前必须初始化

;=============================================================
; NTFS系统文件记录编号
;=============================================================
NTFS_SYSTEM_FILE_MFT        EQU        0    ; $MFT        ( Master File Table )
NTFS_SYSTEM_FILE_MFTMIRR    EQU        1    ; $MFTMirr    ( 至少前四个MFT记录的拷贝)
NTFS_SYSTEM_FILE_LOGFILE    EQU        2    ; $LogFile    ( 事务日志)    
NTFS_SYSTEM_FILE_VOLUME        EQU        3    ; $Volume    ( 卷名及卷信息以及文件系统版本 )
NTFS_SYSTEM_FILE_ATTRDEF    EQU        4    ; $AttrDef    ( 所有支持的属性定义 )
NTFS_SYSTEM_FILE_ROOT        EQU        5    ; .        ( 根目录 )
NTFS_SYSTEM_FILE_BITMAP        EQU        6    ; $Bitmap    ( 卷的数据簇分配位图 )
NTFS_SYSTEM_FILE_BOOT        EQU        7    ; $Boot    ( 卷的引导记录,指向引导扇区 )
NTFS_SYSTEM_FILE_BADCLUS    EQU        8    ; $BadClus    ( 卷的坏簇列表 )
NTFS_SYSTEM_FILE_SECURE        EQU        9    ; $Secure    ( 卷使用的安全描述符 )
NTFS_SYSTEM_FILE_UPCASE        EQU        10    ; $UpCase    ( 64K个UNICODE字符串的大写形式 )
NTFS_SYSTEM_FILE_EXTEND        EQU        11    ; $Extend    ( 包含其他系统文件的目录 $ObjId $Quota $Reparse $UsnJrnl - NTFS 3.0 )
NTFS_SYSTEM_FILE_RESERVED12    EQU        12    ; 保留
NTFS_SYSTEM_FILE_RESERVED13    EQU        13    ; 保留
NTFS_SYSTEM_FILE_RESERVED14    EQU        14    ; 保留
NTFS_SYSTEM_FILE_RESERVED15    EQU        15    ; 保留
NTFS_SYSTEM_FILE_FIRSTUSER    EQU        16    ; 第一个用户可以使用的文件记录编号

; MFT文件记录属性
NTFS_FILE_RECORD_FLAG_INUSE            EQU    0x0001    ; 正在使用
NTFS_FILE_RECORD_FLAG_DIRECTORY        EQU    0x0002    ; 是目录

;==============================================================================
;
; 属性排序规则
;
;==============================================================================
NTFS_COLLATION_BINARY                EQU    0    ; 按原始字节依次顺序比较
NTFS_COLLATION_FILE_NAME            EQU    1    ; 按UNICODE方式比较文件名(不区分大小写?)
NTFS_COLLATION_UNICODE_STRING        EQU    2    ; 按UNICODE字符串比较(区分大小写?)
NTFS_COLLATION_ULONG                EQU    16    ; 按32为无符号整数排序
NTFS_COLLATION_SID                    EQU    17    ; 按SID值排序
NTFS_COLLATION_SECURITY_HASH        EQU    18    ; 首先按哈希值排序,然后按SID排序
NTFS_COLLATION_ULONGS                EQU    19    ; 按整数序列排序(GUID?)

;==============================================================================
;
; 属性定义标志(用于属性定义结构)
;
;==============================================================================
NTFS_ATTRDEF_FLAG_INDEXABLE            EQU    0x00000002    ; 属性可以被索引
NTFS_ATTRDEF_FLAG_MULTIPLE            EQU    0x00000004    ; 属性可以出现多次
NTFS_ATTRDEF_FLAG_NOT_NULLABLE        EQU    0x00000008    ; 属性值必须至少包括一个非0字节
NTFS_ATTRDEF_FLAG_INDEXED_UNIQUE    EQU    0x00000010    ; 属性必须被索引并且必须唯一
NTFS_ATTRDEF_FLAG_NAMED_UNIQUE        EQU    0x00000020    ; 属性必须被命名并且名称必须唯一
NTFS_ATTRDEF_FLAG_RESIDENT            EQU    0x00000040    ; 属性必须是驻留的
NTFS_ATTRDEF_FLAG_LOG                EQU    0x00000080    ; 属性的修改必须记录日志,不管属性
                                                    ; 是否为驻留属性;如果没有该标志,
                                                    ; 则只记录驻留属性的修改日志
                                                    
;==============================================================================
;
; 属性类型
;
;==============================================================================
;    Type Name                    Flags    IRN        MinSize MaxSize
;------------------------------------------------------------------------------
;    0x10 $STANDARD_INFORMATION    0x40    R        0x30    0x48
;    0x20 $ATTRIBUTE_LIST        0x80    N        -        -
;    0x30 $FILE_NAME                0x42    IR        0x44    0x242
;    0x40 $VOLUME_VERSION        0x40    R        0x8        0x8
;    0x40 $OBJECT_ID                0x40    R        -        0x100
;    0x50 $SECURITY_DESCRIPTOR    0x80    N        -        -
;    0x60 $VOLUME_NAME            0x40    R        0x2        0x100
;    0x70 $VOLUME_INFORMATION    0x40    R        0xC        0xC
;    0x80 $DATA                    0x00            -        -
;    0x90 $INDEX_ROOT            0x40    R        -        -
;    0xA0 $INDEX_ALLOCATION        0x80    N        -        -
;    0xB0 $BITMAP                0x80    N        -        -
;    0xC0 $SYMBOLIC_LINK            0x80    N        -        -
;    0xC0 $REPARSE_POINT            0x80    N        -        0x4000
;    0xD0 $EA_INFORMATION        0x40    R        0x8        0x8
;    0xE0 $EA                    0x00            -        0x10000
;    0xF0 $PROPERTY_SET            -        -        -        -
;    0x100$LOGGED_UTILITY_STREAM 0x80    N        -        0x10000
;------------------------------------------------------------------------------
;
; 其中:    I - Indexable
;            N - NonNullable
;            R - Resident
;
;==============================================================================
NTFS_ATTRIBUTE_TYPE_STANDARD_INFORMATION    EQU    0x00000010    ; 标准信息
NTFS_ATTRIBUTE_TYPE_ATTRIBUTE_LIST            EQU    0x00000020    ; 属性列表
NTFS_ATTRIBUTE_TYPE_FILE_NAME                EQU    0x00000030    ; 文件名
NTFS_ATTRIBUTE_TYPE_VOLUME_VERSION            EQU    0x00000040    ; 卷版本信息(WINNT)
NTFS_ATTRIBUTE_TYPE_OBJECT_ID                EQU    0x00000040    ; 对象ID(Win2000/XP)
NTFS_ATTRIBUTE_TYPE_SECURITY_DESCRIPTOR        EQU    0x00000050    ; 安全描述符
NTFS_ATTRIBUTE_TYPE_VOLUME_NAME                EQU    0x00000060    ; 卷名称
NTFS_ATTRIBUTE_TYPE_VOLUME_INFORMATION        EQU    0x00000070    ; 卷信息
NTFS_ATTRIBUTE_TYPE_DATA                    EQU    0x00000080    ; 数据
NTFS_ATTRIBUTE_TYPE_INDEX_ROOT                EQU    0x00000090    ; 索引根目录
NTFS_ATTRIBUTE_TYPE_INDEX_ALLOCATION        EQU    0x000000A0    ; 索引分配缓冲区
NTFS_ATTRIBUTE_TYPE_BITMAP                    EQU    0x000000B0    ; 位图
NTFS_ATTRIBUTE_TYPE_SYMBOLIC_LINK            EQU    0x000000C0    ; 符号连接(WINNT)
NTFS_ATTRIBUTE_TYPE_REPARSE_POINT            EQU    0x000000C0    ; 重解析点(WIN2000/XP)
NTFS_ATTRIBUTE_TYPE_EA_INFORMATION            EQU    0x000000D0    ; 附加信息
NTFS_ATTRIBUTE_TYPE_EA                        EQU    0x000000E0    ; 附加属性
NTFS_ATTRIBUTE_TYPE_PROPERTY_SET            EQU    0x000000F0    ; 属性集(WIN2000/XP)
NTFS_ATTRIBUTE_TYPE_LOGGED_UTILITY_STREAM    EQU    0x00000100    ; 事务日志
NTFS_ATTRIBUTE_TYPE_FIRST_USER                EQU    0x00001000    ; 用户自定义属性起始值
NTFS_ATTRIBUTE_TYPE_END                        EQU    0xFFFFFFFF    ; 表明属性结束

; 属性名称最大长度
NTFS_ATTRIBUTE_NAME_LENGTH                    EQU    64            ; UNICODE字符长度

; 属性标志
NTFS_ATTRIBUTE_FLAG_COMPRESSED                EQU    0x0001        ; 压缩标志
NTFS_ATTRIBUTE_FLAG_ENCRYPTED                EQU    0x4000        ; 加密标志
NTFS_ATTRIBUTE_FLAG_SPARSE                    EQU    0x8000        ; 稀疏文件
NTFS_ATTRIBUTE_RESIDENT_FLAG_INDEXED        EQU    0x0001        ; 驻留属性被索引

;============================================================
; 文件属性
;=============================================================
NTFS_FILE_FLAG_READONLY                        EQU    0x00000001        ; 只读标志
NTFS_FILE_FLAG_HIDDEN                        EQU    0x00000002        ; 隐藏标志
NTFS_FILE_FLAG_SYSTEM                        EQU    0x00000004        ; 系统标志
NTFS_FILE_FLAG_VOLUME                        EQU    0x00000008        ; 卷标标准(NTFS不使用)
NTFS_FILE_FLAG_DIRECTORY                    EQU    0x00000010        ; 目录属性(NTFS不使用)
NTFS_FILE_FLAG_ARCHIVE                        EQU    0x00000020        ; 归档标志
NTFS_FILE_FLAG_DEVICE                        EQU    0x00000040        ; 设备
NTFS_FILE_FLAG_NORMAL                        EQU    0x00000080        ; 普通属性
NTFS_FILE_FLAG_TEMPORARY                    EQU    0x00000100        ; 临时文件
NTFS_FILE_FLAG_SPARSE_FILE                    EQU    0x00000200        ; 稀疏文件
NTFS_FILE_FLAG_REPARSE_POINT                EQU    0x00000400        ; 重解析点
NTFS_FILE_FLAG_COMPRESSED                    EQU    0x00000800        ; 压缩标志
NTFS_FILE_FLAG_OFFLINE                        EQU    0x00001000        ; 离线
NTFS_FILE_FLAG_NOT_CONTENT_INDEXED            EQU    0x00002000        ; 内容没有索引
NTFS_FILE_FLAG_ENCRYPTED                    EQU    0x00004000        ; 加密文件
NTFS_FILE_FLAG_INDEX_ROOT_PRESENT            EQU    0x10000000        ; 拷贝自MFT记录,是否目录(存在IndexRoot)
NTFS_FILE_FLAG_VIEW_INDEX_PRESENT            EQU    0x20000000        ; 拷贝自MFT记录,是否存在视图索引(ObjId索引、配额索引等)

;==============================================================================
; 文件名相关常量
;==============================================================================
; 最大允许的文件名长度
NTFS_FILE_NAME_MAXLENGTH        EQU        255

; 可能的名字空间

; 最大的命名空间,大小写敏感,除了'/0'和'/'之外的所有Unicode字符都可以作为文件名;
NTFS_FILE_NAME_POSIX            EQU        0

; 大小写不敏感,'/0', '"', '*', '/', ':', '<',>', '?', '/' ,'|'都不能用于文件名;
; 并且名字不能以句点(.)和空格结尾;
NTFS_FILE_NAME_WIN32            EQU        1
                                    
; 传统的8.3名字,大写字母
NTFS_FILE_NAME_DOS                EQU        2

; 兼顾Win32和DOS名字
NTFS_FILE_NAME_WIN32_AND_DOS    EQU        3

;==============================================================================
; 卷标志
;==============================================================================
NTFS_VOLUME_FLAG_DIRTY                    EQU        0x0001    ; 脏标志
NTFS_VOLUME_FLAG_RESIZE_LOG_FILE        EQU        0x0002    ; 重设日志文件
NTFS_VOLUME_FLAG_UPGRADE_ON_MOUNT        EQU        0x0004    ; 装配时升级
NTFS_VOLUME_FLAG_MOUNTED_ON_NT4            EQU        0x0008    ; 装配标志
NTFS_VOLUME_FLAG_DELETE_USN_UNDERWAY    EQU        0x0010    ; 删除USN
NTFS_VOLUME_FLAG_REPAIR_OBJECT_ID        EQU        0x0020    ; 修复对象ID
NTFS_VOLUME_FLAG_MODIFIED_BY_CHKDSK        EQU        0x8000    ; CHKDSK修改标志

;==============================================================================
; 索引标志
;==============================================================================

; 用于IndexRoot的属性标志
NTFS_INDEX_FLAG_LARGE_INDEX        EQU        1    ; 索引存在IndexAllocation属性

; 用于IndexAllocation的属性标志
; 用于IndexEntry的属性标志
NTFS_INDEX_FLAG_INDEX_NODE        EQU        1    ; 索引节点(存在子节点)

; 用于IndexEntry的属性标志
NTFS_INDEX_FLAG_INDEX_END        EQU        2    ; 指明是最后一个项(结束标志)

;==============================================================================
;
; MFT文件记录引用
;
;    当需要指向MFT中的一个记录时,就需要使用MFT文件记录引用,这是一个64位的数值,
;    由48位的MFT索引号和16位的序列号(用于一致性检查)组成。为了便于报告错误,我
;    们将48位的索引号看成是有符号数;而16位的序列号是一个循环计时器(跳过0),指
;    明被引用的MFT记录被使用的次数;如果该序列号数值位0,则指明不进行序列号一致性
;    检查。
;
;==============================================================================
; NTFS_MFT_REF_MASK            0x0000FFFFFFFFFFFFULL
; NTFS_MAKE_MFT_REF( I,S )    ( (((ULONGLONG)(S)) << 48 ) | (((ULONGLONG)(I)) & NTFS_MFT_REF_MASK) )
; NTFS_MFT_REF_INDEX( R )    ((ULONGLONG)((R) & NTFS_MFT_REF_MASK ))
; NTFS_MFT_REF_SEQUENCE(R)    ((USHORT)(((R) >> 48) & 0xFFFF))
; NTFS_IS_MFT_REF_ERR( R )    (((R) & 0x000080000000ULL ) ? 1:0)



;====================================================================
; 结构定义
;====================================================================

;====================================================================
; 带Fixup(TornBits)的记录头,包括FileRecord,IndexAllocation,重启日志等
;====================================================================
STRUC NTFS_RECORD_HEADER
    .RecordType            RESD     01H        ; 记录类型
    .UsaOffset            RESW    01H        ; 更新序列号数组的偏移(相对于记录开始)
    .UsaCount            RESW    01H        ; 更新序列号数组的大小
    .RecordLsn            RESQ    01H        ; 该记录的日志序列号,每次修改时更新
    ;
    ; 更新序列号数组( USA: Update Sequence Array )是一个USHORT值数组,该
    ; 值属于每个由该数组保护的更新序列记录所保护的扇区的末尾信息。
    ; 注意:该数组的第一个元素是USN( Update Sequence Number ),一个表示
    ;         记录被写入磁盘的次数的循环计数器。注意值0和-1( 0xFFFF )没有
    ;         被使用。余下的每个扇区对应的值必须与USN相等(在读取时检查),
    ;         在写入时设置。
    ;     
ENDSTRUC

;====================================================================
; MFT文件记录
;====================================================================
STRUC NTFS_FILE_RECORD

    ; 公共记录头
    .RecordType                RESD     01H        ; 记录类型
    .UsaOffset                RESW    01H        ; 更新序列号数组的偏移(相对于记录开始)
    .UsaCount                RESW    01H        ; 更新序列号数组的大小
    .RecordLsn                RESQ    01H        ; 该记录的日志序列号,每次修改时更新
    
    ; 文件记录特有属性
    .SequenceNumber            RESW    01H        ; 该记录被使用的序列号(循环计数器,跳过0)
    .LinkCount                RESW    01H        ; 硬连接数,及目录中引用该记录的次数,只在MFT基文件记录中使用;
    .AttributesOffset        RESW    01H        ;第一个属性的偏移,相对于记录开始,必须8字节对齐
    .RecordFlags            RESW    01H        ; 记录属性 NTFS_FILE_RECORD_FLAG_xxx
    .BytesInUse                RESD    01H        ; 记录头和属性的总长度(文件记录的实际长度),8字节对齐
    .BytesAllocated            RESD    01H        ; 总共分配给文件记录的长度(应该与BytesPerFileRecord一致)
    .BaseFileRecord            RESQ    01H        ; 基本文件记录中的文件索引号(对于基本文件记录,其值为0)
    .NextAttributeNumber    RESW    01H        ; 下一个属性ID,注意第一个属性ID为0;每次增1并在重用是复位为0;
    
    ; 以下两项出现在NTFS 3.1+ ( Windows XP及以上版本 _
    .Reserved                RESW    01H        ; 保留字节用于对齐
    .FileRecordNumber        RESD    01H        ; 本记录的索引号
    
    ;
    ; 当使用MFT记录时,将USA(更新序列号数组)放在这个位置,即在第一个属性开始
    ; 之前的位置。
    ;
ENDSTRUC


;====================================================================
; 属性定义结构
;====================================================================
STRUC NTFS_ATTRIBUTE_DEFINITION
    .AttributeName            RESB    NTFS_ATTRIBUTE_NAME_LENGTH    ;属性名称
    .AttribyteType            RESD    01H                            ; 属性类型
    .DisplayRule            RESD    01H                            ; 默认显示规则
    .CollationRule            RESD    01H                            ; 默认排序规则
    .AttributeFlags            RESD    01H                            ; 属性标志
    .MinimumSize            RESQ    01H                            ; 属性最小长度
    .MaximumSize            RESQ    01H                            ; 属性最大长度
ENDSTRUC

;====================================================================
; 公共属性头
;====================================================================
STRUC NTFS_ATTRIBUTE
    .AttributeType            RESD    01H    ; 属性类型
    .Length                    RESD    01H    ; 驻留部分的长度
    .Nonresident            RESB    01H    ; 指明是否非驻留属性
    .NameLength                RESB    01H    ; 属性名称长度(UNICODE)
    .NameOffset                RESW    01H    ; 名称偏移
    .AttributeFlags            RESW    01H    ; 属性标志
    .AttributeNumber        RESW    01H    ; 属性编号(在文件记录内唯一)
ENDSTRUC

;====================================================================
; 驻留属性
;====================================================================
STRUC NTFS_RESIDENT_ATTRIBUTE
    ; 公共属性头
    .AttributeType            RESD    01H    ; 属性类型
    .Length                    RESD    01H    ; 驻留部分的长度
    .Nonresident            RESB    01H    ; 指明是否非驻留属性
    .NameLength                RESB    01H    ; 属性名称长度(UNICODE)
    .NameOffset                RESW    01H    ; 名称偏移
    .AttributeFlags            RESW    01H    ; 属性标志
    .AttributeNumber        RESW    01H    ; 属性编号(在文件记录内唯一)

    ; 驻留属性
    .ValueLength            RESD    01H    ; 属性值的长度
    .ValueOffset            RESW    01H    ; 属性偏移(如果存在名字,则需要8字节对齐)
    .ResidentFlags            RESW    01H    ; 驻留属性标志
ENDSTRUC


;====================================================================
; 非驻留属性
;====================================================================
STRUC NTFS_NONRESIDENT_ATTRIBUTE

    ; 公共属性头
    .AttributeType            RESD    01H    ; 属性类型
    .Length                    RESD    01H    ; 驻留部分的长度
    .Nonresident            RESB    01H    ; 指明是否非驻留属性
    .NameLength                RESB    01H    ; 属性名称长度(UNICODE)
    .NameOffset                RESW    01H    ; 名称偏移
    .AttributeFlags            RESW    01H    ; 属性标志
    .AttributeNumber        RESW    01H    ; 属性编号(在文件记录内唯一)

    ; 非驻留属性
    .LowVcn                    RESQ    01H    ; 该属性片断的起始VCN(虚拟簇号),只有存在AttributeList时该值才不为0;
    .HighVcn                RESQ    01H    ; 该属性片断的终止VCN (-1表示长度为0)
    .RunArrayOffset            RESW    01H    ; DataRun数组相对于属性开始处的偏移(8字节对齐)
    .CompressionUnit        RESB    01H    ; 压缩单元,代表簇数的2的幂;0代表不压缩;WINNT只使用值4,代表压缩单位为16簇。
    .Reserved                RESB    05H ; 保留字节用于对齐
    .AllocatedSize            RESQ    01H    ; 分配的磁盘空间;当使用压缩时,它为压缩块的倍数,并表示逻辑大小。
    .DataSize                RESQ    01H    ; 数据的真实大小;
    .InitializedSize        RESQ    01H    ; 初始化大小,一般等于DataSize
    .CompressedSize            RESQ    01H    ; 压缩后的大小(真实磁盘空间大小)
    
ENDSTRUC


;====================================================================
; 标准属性(驻留)
;====================================================================
STRUC NTFS_STANDARD_INFORMATION
    .CreationTime            RESQ    01H    ; 创建时间,当修改文件名时更新;
    .ChangeTime                RESQ    01H    ; 修改时间,当数据属性被修改时更新。
    .LastWriteTime            RESQ    01H    ; 最后写入时间,当MFT文件记录被修改时更新。
    .LastAccessTime            RESQ    01H    ; 最后访问时间,对只读介质,不更新;可以取消该字段的更新(性能考虑);
    .FileAttributes            RESD    01H    ; 文件属性
    .MaximumVersion            RESD    01H    ; 最大允许的文件版本号,如果不启用版本则为0;
    .CurrentVersion            RESD    01H    ; 当前文件版本号,不使用版本时为0;
    .ClassId                RESD    01H    ; 类标识符索引(?)
    .QuotaId                RESD    01H    ; 文件所有者ID,用于配额控制
    .SecurityId                RESD    01H    ; 文件安全ID
    .QuotaCharge            RESQ    01H    ; 配额大小(如果不使用配额,则为0);
    .Usn                    RESQ    01H    ; 最后一次更新日志序列号。
ENDSTRUC


;==============================================================================
;
; 属性列表
;
;==============================================================================
;
; ·属性列表可以是驻留的(如果足够小),也可以是非驻留的;
; ·由一系列变长的、8字节对齐的NTFS_ATTRIBUTE_LIST_ENTRY项所组成;
; ·属性列表中对文件的每个属性都有一条相应的记录项(除了AttributeList自身),
;   属性列表是排序的:首先按属性类型排序,然后按属性名称排序,最后根据
;   属性编号排序;扩展的非驻留属性紧跟在初始的区域之后,并且按照LowVCN排序;
;    同时属性编号设为0;
; ·如果是非驻留的,那么VCN到LCN的映射数组必须能够容纳在基文件记录中;
; ·属性列表的最大尺寸为256K,这是由Windows缓存管理器决定的;
; ·只有当MFT记录在将必须驻留的属性排除之后,所有可非驻留属性不能容纳在文件
;   记录当中时才使用属性列表;例如:文件有大量的硬连接;因为磁盘碎片,VCN到
;    LCN的映射数组变得过大;有很多命名数据流;
;
STRUC NTFS_ATTRIBUTE_LIST_ENTRY
    .AttributeType            RESD    01H    ; 所存储的属性类型
    .Length                    RESW    01H    ; 该项的长度
    .NameLength                RESB    01H    ; 名字长度(UNICODE)
    .NameOffset                RESB    01H    ; 名字偏移(即使不使用名字时该值总是正确设置)
    .LowVcn                    RESQ    01H    ; 该属性值片断的最小虚拟簇号
    .FileRecordNumber        RESQ    01H    ; 主文件记录索引
    .AttributeNumber        RESW    01H    ; 如果LowVcn =0,则是属性的编号;否则,为0;
    .AttributeName            RESW    01H ; 属性名称,如果存在的话
ENDSTRUC

;====================================================================
; 文件名称属性
;====================================================================
STRUC NTFS_FILE_NAME
    .ParentDirectory        RESQ    01H    ; 父目录的MFT记录索引
    .CreationTime            RESQ    01H    ; 创建时间,当名字被修改时更新;
    .ChangeTime                RESQ    01H    ; 名字被最后修改的时间
    .LastWriteTime            RESQ    01H    ; MFT记录被最后修改的时间
    .LastAccessTime            RESQ    01H    ; MFT记录最后被访问的时间
    .AllocatedSize            RESQ    01H    ; 分配大小
    .DataSize                RESQ    01H    ; 实际大小
    .FileAttributes            RESD    01H    ; 文件属性
    .ReparsePointTag        RESD    01H    ; 重解析点标志
    .NameLength                RESB    01H    ; 名字长度(UNICODE)
    .NameType                RESB    01H    ; 名字空间类型
    .Name                    RESW    01H    ; 文件名
ENDSTRUC



;====================================================================
; 索引项头部
;====================================================================
STRUC NTFS_INDEX_HEADER
    .EntriesOffset            RESD    01H    ; 索引入口的偏移(8字节对齐)
    .IndexBlockLength        RESD    01H    ; 索引项目的大小(8字节对齐)
    .AllocatedSize            RESD    01H    ; 索引分配的大小(8字节对齐)
    .IndexFlags                RESD    01H    ; 索引标志
ENDSTRUC

;====================================================================
;
; 索引根属性(IndexRoot)中是驻留属性,由一系列NTFS_INDEX_ENTRY结构紧随其后。
; 当目录很小,在IndexRoot中能够容纳所有的索引节点时,只有IndexRoot属性存在;
; 当目录很大,在IndexRoot中容纳不下的时候,两个额外的属性IndexAllocation和
; Bitmap属性存在(描述哪个虚拟簇号在IndexAllocation中被使用)。
; 注意:文件系统根目录(.)包含自身的一个项;其他目录不包含自身;
;
;====================================================================
STRUC NTFS_INDEX_ROOT
    .AttributeType            RESD    01H    ; 索引属性类型(对于目录是$FILE_NAME,对于视图索引为0。
    .CollationRule            RESD    01H    ; 排序规则,当类型为文件名时,必须为COLLATION_FILE_NAME.                
    .BytesPerIndexBlock        RESD    01H    ; 索引块的大小
    .ClustersPerIndexBlock    RESB    01H    ; 索引块的簇数;如果小于0,则为2的幂;
    .Reserved                RESB    03H    ; 对齐字节
    
    ; IndexHeader
    .EntriesOffset            RESD    01H    ; 索引入口的偏移(8字节对齐)
    .IndexBlockLength        RESD    01H    ; 索引项目的大小(8字节对齐)
    .AllocatedSize            RESD    01H    ; 索引分配的大小(8字节对齐)
    .IndexFlags                RESD    01H    ; 索引标志
ENDSTRUC

;====================================================================
; 索引数据块 IndexAllocation(总是非驻留的)
;====================================================================
STRUC NTFS_INDEX_BLOCK
    
    ; 公共记录头
    .RecordType                RESD     01H    ; 记录类型
    .UsaOffset                RESW    01H    ; 更新序列号数组的偏移(相对于记录开始)
    .UsaCount                RESW    01H    ; 更新序列号数组的大小
    .RecordLsn                RESQ    01H    ; 该记录的日志序列号,每次修改时更新

    ; 索引块字段
    .IndexBlockVcn            RESQ    01H    ; 索引块的虚拟簇号
    
    ; IndexHeader
    .EntriesOffset            RESD    01H    ; 索引入口的偏移(8字节对齐)
    .IndexBlockLength        RESD    01H    ; 索引项目的大小(8字节对齐)
    .AllocatedSize            RESD    01H    ; 索引分配的大小(8字节对齐)
    .IndexFlags                RESD    01H    ; 索引标志
    
    ; 注意:Usa置于此处;
    ; 接下来就是具体的索引项
    ; 如果有子节点,则最后的8字节数据为子节点VCN
ENDSTRUC


;=============================================================
; 索引项头(忽略了非文件名索引,如$R)
;=============================================================
STRUC NTFS_INDEX_ENTRY
    
    .IndexedFile            RESQ    01H ; 目录索引所引用的文件在MFT中的记录号
    .Length                    RESW    01H ; 该索引项的长度(8字节对齐)
    .KeyLength                 RESW    01H    ; 键值长度(注意:非8字节对齐)
    .EntryFlags                RESW    01H    ; 索引项标志,指明是否有子节点,以及是否结束项
    .Alignment                RESW    01H    ; 对齐字节
    
    ; 接下来就是索引键及附加数据
    ; 这里只列出了NTFS_FILE_NAME
    .ParentDirectory        RESQ    01H    ; 父目录的MFT记录索引
    .CreationTime            RESQ    01H    ; 创建时间,当名字被修改时更新;
    .ChangeTime                RESQ    01H    ; 名字被最后修改的时间
    .LastWriteTime            RESQ    01H    ; MFT记录被最后修改的时间
    .LastAccessTime            RESQ    01H    ; MFT记录最后被访问的时间
    .AllocatedSize            RESQ    01H    ; 分配大小
    .DataSize                RESQ    01H    ; 实际大小
    .FileAttributes            RESD    01H    ; 文件属性
    .ReparsePointTag        RESD    01H    ; 重解析点标志
    .NameLength                RESB    01H    ; 名字长度(UNICODE)
    .NameType                RESB    01H    ; 名字空间类型
    .Name                    RESW    01H    ; 文件名
ENDSTRUC

;====================================================================
;
; 启动扇区(最多16个扇区)
;
;====================================================================
_ENTRY_POINT:
    ; 3字节的跳转指令
    JMP    SHORT _BOOT_CODE    ; 跳转到真正的引导代码
    NOP                      ; 空指令以保证字节数为3

; 8字节的OEMName
OEMName                    DB    "NTFS    "
    
;====================================================================
;
; BPB( BIOS Parameter Block )
;
;====================================================================
BytesPerSector            DW    ?    ; 每个扇区的字节数 (512 1024 2048 4096)
SectorsPerCluster        DB    ?    ; 每个簇的扇区数 ( 1 2 4 8 16 32 64 128 )
                                ; 两者相乘不能超过32K(簇最大大小)
ReservedSectors            DW    ?    ; 从卷的第一个扇区开始的保留扇区数目;
                                ; 该值不能为0,对于FAT12/FAT16,该值通常为1;
                                ; 对于FAT32,典型值为32;
NumberOfFATs            DB    ?    ; 卷上FAT数据结构的数目,该值通常应为2
                                ; [NTFS不使用NumberOfFATs字段,必须为0]
RootEntries                DW    ?    ; 对于FAT12/FAT16,该值表示32字节目录项的数目;
                                ; 对于FAT32,该值必须为0;[NTFS不使用]
NumberOfSectors16        DW    ?    ; 该卷上的扇区总数,该字段可以为0,如果该字段
                                ; 为0,则NumberOfSectors32不能为0;对于FAT32,
                                ; 该字段必须为0 [FAT32/NTFS不使用该字段]
MediaDescriptor            DB    ?    ; 介质类型
SectorsPerFAT16            DW    ?    ; 该字段标识一个FAT结构占有的扇区数(FAT12/FAT16),
                                ; 对于FAT32卷,该字段必须为0;[FAT32/NTFS不使用该字段]
SectorsPerTrack            DW    ?    ; 用于INT 0x13中断的每个磁道的扇区数
HeadsPerCylinder        DW    ?    ; 用于INT 0x13中断的每个柱面的磁头数
HiddenSectors            DD    ?    ; 包含该FAT卷的分区之前的隐藏扇区数
NumberOfSectors32        DD    ?    ; 该字段包含该卷上的所有扇区数目,对于FAT32,该字段
                                ; 不为0;FAT12/FAT16可根据实际大小是否超过65536个扇
                                ; 区数决定是否采用该字段; [NTFS不使用该字段]
                                
;====================================================================
;
; EBPB ( Extended BIOS Parameter Block )
;
;====================================================================
UnknownFlags            DD    ?    ; 未知用途标志字段,总是为0x00800080或类似的值
NumberOfSectors64Low    DD    ?    ; 扇区总数低32位
NumberOfSectors64High    DD    ?    ; 扇区总数高32位
MftStartLcnLow            DD    ?    ; 主文件表(MFT: Master File Table)的逻辑簇号
MftStartLcnHigh            DD    ?    ; Logical Cluster Number for the file $MFT
Mft2StartLcnLow            DD    ?    ; 主文件表镜像(备份)的逻辑簇号
Mft2StartLcnHigh        DD    ?    ; Logical Cluster Number for the file $MFTMirr
ClustersPerFRS            DB    ?    ; 每文件记录段的簇数(必须为2的幂,负数表示位移量)
Reserved0    TIMES    3    DB    ?    ; 保留对齐字节
ClustersPerIAB            DB    ?    ; 缺省的每索引分配缓冲的簇数(必须为2的幂,负数表示位移量)
Reserved1    TIMES    3    DB    ?    ; Default Clusters Per Index Allocation Buffer
SerialNumberLow            DD    ?    ; 卷序列号( Volume Serial Number )
SerialNumberHigh        DD    ?    ; 卷序列号( Volume Serial Number )
CheckSum                DD    ?    ; 校验和


;====================================================================
;
; 真正的启动代码从这开始
; 其功能是搜索磁盘的根目录,查找FDOSLDR.BIN文件,将其读入内存并运行。
;
;====================================================================
_BOOT_CODE:

    ; 初始化相关寄存器及标志位
    CLI                        ; 先关掉中断
    CLD                        ; 方向为向前递增
    XOR        AX,AX            ; AX = 0
    MOV     DS,AX            ; 设置数据段寄存器    DS:SI
    MOV        ES,AX            ; 设置附加段寄存器    ES:DI
    MOV        SS,AX            ; 设置堆栈段寄存器
    MOV        BP,8000H        ; 设置基址寄存器
    MOV        SP,STACK_ADDR    ; 设置堆栈栈顶
    STI                        ; 允许中断

    ;====================================================================
    ; 保存启动的磁盘编号
    ;====================================================================
    MOV     [BP-DRIVE_NUMBER],DL; 该值由BIOS设置,如果是从USB启动,该值为0x80
                                ; 即为第一个硬盘的编号,该值将用于后续的磁盘
                                ; 读取调用
                                
                                    
    ;====================================================================        
    ; 检查是否支持磁盘中断INT 13H的扩展
    ;====================================================================
    MOV        BYTE [BP - DISK_EXT_SUPPORT],00H        ; 00H表示不支持磁盘扩展
    MOV        AH,41H
    MOV     BX,055AAH
    INT        13H
    JC        .LoadSectors                            ; 如果失败,进位标志为1或者BX值不对( AA55 or 55AA )
    
    ; 设置磁盘支持扩展中断标志
    MOV        BYTE [BP - DISK_EXT_SUPPORT],01H        ; 01H表示支持磁盘扩展

; 不支持磁盘扩展
.LoadSectors:
                                        
    ;====================================================================
    ;
    ; 初始化DiskAddressPacket
    ; 使用时只需要修改字段:DATA_BUFFER_OFF DATA_BUFFER_SEG
    ;                         DAP_SECTOR_LOW     DAP_SECTOR_HIGH
    ;
    ;====================================================================
    MOV     DWORD [BP - DAP_SECTOR_HIGH ],00H                ; 起始扇区号
    MOV     DWORD [BP - DAP_SECTOR_LOW  ],00H
    MOV     WORD  [BP - DAP_BUFFER_SEG  ],00H                ; 缓冲区段地址
    MOV     BYTE  [BP - DAP_RESERVED1   ],00H
    MOV     BYTE  [BP - DAP_RESERVED2   ],00H
    MOV     BYTE  [BP - DAP_PACKET_SIZE ],10H
    MOV     WORD  [BP - DAP_BUFFER_OFF  ],BOOT_SEC_ADDR        ; 缓冲区偏移
    MOV     BYTE  [BP - DAP_READ_SECTORS],BOOT_SEC_NUM        ; 最多16个扇区
    
    
    ; 装载第二个启动扇区
    MOV        EAX , DWORD[HiddenSectors]
    MOV        DWORD [BP - DAP_SECTOR_LOW  ],EAX
    CALL    ReadSectors
    JMP        0:.RealStart                                    

; 真正开始执行代码的地方            
.RealStart:

    ; 计算常用参数
    ; 每簇字节数
    XOR        EAX,EAX
    MOV        AX, WORD[BytesPerSector]
    XOR        ECX,ECX
    MOV        CL, BYTE[SectorsPerCluster]
    MUL        ECX
    MOV        DWORD[BP - BYTES_PER_CLUSTER],EAX
    
;%IFDEF DEBUG        
;    CALL    PrintDword
;    MOV        AL,20H
;    CALL    PrintChar
;%ENDIF
    
    
    ; 每文件记录的字节数
    XOR        EAX,EAX
    MOV        AL,BYTE[ClustersPerFRS]
    CALL    GetRealSizeFromClusters ; EAX = 字节数
    MOV        DWORD[BP - BYTES_PER_FILE_RECORD],EAX
    
;%IFDEF DEBUG        
;    CALL    PrintDword
;    MOV        AL,20H
;    CALL    PrintChar
;%ENDIF
    
    ; 计算每索引分配块大小
    XOR        EAX,EAX
    MOV        AL,BYTE[ClustersPerIAB]
    CALL    GetRealSizeFromClusters ; EAX = 字节数
    MOV        DWORD[BP - BYTES_PER_INDEX_BLOCK],EAX
    
;%IFDEF DEBUG        
;    CALL    PrintDword
;    MOV        AL,20H
;    CALL    PrintChar
;%ENDIF    
    
    ; 下面开始查找根目录并且装载FDOSLDR.BIN
    JMP        _SEARCH_LOADER

;====================================================================
;    错误处理
;====================================================================
_MISSING_LOADER:                    ; 显示没有装载程序
    MOV        SI,MessageMissLoader
    CALL    ShowMessage
    JMP        _REBOOT

_DISK_ERROR:                        ; 显示磁盘错误信息
    MOV        SI,MessageDiskError
    CALL    ShowMessage

_REBOOT:                            ; 重启
    MOV        SI,MessageRestart
    CALL    ShowMessage
    
    ; 调用键盘中断,等待用户按键
    MOV        AH,00H
    INT        16H
    
    ; 重启计算机
    INT        19H    

    ; 死循环
    JMP     $
    
    
;====================================================================
;
; 子过程
;
;====================================================================

;====================================================================
;
; 计算文件记录或索引分配块的字节数
; 输入:AL  = 用字节表示的簇的倍数
; 输出:EAX = 真实的字节数
;
;====================================================================
GetRealSizeFromClusters:
    PUSH    ECX
    PUSH     EDX

    ; 首先保存相对簇数
    XOR        ECX,ECX
    MOV        CL,AL
    CMP        CL,00H
    JLE        .ShiftBits    ; 有符号比较
    
    ; ClusersNumber > 0
    MOV        EAX,DWORD[BP - BYTES_PER_CLUSTER]
    MUL        ECX             ; EDX:EAX = 字节数
    JMP        .CalculateOK

; 负数,为相对于1的唯一
.ShiftBits:
    NEG        CL
    MOV        EAX,1
    SHL        EAX,CL

.CalculateOK:    
    POP     EDX
    POP        ECX
    RET

;====================================================================
;
; 读取一个或多个磁盘扇区
;    输入: 已经设置了DAP中相应的字段
;
;====================================================================
ReadSectors:

    PUSHA        ; 保存寄存器
    
    ; 检查是否使用扩展方式
    CMP        BYTE [BP - DISK_EXT_SUPPORT],00H
    JZ        .NoDiskExtension
    
;====================================================================
; INT 13H     AH = 42H 扩展磁盘调用
;====================================================================
    MOV        AH,42H                                    ; 功能号    
    LEA     SI ,[BP - DAP_PACKET_SIZE]                ; 地址包地址
    MOV     DL ,[BP - DRIVE_NUMBER]                    ; 驱动器号
    INT     13H
    JC         _DISK_ERROR                                ; 读取失败
    JMP        _READ_SECTOR_OK                            ; 读取成功

;====================================================================
;    
; INT 13H    
;        AH = 2                                柱面号:0 - 1023
;        AL = 要读取的扇区数                    磁头号:0 - 255       
;        CH = 柱面号低8位                    扇区号:1 - 63  
;        CL = 柱面号高2位 : 6位扇区号        
;        DH = 磁头号
;        DL = 驱动器号 ES:BX = 缓冲区地址
;
;    LBA = ( (cylinder * HeadsPerCylinder + heads ) * SectorsPerTrack ) + sector - 1
;
;    Sector    = LBA % SectorsPerTrack +1
;    Head    = (  LBA / SectorsPerTrack    ) % HeadsPerCylinder
;   Cylinder= (  LBA / SectorsPerTrack    ) / HeadsPerCylinder
;
;====================================================================    
.NoDiskExtension:
        
    ;====================================================================        
    ; 首先需要将扇区号转换为CHS地址
    ;====================================================================
    
    ; 首先计算扇区号
    MOV        AX,WORD [ BP - DAP_SECTOR_LOW   ]
    MOV        DX,WORD    [ BP - DAP_SECTOR_LOW+2 ]
    DIV        WORD [SectorsPerTrack ] ; AX = LBA / SectorsPerTrack DX = LDA % SectorsPerTrack
    MOV        CX,DX
    INC        CX                        ; CL = Sector
    AND        CL,3FH                    ; 1-63

    ; 再计算磁头号和柱面号
    XOR        DX,DX                    ; DX:AX = LBA / SectorsPerTrack
    DIV        WORD [HeadsPerCylinder] ; DX = ( LBA/SectorsPerTrack ) %  HeadsPerCylinder = Head
                                    ; AX = ( LBA/SectorsPerTrack ) /  HeadsPerCylinder = Cylinder
    MOV        CH,AL                    ; 柱面号低8位
    SHL        AH,6
    OR        CL,AH                    ; CL = 柱面号高2位:6位扇区号
    MOV        DH,DL                    ; DL = 磁头号
                                    
    ; 准备读取磁盘                                        
    MOV        AX,WORD[ BP - DAP_BUFFER_SEG ] ; ES:BX = 缓冲区地址
    MOV        ES,AX
    MOV        BX,WORD[ BP - DAP_BUFFER_OFF ]
    MOV        AH,02H                            ; 功能号    
    MOV        AL,BYTE[ BP - DAP_READ_SECTORS] ; 要读取的扇区数
    MOV     DL ,[BP - DRIVE_NUMBER]            ; 驱动器号
    INT     13H
    JC         _DISK_ERROR                ; 读取失败

_READ_SECTOR_OK:
    POPA                            ; 恢复寄存器    
    RET


;====================================================================
;
; 显示一个字符串
;    输入:
;            DS:SI     = 字符串的起始地址(以NULL结束)
;            
;====================================================================
ShowMessage:
    LODSB                ; AL = DS:[SI] SI = SI+1
    OR        AL,AL       ; 检测是否遇到NULL字符串
    JZ        _SHOW_END
    MOV        AH,0EH
    MOV        BX,07H
    INT        10H
    JMP        ShowMessage

_SHOW_END:
    RET    

    
;====================================================================
; 数据区
;====================================================================
; 第二阶段启动程序 FDOSLDR.BIN
LoaderName                    DB     46H,00H        ; F
                            DB    44H,00H        ; D
                            DB    4FH,00H        ; O
                            DB    53H,00H        ; S
                            DB    4CH,00H        ; L
                            DB    44H,00H        ; D
                            DB    52H,00H        ; R
                            DB    2EH,00H        ; .
                            DB    42H,00H        ; B
                            DB    49H,00H        ; I
                            DB    4EH,00H        ; N
MessageMissLoader            DB    "NO FDOSLDR.BIN.",0DH,0AH,00H            ; 没有找到装载程序
MessageDiskError            DB    "Disk Error.",0DH,0AH,00                ; 磁盘错误消息
MessageRestart                DB    "Press any key to restart." ,0DH,0AH,00    ; 提示重启消息


;====================================================================
; 扇区最后的标记字节(NASM不支持重复ORG)
;====================================================================
Padding    TIMES 510-($-$$)    DB     00H
SectorSignature                DW     0AA55H     


;====================================================================
; 第二个扇区的代码(该代码位于分区的第二个扇区)
;====================================================================
; 下面开始查找根目录并且装载FDOSLDR.BIN
_SEARCH_LOADER:

    ;
    ; 首先装载MFT,然后获得根目录的文件记录
    ; 根据MftStartLcn读取一定的数据,必须确保根目录的文件记录也被读进来了
    ;
    
    ; 计算需要读取的扇区数
    MOV        EAX,NTFS_SYSTEM_FILE_ROOT
    INC        EAX
    MOV        ECX,DWORD[BP - BYTES_PER_FILE_RECORD]
    MUL        ECX        ; EDX:EAX = 6*BytePerFileRecord
    XOR        ECX,ECX
    MOV        CX,WORD[BytesPerSector]
    ADD        AX,CX
    DEC        EAX
    DIV        ECX        ; EAX = 需要读取的簇数
    MOV        EBX,EAX    ; EBX = 需要读取的簇数
    
    ; 读取MFT记录
    MOV        EAX,DWORD[MftStartLcnLow]
    MOV        EDX,DWORD[MftStartLcnHigh]
    XOR        ECX,ECX
    MOV        CL, BYTE[SectorsPerCluster]
    MUL        ECX    ; EDX:EAX = 起始扇区号
    
    ; 构造读取数据包
    MOV        DWORD[ BP - DAP_SECTOR_HIGH ], EDX
    MOV        DWORD[ BP - DAP_SECTOR_LOW     ], EAX
    MOV        WORD [ BP - DAP_BUFFER_SEG  ], DATA_BUF_SEG
    MOV        WORD [ BP - DAP_BUFFER_OFF  ], DATA_BUF_OFF
    MOV        BYTE [ BP - DAP_READ_SECTORS], BL
    CALL    ReadSectors

    ; 将根目录记录拷贝到80000处
    MOV        AX,FILE_BUF_SEG
    MOV        ES,AX
    MOV        EDI,FILE_BUF_OFF
    
    ; 计算记录偏移
    MOV        EAX,NTFS_SYSTEM_FILE_ROOT
    MOV        ECX,DWORD[BP - BYTES_PER_FILE_RECORD]
    MUL        ECX        ; EDX:EAX = 6*BytePerFileRecord
    ADD        EAX,DATA_BUF_OFF
    MOV        ESI,EAX
    MOV        AX,DATA_BUF_SEG
    MOV        DS,AX
    REP        MOVSB
    
    ;恢复目标地址
    MOV        EDI,FILE_BUF_OFF

    ; 恢复数据段寄存器
    XOR        AX,AX
    MOV        DS,AX    
    
    ; 调整每扇区末尾字节
    ; ES:EDI = FileRecord
    CALL    FixupRecord
    
    ; 现在开始查找IndexRoot和IndexAllocation属性
    MOV        EAX,NTFS_ATTRIBUTE_TYPE_INDEX_ROOT
    CALL    FindAttribute ; EAX = Attribute相对于记录开始位置的偏移
    CMP        EAX,0
    JZ        _MISSING_LOADER

    ; 首先在IndexRoot中查找是否有FDOSLDR.BIN文件的存在
    ; ES:EDI = FileRecord  EAX = IndexRoot属性的偏移
    CALL     SearchIndexRoot
    
    ;检查是否找到了
    ; EDX:EAX = FileIndex
    AND        EDX,0000FFFFH
    CMP        EDX,0
    JNZ        .LoadLoader
    CMP        EAX,0
    JNZ        .LoadLoader
    
    ; 没有找到 EDX == 0 && EAX == 0 => NOT FOUND
    JMP        _MISSING_LOADER
        
;====================================================================
; 找到了FDOSLDR.BIN文件的FileIndex
;====================================================================        
.LoadLoader:    

    ;====================================================================        
    ; 首先读取该文件的文件记录
    ;====================================================================        

    ; 我们现在已经得到了FDOSLDR.BIN文件的文件索引号
    ; 我们假设MFT的连续性没有问题,即不用去查DataRunList,直接根据MFTStartLcn来计算偏移
    ; EDX:EAX = FileIndex
    ;
    MOV        ECX,DWORD[EBP-BYTES_PER_FILE_RECORD]
    MOV        EBX,DWORD[EBP-BYTES_PER_CLUSTER]
    MUL     ECX            ; EDX:EAX = FileIndex*BytesPerFileRecord
    DIV     EBX            ; EDX = BytesOffsetInCluster     EAX = VCN
    MOV        EBX,EDX        ; EBX = BytesOffsetInCluster     EAX = VCN
    XOR        EDX,EDX

    ; 假设FDOSLDR.BIN位于MFT的第一个RUN上
    ; 注意:这个假设可能不成立
    ADD        EAX,DWORD[MftStartLcnLow]
    ADC        EDX,DWORD[MftStartLcnHigh]
    
    ; EDX : EAX = StartLcn
    XOR        ECX,ECX
    MOV        CL,BYTE[SectorsPerCluster]
    MUL        ECX    ; EDX:EAX = 起始扇区号
    
    ; 构造读取数据包
    MOV        DWORD[ BP - DAP_SECTOR_HIGH ], EDX
    MOV        DWORD[ BP - DAP_SECTOR_LOW     ], EAX
    MOV        WORD [ BP - DAP_BUFFER_SEG  ], DATA_BUF_SEG
    MOV        WORD [ BP - DAP_BUFFER_OFF  ], DATA_BUF_OFF
    
    ; 计算需要读取的扇区数
    XOR        ECX,ECX
    XOR        EDX,EDX
    MOV        EAX,DWORD[EBP-BYTES_PER_FILE_RECORD]
    MOV        CX,WORD[BytesPerSector]
    DIV        ECX    
    MOV        BYTE [ BP - DAP_READ_SECTORS], AL
    CALL    ReadSectors

    ; 将文件记录拷贝到80000处
    MOV        AX,FILE_BUF_SEG
    MOV        ES,AX
    MOV        EDI,FILE_BUF_OFF
    MOV        EAX,DATA_BUF_OFF
    MOV        ESI,EAX
    MOV        AX,DATA_BUF_SEG
    MOV        DS,AX
    REP        MOVSB
        
    ;恢复目标地址
    MOV        EDI,FILE_BUF_OFF

    ; 恢复数据段寄存器
    XOR        AX,AX
    MOV        DS,AX
    
    ; 调整每扇区末尾字节
    ; ES:EDI = FileRecord
    CALL    FixupRecord
    
    ;====================================================================        
    ; 然后查找该文件的$DATA数据流
    ;====================================================================        
    ; ES:EDI = FileRecord
        
    ; 现在开始查找Data属性
    MOV        EAX,NTFS_ATTRIBUTE_TYPE_DATA
    CALL    FindAttribute         ; EAX = Attribute相对于记录开始位置的偏移
    CMP        EAX,0            
    JZ        _MISSING_LOADER
    
    ; EAX = Attribute相对于记录开始位置的偏移
    ; 因为FDOSLDR.BIN肯定大于1KB,所以其数据属性必定是非驻留的
    ; 我们假定FDOSLDR.BIN不会形成文件碎片,只有一个RUN
    ; ESI = 文件的尺寸
    MOV        ESI,DWORD[ES:EDI+EAX+NTFS_NONRESIDENT_ATTRIBUTE.DataSize]
    XOR        ECX,ECX
    MOV        CX,WORD[ES:EDI+EAX+NTFS_NONRESIDENT_ATTRIBUTE.RunArrayOffset]
    ADD        EAX,ECX  ; EAX = DataRun
        
    ; 对偏移进行解码,获得目标LCN
    ; 输入: ES:EDI = FileRecord EBX = DataRun EDX:EAX = VCN
    ; 输出: EDX:EAX= LCN 0代表错误
    MOV        EBX,EAX    ; EBX = DataRun
    XOR        EAX,EAX
    XOR        EDX,EDX    ; StartVcn = 0
    CALL    DecodeDataRunList
    
    ; 假设32位(不管高32位)
    CMP        EAX,00H
    JZ        _DISK_ERROR

    ;====================================================================        
    ; 现在得到了文件的起始逻辑簇号 EDX:EAX = StartLcn,可以开始读取文件了
    ;====================================================================        
    ; 假设只有一个DATARUN(即不存在碎片)
    ;找到了Lcn 读取该文件
    ; 构造读取数据包
    XOR        ECX,ECX
    MOV        CL, BYTE[SectorsPerCluster]
    MUL        ECX    ; EDX:EAX = 起始扇区号
    MOV        DWORD[ BP - DAP_SECTOR_HIGH ], EDX
    MOV        DWORD[ BP - DAP_SECTOR_LOW     ], EAX

    ; 计算需要读取的扇区数
    XOR        EDX,EDX
    MOV        EAX,ESI        ; ESI = 文件尺寸
    XOR        ECX,ECX
    MOV        CX,WORD[BytesPerSector]
    ADD        EAX,ECX
    DEC        EAX            ; EAX = 文件尺寸+BytesPerSector-1
    DIV        ECX            ; EAX = 需要读取的扇区数
    MOV        ECX,EAX        ; ECX = 需要读取的总扇区数
    

    ; 起始缓冲区地址        
    MOV        WORD [ BP - DAP_BUFFER_SEG  ], OSLOADER_SEG
    MOV        WORD [ BP - DAP_BUFFER_OFF  ], 0
    MOV        BYTE [ BP - DAP_READ_SECTORS], 1    ; 每次读取一个扇区
    
.ReadNextSector:

    ; 读取该扇区
    CALL    ReadSectors
    
    ; 更新扇区号
    INC        DWORD[ BP - DAP_SECTOR_LOW     ]
    
    ; 更新缓冲区地址
    XOR        EAX,EAX
    MOV        AX,WORD[BytesPerSector]
    SHR        AX,4        ; AX = BytesPerSector/16
    ADD        WORD [ BP - DAP_BUFFER_SEG  ],AX
    
    ;检查是否还有扇区需要读取
    DEC        ECX
    JNZ        .ReadNextSector
    
    ; 读取完毕,可以跳转到FDOSLDR.BIN执行
_RUN_LOADER:    

%IFDEF DEBG
    MOV        ESI,OSLOADER_ADDR
    MOV        EAX,DWORD[DS:ESI]
    CALL     PrintDword
    MOV        AL,20H
    CALL    PrintChar
%ENDIF    

    ; 运行FDOSLDR.BIN
    MOV        DL,BYTE[EBP-DRIVE_NUMBER]            ; BYTE    用于保存启动的磁盘驱动器号    
    JMP        00:OSLOADER_ADDR



;====================================================================
; 调整每扇区末尾字节
;
; 输入:ES:EDI = FileRecord
; 输出:调整了相应字节
;====================================================================
FixupRecord:
    PUSHA
    
    XOR     EAX,EAX
    XOR     EBX,EBX
    XOR     ECX,ECX
    XOR        EDX,EDX
    
    ; 首先得到USN和UsaCount
    MOV        AX,WORD [ES:EDI+NTFS_RECORD_HEADER.UsaOffset] ; AX = USN
    MOV        CX,WORD [ES:EDI+NTFS_RECORD_HEADER.UsaCount ]
    DEC         CX    ; CX = UsaCount - 1
    
    ; 更新数据块
    MOV     BX,WORD[BytesPerSector]
    DEC     BX
    DEC     BX  ; BX = BytesPerSecor - 2
    CMP        CX,0
    JLE        .FixupEnd
    
    ; USA
    MOV        DX,NTFS_RECORD_HEADER.UsaOffset
    INC        DX,
    INC        DX        ; SKIP USN
    
; 更新每个扇区    
.LoopAgain:
    MOV        AX, WORD [ES:EDI+EDX]
    MOV        WORD [ES:EDI+EBX],AX
    INC        DX,
    INC        DX
    ADD        BX,WORD[BytesPerSector]
    LOOP    .LoopAgain
    
.FixupEnd:    
    POPA
    RET
    
;====================================================================
; 查找某个属性
;
; 输入:ES:EDI = FileRecord  EAX = 属性类型
; 输出:EAX    = 属性的偏移位置,如果为0则表示没有找到
; 限制:目前没有实现AttributeList,假定根目录不会有很多文件或子目录。
;====================================================================
FindAttribute:
    PUSH EBX
    PUSH ECX
    PUSH EDX
    
    XOR        EBX,EBX
    XOR        ECX,ECX
    XOR        EDX,EDX
    MOV        BX, WORD[ ES:EDI+EBX+NTFS_FILE_RECORD.AttributesOffset]

; 检查下一个属性
.CheckNextAttribute:    
    
    ; 检查是否为最后一个属性
    MOV        ECX,DWORD[ ES:EDI+EBX+NTFS_ATTRIBUTE.AttributeType]
    CMP        ECX,NTFS_ATTRIBUTE_TYPE_END
    JZ        .NotFound ; 已经没有可找的了
    
    ; 检查是否我们需要的类型
    CMP        ECX,EAX
    JZ        .Found      ; 找到了

    ; 检查下一个属性
    ADD        EBX,DWORD[ ES:EDI+EBX+NTFS_ATTRIBUTE.Length]

    ; 检查是否超过范围
    CMP        EBX,DWORD[BP - BYTES_PER_FILE_RECORD]
    JB        .CheckNextAttribute

.NotFound:    
    MOV        EBX,0
.Found:    
    MOV        EAX,EBX
    
    ; 恢复寄存器
    POP        EDX
    POP        ECX
    POP        EBX
    RET
    
;====================================================================
; 在IndexRoot中查找FDOSLDR.BIN或其应该位于的子节点
;
; 输入:ES:EDI = FileRecord  EAX = IndexRoot属性的偏移
; 输出:
;
;====================================================================
SearchIndexRoot:
    PUSH EBX
    PUSH ECX
    SUB     ESP,16            ; 保存子节点的VCN
    ; 00H EAX
    ; 04H EDX
    ; 08H BytesPerIndexBlock
                
    ; IndexRoot总是驻留属性
    MOV        EBX,EAX
    XOR         ECX,ECX
    MOV        CX, WORD[ES:EDI+EBX+NTFS_RESIDENT_ATTRIBUTE.ValueOffset]
    ADD        EBX,ECX ; EBX = INDEX_ROOT
    MOV         EDX,DWORD[ES:EDI+EBX+NTFS_INDEX_ROOT.BytesPerIndexBlock]
    MOV        DWORD[ESP+08H],EDX; BytesPerIndexBlock
    MOV        ECX,DWORD[ES:EDI+EBX+NTFS_INDEX_ROOT.IndexFlags]
    AND        ECX,NTFS_INDEX_FLAG_LARGE_INDEX    ; 是否有IndexAllocation
    JZ         NEAR .SmallIndexRoot

;====================================================================    
; 存在IndexAllocation的目录(大目录)
;====================================================================
.LargeIndexRoot:
    ; EDX = BytesPerIndexBlock
    ; EBX = INDEX_ROOT    
    ; ES:EDI = FileRecord
    
    ;====================================================================
    ; 首先收缩IndexRoot,得到下一级的子节点    
    ;====================================================================
    ; EBX = IndexRoot
    MOV     EAX,DWORD[ES:EDI+EBX+NTFS_INDEX_ROOT.EntriesOffset] ; EAX = IndexHeader.EntriesOffset
    ADD         EAX,10H        ; 10H = offsetof( NTFS_INDEX_ROOT,IndexHeader)
    ADD     EBX,EAX        ; EBX = First IndexEntry
    
; 检查下一个IndexEntry
.LargeCheckNextEntry:    
    
%IFDEF DEBUG    
;    MOV        AL,'A'
;    CALL    PrintChar
;    MOV        EAX,DWORD[ES:EDI+EBX+NTFS_INDEX_ENTRY.IndexedFile]
;    CALL    PrintDword
;    MOV        AL,20H
;    CALL    PrintChar
%ENDIF    
    
    ; 检查EntryFlags是否为最后一个项
    MOV        CX,WORD[ ES:EDI+EBX+NTFS_INDEX_ENTRY.EntryFlags]
    AND     CX,NTFS_INDEX_FLAG_INDEX_END
    JNZ        NEAR .LargeSubNode
    
    ;检查该名字是否与LoaderName相同
    XOR        ECX,ECX
.LargeCompareName:
        
; DEBUG    
;    MOV        AL,'P'
;    CALL    PrintChar
        
    ; 再比较名称是否相同
    MOV        EDX,EDI    ; SAVE EDI
    MOV        CL,BYTE[ ES:EDI+EBX+NTFS_INDEX_ENTRY.NameLength]
    LEA         EDI,[ ES:EDI+EBX+NTFS_INDEX_ENTRY.Name]
    
    ; 需要将名称变为大写
    ; 输入:ES:EDI = 字符串 CL = 字符串长度
    CALL    ToUpperCase
    CMP        CL,11
    JB        .LargeCompare
    MOV        CL,11            ; CL = Min( NameLength,11)

.LargeCompare:    
    
    ;比较字符串
    MOV        ESI,LoaderName
    ; EDI = 待比较字符
    
.LargeCompareNextChar:    

;    MOV        AL,' '
;    CALL    PrintChar
;    MOV        AX,[ESI]
;    CALL    PrintWord
;    MOV        AL,'U'
;    CALL    PrintChar
;    MOV        AX,[ES:EDI]
;    CALL    PrintWord
;    MOV        AL,' '
;    CALL    PrintChar

    CMPSW    ; ESI - EDI
    JNZ        .NotEqual
    
    ; 相等则继续比较
    DEC        CL
    JNZ        .LargeCompareNextChar
    
    ; CX = 0
    XCHG    EDI,EDX            ; 恢复EDI
    JMP        .LargeCheckNameLength    
    
.NotEqual:
    XCHG    EDI,EDX
    JB        .LargeSubNode    ; FDOSLDR.BIN < Entry.Name
    JA        .LargeNextEntry

    
; 部分名字相同,比较相同部分长度    
.LargeCheckNameLength:    
    CMP        BYTE[ ES:EDI+EBX+NTFS_INDEX_ENTRY.NameLength],11
    JZ        NEAR .FoundLoader ; 找到了
    JA        .LargeSubNode; 该项的名字大于LoaderName,查找其子节点

.LargeNextEntry:    

    ; 继续检查下一个项
    XOR        ECX,ECX
    MOV         CX,WORD[ES:EDI+EBX+NTFS_INDEX_ENTRY.Length]
    ADD        EBX,ECX    ; EBX = 下一个IndexEntry
    JMP        .LargeCheckNextEntry    

.LargeSubNode:
; DEBUG    
;    MOV        AL,'S'
;    CALL    PrintChar
    
    ; EBX = 当前INDEX_ENTRY
    XOR        ECX,ECX
    MOV        CX,WORD[ES:EDI+EBX+NTFS_INDEX_ENTRY.Length]     ; CX = IndexEntry.Length
    ADD        EBX,ECX
    MOV        EAX,DWORD[ES:EDI+EBX-8]    ; EAX = ChildVcnLow
    MOV        EDX,DWORD[ES:EDI+EBX-4]    ; EDX = ChildVcnHigh
    MOV        DWORD[ESP],EAX
    MOV        DWORD[ESP+4],EDX
    
    ; 根据DataRunList获得对应的LCN,然后读取该IndexBlock,再在该IndexBlock内部搜索
    ; 现在开始查找IndexAllocation属性
    MOV        EAX,NTFS_ATTRIBUTE_TYPE_INDEX_ALLOCATION
    CALL    FindAttribute ; EAX = Attribute相对于记录开始位置的偏移
    CMP        EAX,0
    JZ        NEAR .NotFound
    
    ; 目前不考虑有多个IndexAllocation项的复杂情况
    ; EAX = 属性相对于FileRecord的偏移
    XOR        ECX,ECX
    MOV        CX,WORD[ES:EDI+EAX+NTFS_NONRESIDENT_ATTRIBUTE.RunArrayOffset]
    ADD        EAX,ECX  ; EAX = DataRun
    
    ; 对偏移进行解码,获得目标LCN
    ; 输入: ES:EDI = FileRecord EBX = DataRun EDX:EAX = VCN
    ; 输出: EDX:EAX= LCN 0代表错误
    MOV        EBX,EAX    ; EBX = DataRun
    MOV        EAX,DWORD[ESP]
    MOV        EDX,DWORD[ESP+4]
    CALL    DecodeDataRunList
    
    ; 假设32位(不管高32位)
    CMP        EAX,00H
    JZ        NEAR .NotFound        
    
    ;找到了Lcn 读取该IndexBlock
    ; 构造读取数据包
    XOR        ECX,ECX
    MOV        CL, BYTE[SectorsPerCluster]
    MUL        ECX    ; EDX:EAX = 起始扇区号
    MOV        DWORD[ BP - DAP_SECTOR_HIGH ], EDX
    MOV        DWORD[ BP - DAP_SECTOR_LOW     ], EAX
    MOV        WORD [ BP - DAP_BUFFER_SEG  ], DATA_BUF_SEG
    MOV        WORD [ BP - DAP_BUFFER_OFF  ], DATA_BUF_OFF
    
    ; 计算需要读取的扇区数
    XOR        EDX,EDX
    MOV        EAX,DWORD[ESP+08H]    ; BytesPerIndexBlock
    XOR        ECX,ECX
    MOV        CX,WORD[BytesPerSector]
    ADD        EAX,ECX
    DEC        EAX
    DIV        ECX
    MOV        BYTE [ BP - DAP_READ_SECTORS], AL
    
    ; 读取扇区
    CALL    ReadSectors
    
    ; FixupRecord
    ; ES:EDI = FileRecord
    MOV        AX,DATA_BUF_SEG
    MOV        ES,AX
    MOV        EDI,DATA_BUF_OFF
    CALL    FixupRecord
    
    ; 获得第一个IndexEntry项
    MOV        EBX,00H
    MOV     EAX,DWORD[ES:EDI+EBX+NTFS_INDEX_BLOCK.EntriesOffset] ; EAX = IndexHeader.EntriesOffset
    ADD         EAX,18H        ; 18H = offsetof( NTFS_INDEX_BLOCK,IndexHeader)
    ADD     EBX,EAX        ; EBX = First IndexEntry
    JMP        .LargeCheckNextEntry
    
    
;====================================================================
; 不存在IndexAllocation的目录(小目录)    
;====================================================================
.SmallIndexRoot:
    
    ; 只需搜索IndexRoot就可确定是否有FDOSLDR.BIN
    ; EBX = IndexRoot
    MOV     EAX,DWORD[ES:EDI+EBX+NTFS_INDEX_ROOT.EntriesOffset] ; EAX = IndexHeader.EntriesOffset
    ADD         EAX,10H        ; 10H = offsetof( NTFS_INDEX_ROOT,IndexHeader)
    ADD     EBX,EAX        ; EBX = First IndexEntry
    
; 检查下一个IndexEntry
.CheckNextEntry:    
    
    ; 检查EntryFlags是否为最后一个项
    MOV        CX,WORD[ ES:EDI+EBX+NTFS_INDEX_ENTRY.EntryFlags]
    AND     CX,NTFS_INDEX_FLAG_INDEX_END
    JZ        .NotFound

    ; 首先检查名称长度 FDOSLDR.BIN
    CMP        BYTE[ ES:EDI+EBX+NTFS_INDEX_ENTRY.NameLength],11
    JNZ        .NextEntry
    
    ; 再比较名称是否相同
    PUSH     EDI
    LEA         EDI,[ ES:EDI+EBX+NTFS_INDEX_ENTRY.Name]
    MOV        ESI,LoaderName
    MOV        ECX,11
    REPE    CMPSW
    POP        EDI        ; 恢复EDI
    JCXZ     .FoundLoader

.NextEntry:    
    XOR        ECX,ECX
    MOV         CX,WORD[ES:EDI+EBX+NTFS_INDEX_ENTRY.Length]
    ADD        EBX,ECX    ; EBX = 下一个IndexEntry
    JMP        .CheckNextEntry

.FoundLoader:    
    MOV        EAX,DWORD[ES:EDI+EBX+NTFS_INDEX_ENTRY.IndexedFile]        ; 低32位
    MOV        EDX,DWORD[ES:EDI+EBX+NTFS_INDEX_ENTRY.IndexedFile+4]    ; 高32位
    
%IFDEF DEBUG    
;    CALL    PrintDword
;    MOV        AL,20H
;    CALL    PrintChar
%ENDIF                
    
    JMP        SHORT .End
    
; 没有找到
.NotFound:            
    MOV  EAX,0
    MOV     EDX,0

.End:    
    ADD     ESP,16
    POP     ECX
    POP     EBX
    RET
    
;=============================================================    
; 对偏移进行解码,获得目标LCN
;         
; 输入: ES:EDI = FileRecord EBX = DataRun EDX:EAX = VCN
; 输出: EDX:EAX= LCN 0代表错误
;
;=============================================================
DecodeDataRunList:
    PUSH    ESI
    PUSH    ECX
    
    ; 临时变量
    SUB        ESP,40    ; ESP ESP-8 ESP-16 ESP-24 RunOffset RunLength StartLcn
    MOV        DWORD[ESP+00H],00H    ; RunOffset
    MOV        DWORD[ESP+04H],00H    
    MOV        DWORD[ESP+08H],00H    ; RunLength
    MOV        DWORD[ESP+0CH],00H    
    MOV        DWORD[ESP+10H],00H    ; StartLcn
    MOV        DWORD[ESP+14H],00H    
    MOV        DWORD[ESP+18H],00H    ; StartVcn
    MOV        DWORD[ESP+1CH],00H    
    MOV        DWORD[ESP+20H],EAX
    MOV        DWORD[ESP+28H],EDX

; 开始解码
.DecodeRun:    
    
    ; 更新StartVcn
    MOV        EAX,DWORD[ESP+08H]    ; EAX = RunLength
    ADD        DWORD[ESP+18H],EAX    ; StartVcn += RunLenth;

    ; 检查是否结束
    CMP        BYTE[ES:EDI+EBX],00H
    JZ        NEAR .NotFound
    
    ; 首先解码长度和偏移所占的字节数
    ; DH = 长度尺寸
    ; DL = 偏移尺寸
    MOV        DL,BYTE[ES:EDI+EBX]
    MOV        DH,DL
    AND        DH,0FH
    SHR        DL,04H
    AND        DL,0FH
    INC        EBX        ; SKIP First Byte
    MOV        ECX,00H

    
    ; 首先解码长度,假设不会超过4个字节(32位长度)
.NextLengthByte:
    XOR        EAX,EAX
    MOV        AL,BYTE[ES:EDI+EBX]
    SHL        EAX,CL
    ADD        DWORD[ESP+08H],EAX
    INC        EBX
    ADD        CL,08H
    DEC        DH
    JNZ        .NextLengthByte

    ; 解码偏移长度,假设不会超过4个字节(32位偏移)
    MOV        ECX,00H
.NextOffsetByte:
    XOR        EAX,EAX
    MOV        AL,BYTE[ES:EDI+EBX]
    SHL        EAX,CL
    
    ; 是否最后一个字节(符号位)
    CMP        DL,1
    JA        .NotLastByteOrPostive
    
    ; 是最后一个字节,需要考虑符号问题
    CMP        BYTE[ES:EDI+EBX],00H
    JGE        .NotLastByteOrPostive ; 有符号比较
    
    ; 最后一个字节小于0
    NEG        EAX        ; 求补码

.NotLastByteOrPostive:    
    ADD        DWORD[ESP+00H],EAX
    INC        EBX
    ADD        CL,08H
    DEC        DL
    JNZ        .NextOffsetByte
    
    ;=============================================================================    
    ; 接下来检查当前Run是否包含待查询的VCN
    ;=============================================================================
    MOV        EAX,DWORD[ESP+00H]    
    ADD        DWORD[ESP+10H],EAX    ; StartLcn += RunOffset ; 当前Run的起始逻辑簇号
    
    ; 如果 StartVcn <= SearchVcn < StartVcn+RunLength
    MOV        EAX,DWORD[ESP+20H]    ; Eax = SearchVcn
    CMP        EAX,DWORD[ESP+18H]    ; StartVcn
    JB        .DecodeRun            ; SearchVcn < StartVcn
    SUB        EAX,DWORD[ESP+08H]    ; SearchVcn-RunLength
    CMP        EAX,DWORD[ESP+18H]    ;
    JGE        .DecodeRun
    
    ; 找到咯 StartVcn <= SearchVcn < StartVcn+RunLength
    ; Lcn = StartLcn + ( SearchVcn - StartVcn )
    ;
    MOV        EAX,DWORD[ESP+20H]    ; Eax = SearchVcn
    ADD        EAX,DWORD[ESP+10H]    ; SearchVcn + StartLcn
    SUB        EAX,DWORD[ESP+18H]    ; - StartVcn
    MOV        EDX,DWORD[ESP+28H]    ; EDX不变        
    JMP        .DecodeEnd            ; 找到了
    
; 没有找到        
.NotFound:
    MOV        EAX,00H
    MOV        EDX,00H
    
; 解码结束
.DecodeEnd:
    
    ; 恢复堆栈
    ADD        ESP,40    
    POP        ECX
    POP        ESI
    RET

;=============================================================
; 需要将名称变为大写
; 输入:ES:EDI = 字符串 CX = 字符串长度 (宽字符串)
;
;=============================================================
ToUpperCase:
    PUSH     ECX
    PUSH    EBX
    XOR        EBX,EBX
    TEST    CX,CX
    JZ        .End        ; CX = 0
    
    ; 目前只处理 a-z => A-Z
.CheckNextChar:
    CMP        WORD[ES:EDI+EBX],0061H
    JB        .NextChar
    CMP        WORD[ES:EDI+EBX],007AH
    JA        .NextChar
    
    ; a < ch < z
    SUB        WORD[ES:EDI+EBX],20H
    
.NextChar:    
    ; 继续处理下一个字符
    INC        EBX
    INC        EBX
    DEC        CX
    JNZ        .CheckNextChar

.End:
    ; 返回
    POP        EBX
    POP        ECX
    RET

;====================================================================
;
; 显示一个字符
;    输入: AL = 待显示字符
;
;====================================================================
PrintChar:
    PUSH    AX
    PUSH    BX
    MOV     AH,0EH
    MOV        BX,7
    INT        10H
    POP     BX
    POP        AX
    RET
    

;====================================================================
;
; 显示16进制的值(将一个BYTE变为两个ASCII字符打印出来,用于调试)
; 输入: AL = 待显示的字节
;
;====================================================================
PrintByte:
    PUSH    BX
    MOV        BH,AL
    
    ; 显示高4位
    SHR        AL,4
    AND        AL,0FH
    ADD        AL,30H
    CMP        AL,39H
    JLE        .PrintIt
    ADD        AL,07H
.PrintIt:    
    CALL    PrintChar

    ; 显示低4位    
    MOV        AL,BH
    AND        AL,0FH
    ADD        AL,30H
    CMP        AL,39H
    JLE        .PrintItAgain
    ADD        AL,07H
.PrintItAgain:
    CALL    PrintChar
    POP        BX
    RET

;====================================================================
; 显示一个WORD值
; AX = 待显示的字
;====================================================================
PrintWord:
    PUSH    AX
    
    ; 先显示高8位
    SHR        AX,8
    CALL    PrintByte
    
    ; 再显示低8位
    POP        AX
    CALL    PrintByte
    
    ; 返回
    RET
    
;====================================================================
; 显示一个DWORD值
; EAX = 待显示的双字
;====================================================================
PrintDword:
    PUSH    EAX
        
    ; 先显示高16位
    SHR        EAX,16
    CALL    PrintWord
    
    ; 再显示低16位
    POP        EAX
    CALL    PrintWord
    
    ; 返回
    RET

;====================================================================
; 扇区最后的标记字节(NASM不支持重复ORG)
;====================================================================
SecondPadding    TIMES 2558-($-$$)    DB     00H
SecondSignature                        DW    0AA55H

你可能感兴趣的:(NTFS文件系统启动扇区代码(简化版))