; |==================|
; |    这是loader程序  |
; |==================|
; Created by longjin, 2022/01/17

; 由于实模式下,物理地址为CS<<4+IP,而从boot的定义中直到,loader的CS为0x1000, 因此loader首地址为0x10000
org 0x10000
    jmp Label_Start

%include 'fat12.inc'    ; 将fat12文件系统的信息包含进来

Base_Of_Kernel_File equ 0x00
Offset_Of_Kernel_File equ 0x100000 ; 设置内核文件的地址空间从1MB处开始。(大于实模式的寻址空间)

Base_Tmp_Of_Kernel_Addr equ 0x00
Offset_Tmp_Of_Kernel_File equ 0x7e00    ; 内核程序的临时转存空间

Memory_Struct_Buffer_Addr equ 0x7e00    ; 内核被转移到最终的内存空间后,原来的临时空间就作为内存结构数据的存储空间

; ==== 临时的全局描述符表 =====
[SECTION gdt]

LABEL_GDT:		dd	0,0
LABEL_DESC_CODE32:	dd	0x0000FFFF,0x00CF9A00 ; 代码段和数据段的段基地址都设置在0x00000000处, 把段限长设置为0xffffffff,可以索引32位地址空间
LABEL_DESC_DATA32:	dd	0x0000FFFF,0x00CF9200

GdtLen	equ	$ - LABEL_GDT
; GDTR寄存器是一个6B的结构,低2B保存GDT的长度, 高4B保存GDT的基地址
GdtPtr	dw	GdtLen - 1
	dd	LABEL_GDT

; 这是两个段选择子,是段描述符在GDT表中的索引号
SelectorCode32	equ	LABEL_DESC_CODE32 - LABEL_GDT
SelectorData32	equ	LABEL_DESC_DATA32 - LABEL_GDT

; === IA-32e模式的临时gdt表
[SECTION gdt64]
LABEL_GDT64:        dq 0x0000000000000000
LABEL_DESC_CODE64:  dq 0x0020980000000000
LABEL_DESC_DATA64:  dq 0x0000920000000000

GdtLen64    equ $ - LABEL_GDT64
GdtPtr64 dw GdtLen64-1,
            dd LABEL_GDT64

SelectorCode64 equ LABEL_DESC_CODE64 - LABEL_GDT64
SelectorData64 equ LABEL_DESC_DATA64 - LABEL_GDT64




[SECTION .s16]  ;定义一个名为.s16的段
[BITS 16]   ; 通知nasm,将要运行在16位宽的处理器上

Label_Start:
    mov ax, cs
    mov ds, ax ; 初始化数据段寄存器
    mov es, ax ; 初始化附加段寄存器
    mov ax, 0x00
    mov ss, ax ;初始化堆栈段寄存器
    mov sp, 0x7c00

    ;在屏幕上显示 start Loader
    mov ax, 0x1301
    mov bx, 0x000f
    mov dx, 0x0100  ;在第2行显示
    mov cx, 23 ;设置消息长度
    push ax

    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Start_Loader
    int 0x10
    ;jmp $

    ; 使用A20快速门来开启A20信号线
    push ax
    in al, 0x92 ; A20快速门使用I/O端口0x92来处理A20信号线
    or al, 0x02 ; 通过将0x92端口的第1位置1,开启A20地址线
    out 0x92, al
    pop ax

    cli ; 关闭外部中断

    db 0x66
    lgdt [GdtPtr]   ; LGDT/LIDT - 加载全局/中断描述符表格寄存器

    ; 置位CR0寄存器的第0位,开启保护模式
    mov eax, cr0
    or eax, 1
    mov cr0, eax

    ; 为fs寄存器加载新的数据段的值
    mov ax, SelectorData32
    mov fs, ax

    ; fs寄存器加载完成后,立即从保护模式退出。 这样能使得fs寄存器在实模式下获得大于1MB的寻址能力。
    mov eax, cr0
    and al, 11111110b ; 将第0位置0
    mov cr0, eax

    sti ; 开启外部中断




; =========在文件系统中搜索 kernel.bin==========
    mov word [SectorNo], SectorNumOfRootDirStart    ;保存根目录起始扇区号

