editing disabled

Simple Multi-Format Boot Loader


This is a simple FAT12 floppy disk image second stage boot loader.

It will boot and run from
  • a floppy disk
  • a CD
  • a flash drive via floppy disk emulation
  • a flash drive via hard disk emulation
The flash emulation used depends on the BIOS settings.

Four types of removable media - one image file.

After loading the second stage it will call an entry address which is located in the last 2 bytes of the second stage. This will allow some initial second stage code such as displaying a logo. Then by popping the return address into a register and calling that register the boot loader can be returned to as a subroutine call.

The boot loader will then display "Press any key to START |" while a twirling advancing progress bar counts down 5 seconds. Pressing any key will return to the second stage. Pressing the ESCape key or letting it time out will cause the PC operating system to boot and run without removing the removable device. This is similar to some bootable CD's but works with all 4 types of removable media - as is.

If no hard drive boot sector is detected or it is run in QEMU emulator the boot loader will return immediately to the second stage. The example second stage code displays a logo and when STARTed displays a drive letter prompt: "A:\>" for floppy disk, CD and flash drive FD emulation, or "C:\>" for flash drive HD emulation.

Original code written by Mike Gonta,
Public Domain December 30, 2008.

Assemble with FASM or NASM.

boot.asm

- 512 bytes exact
 
LOAD_ADDRESS                   equ 3000h 
RUN_ADDRESS                    equ LOAD_ADDRESS 
ESCAPE_KEY                     equ 1Bh 
BACKSPACE_KEY                  equ 8 
CTRL_FLAG                      equ 4 
 
 
SECTORS_PER_TRACK              equ 7C18h 
NUMBER_OF_HEADS                equ 7C1Ah 
DRIVE_NUMBER                   equ 7C24h 
 
 
org 500h                     ; this is the relocated org 
  jmp start 
  nop 
  db 'SMF-BOOT'              ; OEM name 
  dw 512                     ; bytes per sector 
  db 1                       ; sectors per cluster 
  dw 36                      ; reserved sector count 
  db 2                       ; number of FATs 
  dw 16*14                   ; root directory entries 
  dw 18*2*80                 ; sector count 
  db 0F0h                    ; media byte 
  dw 9                       ; sectors per FAT 
  dw 18                      ; sectors per track 
  dw 2                       ; number of heads 
  dd 0                       ; hidden sectors 
  dd 0                       ; number of sectors huge 
  db 0                       ; drive number 
  db 0                       ; reserved 
  db 29h                     ; signature 
  dd 12345678h               ; volume ID 
  db 'SMF-BOOT   '           ; volume label 
  db 'FAT12   '              ; file system type 
 
 
int_13h: 
  cmp dl, 80h 
  jb .1 
  inc dl 
.1: 
  jmp FAR [cs:int_13h_save] 
int_13h_save                   dd 0 
 
 
start: 
  cli 
  xor ax, ax 
  mov ds, ax 
  mov es, ax 
  mov ss, ax 
  mov sp, 7C00h 
  sti 
 
 
  mov si, 7C04h              ; skip BIOS variable at 500h 
  mov di, 504h 
  call transfer 
 
 
  jmp 0:relocated 
relocated: 
  mov ax, 3 
  int 10h 
  cmp dl, 80h 
  jne .1 
  mov [DRIVE_NUMBER], dl 
  mov ah, 8 
  int 13h                    ; check hard disk format 
  and cx, 3Fh                ; maximum sector number 
  mov [SECTORS_PER_TRACK], cx 
  movzx dx, dh               ; maximum head number 
  add dx, 1 
  mov [NUMBER_OF_HEADS], dx 
