; -*- fundamental -*- (asm-mode sucks)
; $Id: syslinux.asm,v 1.10 1998/05/07 07:03:17 hpa Exp $
; -----------------------------------------------------------------------
;
;   Copyright 1998 H. Peter Anvin - All Rights Reserved
;
;   This program is free software; you can redistribute it and/or modify
;   it under the terms of the GNU General Public License as published by
;   the Free Software Foundation, Inc., 675 Mass Ave, Cambridge MA 02139,
;   USA; either version 2 of the License, or (at your option) any later
;   version; incorporated herein by reference.
;
; -----------------------------------------------------------------------

;
; syslinux.asm
;
;       DOS installer for SYSLINUX
;

               absolute 0
pspInt20:               resw 1
pspNextParagraph:       resw 1
                       resb 1          ; reserved
pspDispatcher:          resb 5
pspTerminateVector:     resd 1
pspControlCVector:      resd 1
pspCritErrorVector:     resd 1
                       resw 11         ; reserved
pspEnvironment:         resw 1
                       resw 23         ; reserved
pspFCB_1:               resb 16
pspFCB_2:               resb 16
                       resd 1          ; reserved
pspCommandLen:          resb 1
pspCommandArg:          resb 127

               section .text
               org 0100h
_start:
               mov ax,3000h                    ; Get DOS version
               int 21h
               xchg al,ah
               mov [DOSVersion],ax
               cmp ax,0314h                    ; DOS 3.20 minimum
               jae dosver_ok
               mov dx,msg_ancient_err
               jmp die

               section .bss
               alignb 2
DOSVersion:     resw 1

               section .text
;
; Scan command line for a drive letter followed by a colon
;
dosver_ok:
               xor cx,cx
               mov si,pspCommandArg
               mov cl,[pspCommandLen]

cmdscan1:       jcxz bad_usage                  ; End of command line?
               lodsb                           ; Load character
               dec cx
               cmp al,' '                      ; White space
               jbe cmdscan1
               cmp al,'-'
               je scan_option
               or al,020h                      ; -> lower case
               cmp al,'a'                      ; Check for letter
               jb bad_usage
               cmp al,'z'
               ja bad_usage
               sub al,'a'                      ; Convert to zero-based index
               mov [DriveNo],al                ; Save away drive index

               section .bss
DriveNo:        resb 1

               section .text
;
; Got the leading letter, now the next character must be a colon
;
got_letter:     jcxz bad_usage
               lodsb
               dec cx
               cmp al,':'
               jne bad_usage
;
; Got the colon; the rest better be whitespace
;
got_colon:      jcxz got_cmdline
               lodsb
               dec cx
               cmp al,' '
               jbe got_colon
;
; We end up here if the command line doesn't parse
;
bad_usage:      mov dx,msg_unfair
               jmp die

               section .data
msg_unfair:     db 'Usage: syslinux [-s] <drive>:', 0Dh, 0Ah, '$'

               section .text
;
; Scan for options after a - sign.  The only recognized option right now
; is -s.
;
scan_option:    jcxz bad_usage
               lodsb
               dec cx
               cmp al,' '
               jbe cmdscan1
               or al,20h
               cmp al,'s'
               jne bad_usage
               push si                 ; make_stupid doesn't save these
               push cx
               call make_stupid        ; Enable stupid boot sector
               pop cx
               pop si
               jmp short scan_option

;
; Parsed the command line OK.  Check that the drive parameters are acceptable
;
               struc DPB
dpbDrive:       resb 1
dpbUnit:        resb 1
dpbSectorSize:  resw 1
dpbClusterMask: resb 1
dpbClusterShift: resb 1
dpbFirstFAT:    resw 1
dpbFATCount:    resb 1
dpbRootEntries: resw 1
dpbFirstSector: resw 1
dpbMaxCluster:  resw 1
dpbFATSize:     resw 1
dpbDirSector:   resw 1
dpbDriverAddr:  resd 1
dpbMedia:       resb 1
dpbFirstAccess: resb 1
dpbNextDPB:     resd 1
dpbNextFree:    resw 1
dpbFreeCnt:     resw 1
               endstruc

got_cmdline:
               mov dl,[DriveNo]
               inc dl                          ; 1-based
               mov bx,DPB
               mov ah,32h
               int 21h                         ; Get Drive Parameter Block

               and al,al
               jnz filesystem_error

               cmp word [bx+dpbSectorSize],512 ; Sector size = 512 required
               jne sectorsize_error

               cmp byte [bx+dpbClusterShift],5 ; Max size = 16K = 2^5 sectors
               jna read_bootsect

hugeclust_error:
               mov dx,msg_hugeclust_err
               jmp die
filesystem_error:
               mov dx,msg_filesystem_err
               jmp die
sectorsize_error:
               mov dx,msg_sectorsize_err
               jmp die