Label_Search_In_Root_Dir_Begin:
    cmp word [RootDirSizeForLoop],  0 ; 比较根目录扇区数量和0的关系。 cmp实际上是进行了一个减法运算
    jz Label_No_KernelBin ; 等于0,不存在kernel.bin
    dec word [RootDirSizeForLoop]

    mov ax, 0x00
    mov es, ax
    mov bx, 0x8000
    mov ax, [SectorNo]  ;向函数传入扇区号
    mov cl, 1
    call Func_ReadOneSector
    mov si, Kernel_FileName ;向源变址寄存器传入Loader文件的名字
    mov di, 0x8000
    cld ;由于LODSB的加载方向与DF标志位有关,因此需要用CLD清零DF标志位

    mov dx, 0x10 ; 每个扇区的目录项的最大条数是(512/32=16,也就是0x10)

Label_Search_For_LoaderBin:
    cmp dx, 0
    jz Label_Goto_Next_Sector_In_Root_Dir
    dec dx
    mov cx, 11 ; cx寄存器存储目录项的文件名长度, 11B,包括了文件名和扩展名,但是不包括 分隔符'.'

Label_Cmp_FileName:
    cmp cx, 0
    jz Label_FileName_Found
    dec cx
    lodsb ; 把si对应的字节载入al寄存器中,然后,由于DF为0,si寄存器自增
    cmp al, byte [es:di]    ; 间接取址[es+di]。   也就是进行比较当前文件的名字对应字节和loader文件名对应字节
    jz  Label_Go_On     ; 对应字节相同
    jmp Label_Different     ; 字节不同,不是同一个文件

Label_Go_On:
    inc di
    jmp Label_Cmp_FileName

Label_Different:
    and di, 0xffe0 ;将di恢复到当前目录项的第0字节
    add di, 0x20     ;将di跳转到下一目录项的第0字节
    mov si, Kernel_FileName
    jmp Label_Search_For_LoaderBin  ;继续搜索下一目录项

Label_Goto_Next_Sector_In_Root_Dir:
    add word [SectorNo], 1
    jmp Label_Search_In_Root_Dir_Begin

Label_No_KernelBin:
    ; 在屏幕上显示 [ERROR] No Kernel Found.
    mov ax, 0x1301
    mov bx, 0x000c  ; 红色闪烁高亮黑底
    mov dx, 0x0200  ; 显示在第3行(前面已经显示过2行了)
    mov cx, 24  ; 字符串长度
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_No_Loader
    int 0x10

    jmp $

; ========= 找到了 kernel.bin ===========
; 将内核加载到内存中
Label_FileName_Found:
    mov ax, RootDirSectors

    ; 先取得目录项DIR_FstClus字段的值(起始簇号)
    and di, 0xffe0
    add di, 0x1a
    mov cx, word    [es:di]
    push cx


    add cx, ax
    add cx, SectorBalance
    mov eax, Base_Tmp_Of_Kernel_Addr    ; 内核放置的临时地址
    mov es, eax ;配置es和bx,指定kernel.bin在内存中的起始地址
    mov bx, Offset_Tmp_Of_Kernel_File
    mov ax, cx

Label_Go_On_Loading_File:
    push ax
    push bx

    ; 显示字符.
    mov ah, 0x0e
    mov al, "."
    mov bl, 0x0f
    int 0x10

    pop bx
    pop ax


    ; 读取一个扇区
    mov cl, 1
    call Func_ReadOneSector
    pop ax

    ; ======逐字节将内核程序复制到临时空间,然后转存到内核空间===
    push cx
    push eax
    push fs
    push edi
    push ds
    push esi

    mov cx, 0x0200 ; 指定计数寄存器的值为512, 为后面循环搬运这个扇区的数据做准备
    mov ax, Base_Of_Kernel_File
    mov fs, ax ; 这样在物理机上是行不通的,因为这样移动的话,fs就失去了32位寻址能力
    mov edi, dword  [OffsetOfKernelFileCount]   ; 指定目的变址寄存器

    mov ax, Base_Tmp_Of_Kernel_Addr
    mov ds, ax
    mov esi, Offset_Tmp_Of_Kernel_File  ; 指定来源变址寄存器