.1: 
 
 
  mov ax, 211h               ; read 17 sectors 
  mov bx, LOAD_ADDRESS 
  mov cx, 2                  ; start at next sector 
  mov dx, WORD [DRIVE_NUMBER] 
  int 13h 
 
 
  mov ax, 18 
  xor dx, dx 
  div WORD [SECTORS_PER_TRACK] 
  add dx, 1 
  mov cx, dx 
  xor dx, dx 
  div WORD [NUMBER_OF_HEADS] 
  mov dh, dl 
  xchg al, ah 
  shl al, 6 
  or cx, ax 
  mov ax, 212h               ; read next 18 sectors 
  add bx, 512*17 
  mov dl, [DRIVE_NUMBER] 
  int 13h 
 
 
  mov dx, WORD [DRIVE_NUMBER] 
  cmp dl, 80h 
  mov dl, 80h 
  jne .2 
  inc dl 
.2: 
  mov ax, 201h 
  mov bx, 7E00h 
  mov cx, 1 
  int 13h 
 
 
  pushf 
  push ds 
  mov dl, [DRIVE_NUMBER] 
  call WORD [LOAD_ADDRESS+((512*35)-2)] 
  pop ax 
  pop ds 
  popf 
  push ax 
  jnc .3 
  ret 
.3: 
  mov ax, 0B814h             ; third line 
  mov es, ax 
  mov si, prompt 
  xor bx, bx 
.4: 
  mov al, [si] 
  inc si 
  test al, al 
  je .5 
  mov BYTE [es:bx], al 
  lea bx, [bx+2] 
  jmp short .4 
.5: 
 
 
  cli 
  mov eax, [20h] 
  mov [irq0_save], eax 
  mov DWORD [20h], irq0 
  sti 
 
 
.6: 
  wait 
  jmp .6 
 
 
transfer: 
  mov cx, 512/4 
.1: 
  mov eax, [si] 
  lea si, [si+4] 
  mov [di], eax 
  lea di, [di+4] 
  sub cx, 1 
  jne .1 
  ret 
 
 
irq0: 
  mov ah, 1 
  int 16h 
  je .1 
  xor ax, ax 
  int 16h 
  jmp .2 
.1: 
  mov ah, 2 
  int 16h 
  and ax, CTRL_FLAG 
  je .waiting 
.2: 
  cmp al, ESCAPE_KEY 
  jne .char 
.timeout: 
  call .clean_up 
  push ds 
  pop es 
  cmp BYTE [DRIVE_NUMBER], 80h 
  jne .3 
  mov eax, [13h*4] 
  mov [int_13h_save], eax 
  mov DWORD [13h*4], int_13h 
.3: 
  mov si, 7E00h 
  mov di, 7C00h 
  call transfer 
  jmp 0:7C00h 
.char: 
  mov al, ' ' 
  xor bx, bx 
  mov cx, 28 
.4: 
  mov [es:bx], al 
  lea bx, [bx+2] 
  sub cx, 1 
  jne short .4 
  mov dl, [DRIVE_NUMBER] 
  add sp, 6 
.clean_up: 
  mov eax, [irq0_save] 
  mov [20h], eax 
  mov al, 20h 
  out 20h, al 
  sti 
  ret 
.waiting: 
  mov bx, WORD [clock_ticks] 
  test bx, 1 
  je short .5 
  shr bx, 1 
  and bx, 3 
  mov al, [clock_hand+bx] 
  mov bx, [cursor] 
  mov [es:bx], al 
.5: 
  sub WORD [clock_ticks], 1 
  jne .exit 
  mov WORD [clock_ticks], 21 
  sub WORD [seconds], 1 
  je .timeout 
  lea bx, [bx+2] 
  mov [es:bx], al 
  mov [cursor], bx 
.exit: 
  jmp FAR [irq0_save] 
irq0_save                      dd 0 
clock_hand                     db '|\-/' 
clock_ticks                    dw 21 
seconds                        dw 5 
cursor                         dw 46 
prompt                       db 'Press any key to START', 0 
  times 510-($-$$)             db 0 
                               dw 0AA55h 


second.asm

- 17920 bytes exact
 