;
; Good enough.  Now read the old boot sector and copy the superblock.
;
               section .data
               align 4, db 0
DISKIO          equ $
diStartSector:  dd 0                            ; Absolute sector 0
diSectors:      dw 1                            ; One sector
diBuffer:       dw SectorBuffer                 ; Buffer offset
               dw 0                            ; Buffer segment

               section .text
read_bootsect:
               mov ax,cs                       ; Set DS <- CS
               mov ds,ax

               cmp word [DOSVersion],0400h     ; DOS 4.00 has a new interface
               jae .new
old:
               mov bx,SectorBuffer
               mov cx,1                        ; One sector
               jmp short .common
new:
               mov bx,DISKIO
               mov [bx+8],ax                   ; Buffer segment
               mov cx,-1
common:
               xor dx,dx                       ; Absolute sector 0
               mov al,[DriveNo]
               int 25h                         ; DOS absolute disk read
               pop ax                          ; Remove flags from stack
               jc disk_read_error

               mov si,SectorBuffer+11          ; Offset of superblock
               mov di,BootSector+11
               mov cx,51                       ; Superblock = 51 bytes
               rep movsb                       ; Copy the superblock
               jmp short write_file
disk_read_error:
               mov dx,msg_read_err
               jmp die
;
; Writing LDLINUX.SYS
;
write_file:
               ; 0. Set the correct filename

               mov al,[DriveNo]
               add [ldlinux_sys_str],al

               ; 1. If the file exists, strip its attributes and delete

               xor cx,cx                       ; Clear attributes
               mov dx,ldlinux_sys_str
               mov ax,4301h                    ; Set file attributes
               int 21h

               mov dx,ldlinux_sys_str
               mov ah,41h                      ; Delete file
               int 21h

               section .data
ldlinux_sys_str: db 'A:\LDLINUX.SYS', 0

               section .text

               ; 2. Create LDLINUX.SYS and write data to it

               mov dx,ldlinux_sys_str
               xor cx,cx                       ; Normal file
               mov ah,3Ch                      ; Create file
               int 21h
               jc file_write_error
               mov [FileHandle],ax

               mov bx,ax
               mov cx,ldlinux_size
               mov dx,LDLinuxSYS
               mov ah,40h                      ; Write data
               int 21h
               jc file_write_error
               cmp ax,ldlinux_size
               jne file_write_error

               mov bx,[FileHandle]
               mov ah,3Eh                      ; Close file
               int 21h

               section .bss
FileHandle:     resw 1

               section .text

               ; 3. Set the readonly flag on LDLINUX.SYS

               mov dx,ldlinux_sys_str
               mov cx,1                        ; Read only
               mov ax,4301h                    ; Set attributes
               int 21h
;
; Writing boot sector
;
write_bootsect:
               cmp word [DOSVersion],0400h     ; DOS 4.00 has a new interface
               jae .new
old:
               mov bx,BootSector
               mov cx,1                        ; One sector
               jmp short .common
new:
               mov bx,DISKIO
               mov word [bx+6],BootSector
               mov cx,-1
common:
               xor dx,dx                       ; Absolute sector 0
               mov al,[DriveNo]
               int 26h                         ; DOS absolute disk write
               pop ax                          ; Remove flags from stack
               jc disk_write_error

all_done:       mov ax,4C00h                    ; Exit good status
               int 21h
;
; Error routine jump
;
disk_write_error:
file_write_error:
               mov dx,msg_write_err
die:
               push cs
               pop ds
               push dx
               mov dx,msg_error
               mov ah,09h
               int 21h
               pop dx

               mov ah,09h                      ; Write string
               int 21h

               mov ax,4C01h                    ; Exit error status
               int 21h

;
; This includes a small subroutine make_stupid to patch up the boot sector
; in case we give the -s (stupid) option
;
               %include "stupid.inc"

               section .data
msg_error:              db 'ERROR: $'
msg_ancient_err:        db 'DOS version 3.20 or later required', 0Dh, 0Ah, '$'
msg_filesystem_err:     db 'Filesystem not found on disk', 0Dh, 0Ah, '$'
msg_sectorsize_err:     db 'Sector sizes other than 512 bytes not supported', 0Dh, 0Ah, '$'
msg_hugeclust_err:      db 'Clusters larger than 16K not supported', 0Dh, 0Ah, '$'
msg_read_err:           db 'Boot sector read failed', 0Dh, 0Ah, '$'
msg_write_err:          db 'Disk write failed', 0Dh, 0Ah, '$'

               section .data
               align 4, db 0
BootSector:     incbin "bootsect.bin"
LDLinuxSYS:     incbin "ldlinux.sys"
ldlinux_size:   equ $-LDLinuxSYS

               section .bss
               alignb 4
SectorBuffer:   resb 512