Label_Move_Kernel:
    ; 真正进行数据的移动
    mov al, byte [ds:esi]   ; 移动到临时区域
    mov byte [fs:edi], al   ; 再移动到目标区域

    inc esi
    inc edi
    loop Label_Move_Kernel

    ; 当前扇区数据移动完毕
    mov eax, 0x1000
    mov ds, eax

    mov dword [OffsetOfKernelFileCount],    edi ; 增加偏移量

    pop esi
    pop ds
    pop edi
    pop fs
    pop eax
    pop cx


    call Func_GetFATEntry

	cmp	ax,	0x0fff
	jz	Label_File_Loaded



	push ax
	mov	dx,	RootDirSectors
	add	ax,	dx
	add	ax,	SectorBalance



    ; 继续读取下一个簇
	jmp Label_Go_On_Loading_File

Label_File_Loaded:


    ;在屏幕上显示 kernel loaded
    mov ax, 0x1301
    mov bx, 0x000f
    mov dx, 0x0200  ;在第3行显示
    mov cx, 20 ;设置消息长度

    push ax

    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Kernel_Loaded
    int 0x10

     ; ======直接操作显示内存=======
    ; 从内存的0x0B800开始,是一段用于显示字符的内存空间。
    ; 每个字符占用2bytes,低字节保存要显示的字符,高字节保存样式
    mov ax, 0xB800
    mov gs, ax
    mov ah, 0x0F ;黑底白字
    mov al, '.'
    mov [gs:((80 * 2 + 20) * 2)], ax ;在屏幕第0行,39列





Label_Kill_Motor:
    ; =====关闭软驱的马达======
    ; 向IO端口0x03f2写入0,关闭所有软驱
    push dx
    mov dx, 0x03F2
    mov al, 0
    out dx, al
    pop dx

    ; =====获取物理地址空间====

    ; 显示 正在获取内存结构
    mov ax, 0x1301
    mov bx, 0x000F
    mov dx, 0x0300 ; 在第四行显示
    mov cx, 34
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Start_Get_Mem_Struct
    int 0x10


    mov ax, 0x00
    mov es, ax

    mov di, Memory_Struct_Buffer_Addr ; 设置内存结构信息存储的地址
    mov ebx, 0 ;第一次调用0x15的时候,ebx要置为0 ebx存储的是下一个待返回的ARDS  (Address Range Descriptor Structure)

Label_Get_Mem_Struct:
    ;==== 获取内存物理地址信息
    ; 使用0x15中断程序的功能号0xe820来获取内存信息
    ; 返回信息在[es:di]指向的内存中
    ; 一共要分5次才能把20个字节的信息获取完成
    ; 这些信息在内核初始化内存管理单元的时候,会去解析它们。

    mov eax, 0xe820
    mov ecx, 20 ; 指定ARDS结构的大小,是固定值20
    mov edx, 0x534d4150 ; 固定签名标记,是字符串“SMAP”的ASCII码
    int 0x15

    jc Label_Get_Mem_Fail ; 若调用出错,则CF=1

    add di, 20
    cmp ebx, 0
    jne Label_Get_Mem_Struct ; ebx不为0
    jmp Label_Get_Mem_OK ; 获取内存信息完成

Label_Get_Mem_Fail:
    ; =====获取内存信息失败====
    ; 显示 正在获取内存结构
    mov ax, 0x1301
    mov bx, 0x000c
    mov dx, 0x0400 ; 在第5行显示
    mov cx, 33
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Get_Mem_Failed
    int 0x10

    jmp $

Label_Get_Mem_OK:
    ; ==== 成功获取内存信息 ===
    mov ax, 0x1301
    mov bx, 0x000f
    mov dx, 0x0400 ; 在第5行显示
    mov cx, 38
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Get_Mem_Success
    int 0x10

    jmp Label_Get_SVGA_Info

Label_Get_SVGA_Info:
    ; ==== 获取SVGA芯片的信息
    mov ax, 0x1301
    mov bx, 0x000f
    mov dx, 0x0500 ; 在第6行显示
    mov cx, 34
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Start_Get_SVGA_VBE_Info
    int 0x10

    ; 使用INT0x10的主功能号0x4F00获取SVGA VBE信息
    ; For more information, please visit: https://longjin666.top/?p=1321
    mov ax, 0x00
    mov es, ax
    mov di, 0x8000
    mov ax, 0x4F00
    int 0x10

    cmp ax, 0x004F ; 获取成功
    jz Label_Get_SVGA_VBE_Success