LOAD_ADDRESS                   equ 3000h 
 
 
org LOAD_ADDRESS 
second_stage: 
  mov si, notice 
  call print 
 
 
  pop ax 
  call ax 
 
 
  add BYTE [prompt1], dl 
  cmp BYTE [prompt1], 'A' 
  je .1 
  mov BYTE [prompt1], 'C' 
.1: 
  mov si, prompt1 
  call print 
  jmp $ 
 
 
print: 
  mov ah, 0Eh 
.1: 
  mov al, [si] 
  inc si 
  test al, al 
  je .2 
  int 10h 
  jmp .1 
.2: 
  ret 
notice db 'Simple Multi Format - Boot Loader',0Dh,0Ah,0Dh 
       db 0Ah, 0 
prompt1                        db 'A:\>', 0 
 
 
  times (512*35)-($-second_stage)-2 db 0 
  dw RUN_ADDRESS 


fat.asm

- 20480 bytes exact
 
YEAR                           equ 2008 
MONTH                          equ 12 
DAY                            equ 30 
HOUR                           equ 13 
MINUTE                         equ 0 
SECOND                         equ 0 
DATE                 equ ((YEAR-1980)*512)+(MONTH*32)+DAY 
TIME                 equ (HOUR*2048)+(MINUTE*32)+(SECOND/2) 
 
 
fat1: 
  db 0F0h, 0FFh, 0FFh 
  db 003h,040h,000h,005h,060h,000h,007h,080h,000h,009h,0F0h 
  db 0FFh 
  times 512*9-($-fat1) db 0 
fat2: 
  db 0F0h, 0FFh, 0FFh 
  db 003h,040h,000h,005h,060h,000h,007h,080h,000h,009h,0F0h 
  db 0FFh 
  times 512*9-($-fat2) db 0 
root: 
  db 'SMF-BOOT   ' 
  db 8 
  times 20 db 0 
  times 159*32 db 0E5h       ; 159 deleted reusable entries 
; this is the Primary Volume Descriptor - must be located 
; here and named '_CD001     ', this is also the name of 
; the file which is the Boot Record Descriptor and the 
; BootCat.Bin which must be located exactly as in this code. 
  .name                        db '_CD001     ' 
  .attribute                   db 20h 
  .reserved                    db 0 
  .create_time_fine            db 0 
  .create_time                 dw TIME 
  .create_date                 dw DATE 
  .access_date                 dw DATE 
  .ea_index                    dw 0 
  .modified_time               dw TIME 
  .modified_date               dw DATE 
  .first_cluster               dw 2 
  .file_size                   dd 1000h 
  times 512*14-($-root) db 0 
 
 
Boot_Record_Descriptor: 
  .Volume_Descriptor_Type      db 0 
  .Standard_Identifier         db 'CD001' 
  .Volume_Descriptor_Version   db 1 
                               db 'EL TORITO SPECIFICATION' 
  times 41                     db 0 
  .Boot_Catalog_Sector         dd 18 
  times 2048-($-Boot_Record_Descriptor) db 0 
 
 
BootCat.Bin: 
  .Validation_Entry: 
    .Header_ID                 db 1 
    .Platform_ID               db 0 
                               dw 0 
    .ID_String: 
    times 24                   db 0 
    .Checksum_Word             db 0AAh, 55h 
    .Signature                 db 55h, 0AAh 
  .Default_Entry: 
    .Boot_Indicator            db 88h 
    .Boot_Media_Type           db 2 
    .Load_Segment              dw 0 
    .System_Type               db 0 
                               db 0 
    .Virtual_Sector_Count      dw 1 
    .Load_RBA                  dd 0 
  times 2048-($-BootCat.Bin) db 0 



fill.asm

- 1435648 bytes exact
 
  times 2804*512 db 0F6h 

Setting up


Assemble the code above and then concatenate the components as shown in the following DOS command
copy /B boot + second + fat + fill smf-boot

or as in the following Unix command
cat boot second fat fill > smf-boot

This will produce a file smf-boot of 1,474,560 bytes.