Label_Get_SVGA_VBE_Failed:
    ; 获取SVGA VBE信息失败
    mov ax, 0x1301
    mov bx, 0x008c
    mov dx, 0x0600 ; 在第7行显示
    mov cx, 33
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Get_SVGA_VBE_Failed
    int 0x10
    jmp $

Label_Get_SVGA_VBE_Success:
    mov ax, 0x1301
    mov bx, 0x000f
    mov dx, 0x0600 ; 在第7行显示
    mov cx, 38
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Get_SVGA_VBE_Success
    int 0x10

Label_Get_SVGA_Mode_Info:
    ; ====== 获取SVGA mode信息 ======
    mov ax, 0x1301
    mov bx, 0x000f
    mov dx, 0x0700 ; 在第8行显示
    mov cx, 35
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Start_Get_SVGA_Mode_Info
    int 0x10

    mov ax, 0x00
    mov es, ax
    mov si, 0x800e ; 根据文档可知,偏移量0Eh处,	DWORD	pointer to list of supported VESA and OEM video modes
                    		;(list of words terminated with FFFFh)
    mov esi, dword [es:si]
    mov edi, 0x8200

Label_SVGA_Mode_Info_Get:
    mov cx, word [es:esi]


; ===========显示SVGA mode的信息
    ;push	ax

	;mov	ax,	0x00
	;mov	al,	ch
	;call	Label_DispAL

	;mov	ax,	0x00
	;mov	al,	cl
	;call	Label_DispAL

	;pop	ax
;============

    ;  判断是否获取完毕
    cmp cx, 0xFFFF
    jz Label_SVGA_Mode_Info_Finish

    mov ax, 0x4f01 ; 使用4f01功能,获取SVGA的模式
    int 0x10

    cmp ax, 0x004f ; 判断是否获取成功
    jnz Label_SVGA_Mode_Info_Fail

    add esi, 2
    add edi, 0x100 ; 开辟一个 256-byte 的 buffer

    jmp Label_SVGA_Mode_Info_Get

Label_SVGA_Mode_Info_Fail:
    ; === 获取信息失败 ===
    mov ax, 0x1301
    mov bx, 0x008c
    mov dx, 0x0800 ; 在第9行显示
    mov cx, 34
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Get_SVGA_Mode_Failed
    int 0x10

    jmp $

Label_SVGA_Mode_Info_Finish:
    ; === 成功获取SVGA mode信息 ===
    mov ax, 0x1301
    mov bx, 0x000f
    mov dx, 0x0800 ; 在第9行显示
    mov cx, 39
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Get_SVGA_Mode_Success
    int 0x10
    jmp Label_Set_SVGA_Mode

Label_SET_SVGA_Mode_VESA_VBE_FAIL:
    ; 设置SVGA显示模式失败
    mov ax, 0x1301
    mov bx, 0x008c
    mov dx, 0x0800 ; 在第10行显示
    mov cx, 29
    push ax
    mov ax, ds
    mov es, ax
    pop ax
    mov bp, Message_Set_SVGA_Mode_Failed
    int 0x10

    jmp $


Label_Set_SVGA_Mode:

; ===== 设置SVGA芯片的显示模式(VESA VBE) ===
    mov ax, 0x4f02 ; 使用int0x10 功能号AX=4f02设置SVGA芯片的显示模式
    mov bx, 0x4180 ; 显示模式可以选择0x180(1440*900 32bit)或者0x143(800*600 32bit)
    int 0x10

    cmp ax, 0x004F
    jnz Label_SET_SVGA_Mode_VESA_VBE_FAIL

; ===== 初始化GDT表,切换到保护模式 =====

    cli ; 关闭外部中断
    db 0x66
    lgdt [GdtPtr]

    db 0x66
    lidt [IDT_POINTER]

    mov eax, cr0
    or eax, 1 ; 启用保护模式
    mov cr0, eax

    ; 跳转到保护模式下的第一个程序
    jmp dword SelectorCode32:GO_TO_TMP_Protect


[SECTION .s32]
[BITS 32]
GO_TO_TMP_Protect:
    ; ==== 切换到长模式 =====
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov ss, ax
    mov esp, 0x7e00 ; 将栈指针设置在实模式获取到的数据的基地址上

    call support_long_mode ; 检测是否支持长模式

    test eax, eax ; 将eax自身相与,检测是否为0(test指令不会把结果赋值回去eax)
    jz no_support ; 不支持长模式

    ; 初始化临时页表, 基地址设置为0x90000
    ; 设置各级页表项的值(页表起始地址与页属性组成)
    mov	dword	[0x90000],	0x91007
	mov	dword	[0x90004],	0x00000
	mov	dword	[0x90800],	0x91007
	mov	dword	[0x90804],	0x00000

	mov	dword	[0x91000],	0x92007
	mov	dword	[0x91004],	0x00000

	mov	dword	[0x92000],	0x000083
	mov	dword	[0x92004],	0x000000

	mov	dword	[0x92008],	0x200083
	mov	dword	[0x9200c],	0x000000

	mov	dword	[0x92010],	0x400083
	mov	dword	[0x92014],	0x000000

	mov	dword	[0x92018],	0x600083
	mov	dword	[0x9201c],	0x000000

	mov	dword	[0x92020],	0x800083
	mov	dword	[0x92024],	0x000000

	mov	dword	[0x92028],	0xa00083
	mov	dword	[0x9202c],	0x000000

	; === 加载GDT ===
	db 0x66
	lgdt [GdtPtr64] ; 加载GDT
    ; 把临时gdt的数据段加载到寄存器中(cs除外)
    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov ss, ax

    mov esp, 0x7e00

    ; ====== 开启物理地址扩展 =====
    ; 通过bts指令,将cr4第5位置位,开启PAE
    mov eax, cr4
    bts eax, 5
    mov cr4, eax

    ; 将临时页目录的地址设置到CR3控制寄存器中
    mov eax, 0x90000
    mov cr3, eax

    ; ==== 启用长模式 ===
    ; 参见英特尔开发手册合集p4360 volume4, chapter2  页码2-60 Vol. 4
    ; IA32_EFER寄存器的第8位是LME标志位,能启用IA-32e模式
    mov ecx, 0xC0000080
    rdmsr
    bts eax, 8
    wrmsr

    ; === 开启分页机制 ===
    mov eax, cr0
    bts eax, 0 ; 再次开启保护模式
    bts eax, 31 ; 开启分页管理机制
    mov cr0, eax


    ; === 通过此条远跳转指令,处理器跳转到内核文件进行执行,正式进入IA-32e模式

    jmp SelectorCode64:Offset_Of_Kernel_File


support_long_mode:
    ; ===== 检测是否支持长模式 ====
    mov eax, 0x80000000
    cpuid ; cpuid指令返回的信息取决于eax的值。当前返回到eax中的是最大的输入参数值。 详见:英特尔开发人员手册卷2A Chapter3 (Page 304)
    cmp eax, 0x80000001
    setnb al ; 当cmp结果为不低于时,置位al
    jb support_long_mode_done ; 当eax小于0x80000001时,跳转

    mov eax, 0x80000001
    cpuid ; 获取特定信息,参照开发人员手册卷2A p304

    bt edx, 29 ; 将edx第29位的值移到CF上。该位指示了CPU是否支持IA-32e模式
                ; Bit 29: Intel® 64 Architecture available if 1.
    setc al ; 若支持则al置位

support_long_mode_done:
     movzx eax, al ; 将al,零扩展为32位赋值给eax
     ret

no_support:
    ; 不支持长模式
    jmp $



[SECTION .s16lib]
[BITS 16]
; 从软盘读取一个扇区
; AX=待读取的磁盘起始扇区号
; CL=读入的扇区数量
; ES:BX=>目标缓冲区起始地址
Func_ReadOneSector:

    push bp
    mov bp,  sp
    sub esp, 2
    mov byte [bp-2], cl
    push bx
    mov bl, [BPB_SecPerTrk]
    div bl  ;用AX寄存器中的值除以BL,得到目标磁道号(商:AL)以及目标磁道内的起始扇区号(余数:AH)
    inc ah  ; 由于磁道内的起始扇区号从1开始计数,因此将余数+1
    mov cl, ah
    mov dh, al
    shr al, 1 ;计算出柱面号
    mov ch, al
    and dh, 1;计算出磁头号

    pop bx
    mov dl, [BS_DrvNum]
    ;最终,dh存储了磁头号,dl存储驱动器号
    ;   ch存储柱面号,cl存储起始扇区号

Label_Go_On_Reading:
    ; 使用BIOS中断服务程序INT13h的主功能号AH=02h实现软盘读取操作
    mov ah, 2
    mov al, byte [bp-2]
    int 0x13

    jc Label_Go_On_Reading  ;当CF标志位被复位时,说明数据读取完成,恢复调用现场

    add esp, 2
    pop bp

    ret


;   解析FAT表项,根据当前FAT表项索引出下一个FAT表项
Func_GetFATEntry:
    ; AX=FAT表项号(输入、输出参数)
    ; 保存将要被修改的寄存器


    push es
    push bx
    push ax

    ; 扩展段寄存器
    mov ax, 00
    mov es, ax

    pop ax
    mov byte [Odd], 0   ;将奇数标志位置0

    ; 将FAT表项号转换为总的字节号
    mov bx, 3
    mul bx
    mov bx, 2
    div bx

    cmp dx, 0
    jz Label_Even ; 偶数项
    mov byte [Odd], 1

Label_Even:
    xor dx, dx  ;把dx置0

    ; 计算得到扇区号(商)和扇区内偏移(余数)
    mov bx, [BPB_BytesPerSec]
    div bx
    push dx

    ; 读取两个扇区到[es:bx]
    mov bx, 0x8000
    add ax, SectorNumOfFAT1Start
    mov cl, 2 ; 设置读取两个扇区,解决FAT表项跨扇区的问题



    call Func_ReadOneSector

    pop dx
    add bx, dx
    mov ax, [es:bx]
    cmp byte [Odd], 1
    jnz Label_Even_2 ;若是偶数项,则跳转

    shr ax, 4 ; 解决奇偶项错位问题

Label_Even_2:
    and ax, 0x0fff ; 确保表项号在正确的范围内  0x0003~0x0fff
    pop bx
    pop es
    ret

; ==== 显示AL中的信息 ===
Label_DispAL:
    push ecx
    push edx
    push edi

    mov edi, [DisplayPosition]
    mov ah, 0x0F
    mov dl, al  ; 为了先显示al的高4位,因此先将al暂存在dl中,然后把al往右移动4位
    shr al, 4
    mov ecx, 2 ; 计数为2

.begin:
    and al, 0x0F
    cmp al, 9
    ja .1 ; 大于9,跳转到.1
    add al, '0'
    jmp .2
.1:
    sub al, 0x0a
    add al, 'A'
.2:
    ; 移动到显示内存中
    mov [gs:edi], ax
    add edi, 2

    mov al, dl
    loop .begin

    mov [DisplayPosition], edi

    pop edi
    pop edx
    pop ecx

    ret

; === 临时的中断描述符表 ===
; 为临时的IDT开辟空间。
; 由于模式切换过程中已经关闭了外部中断,只要确保模式切换过程中不产生异常,就不用完整的初始化IDT。甚至乎,只要没有异常产生,没有IDT也可以。
IDT:
    times 0x50 dq 0
IDT_END:

IDT_POINTER:
    dw IDT_END - IDT - 1
    dd IDT

;==== 临时变量 =====
RootDirSizeForLoop	dw	RootDirSectors
SectorNo		dw	0
Odd			db	0
OffsetOfKernelFileCount	dd	Offset_Of_Kernel_File

DisplayPosition dd 0

; 要显示的消息文本
Message_Start_Loader: db "[DragonOS] Start Loader"
Message_No_Loader: db "[ERROR] No Kernel Found."
Message_Kernel_Loaded: db "[INFO] Kernel loaded"
Message_Start_Get_Mem_Struct: db "[INFO] Try to get memory struct..."
Message_Get_Mem_Failed: db "[ERROR] Get memory struct failed."
Message_Get_Mem_Success: db "[INFO] Successfully got memory struct."
Message_Start_Get_SVGA_VBE_Info: db "[INFO] Try to get SVGA VBE info..."
Message_Get_SVGA_VBE_Failed: db "[ERROR] Get SVGA VBE info failed."
Message_Get_SVGA_VBE_Success: db "[INFO] Successfully got SVGA VBE info."
Message_Start_Get_SVGA_Mode_Info: db "[INFO] Try to get SVGA mode info..."
Message_Get_SVGA_Mode_Failed: db "[ERROR] Get SVGA Mode info failed."
Message_Get_SVGA_Mode_Success: db "[INFO] Successfully got SVGA Mode info."
Message_Set_SVGA_Mode_Failed: db "[ERROR] Set SVGA Mode failed."

Kernel_FileName: db "KERNEL  BIN", 0