; sector.asm
;
; writes a boot sector out to the floppy disk in the A: drive
;
; The input file is assumed to be a binary file with the relevant code
; at offset 7C00h relative to the beginning of the file.  This is because
; when the sector loader transfers control to the boot sector, it's at
; address 0:7C00h.  Rather than clutter up the boot loader source with
; a bunch of ugly offsets, we simply use an ORG 7C00h instead and let
; the linker insert a bunch of empty space which this program skips over.
;
; Style Note:
;   There aren't any hardwired numbers in this code.  That is to say,
;   equates and macros are used to render gibberish like this:
;     mov ax,4c00h
;     int 33
;
;   into somewhat self-documenting code like this:
;     DosInt DOS_TERMINATE, 0
;
;   This is done to make the code more readable, and comprehensible, and
;   to aid in maintenance by not littering mysterious constants throughout
;   the code.  Please be kind to animals (specifically your fellow
;   programmers) and use this practice in your own code.
;
;

STACKSIZE        = 200h         ; this is how much stack we'll allocate

SECTORSIZE       = 200h         ; size of the boot sector on a floppy

CMD_LINE_LEN     = 80h          ; offset of the command line length (one byte)
                                ; relative to the beginning of the PSP.

CMD_LINE         = 81h          ; the offset relative to the beginning of the
                                ; PSP that is the start of the command line
                                ; arguments.


DOS_OPEN_HANDLE  = 03dh         ; open file
 READ_ONLY_DENY_NONE = 020h     ;  file open mode
DOS_MOVE_HANDLE  = 042h         ; move file pointer
 WHENCE_BEGIN    = 0            ;  move pointer relative to beginning
 WHENCE_CURRENT  = 1            ;  move pointer relative to current location
 WHENCE_EOF      = 2            ;  move pointer relative to end of file
DOS_READ_HANDLE  = 03fh         ; read from an open file handle
DOS_CLOSE_HANDLE = 03eh         ; close an open file handle
DOS_WRITE_HANDLE = 040h         ; write to open file
DOS_TERMINATE    = 04ch         ; terminate and exit

DOS_INT         = 021h

; various named character constants
NUL     = 0
CR      = 13
LF      = 10
SPACE   = ' '

GenericInt macro function, subfunction
        ifb <subfunction>
                mov     ah,function
        else
                mov     ax,(function SHL 8) OR (subfunction AND 0ffh)
        endif
endm

DosInt  macro function, subfunction
        GenericInt <function>,<subfunction>
        int     DOS_INT
endm

BDISK_WRITE_SECTOR      = 03h

BDISK_INT       = 013h

; constants unique to this program

FILE_OFFS_LO    = 7C00h         ;
FILE_OFFS_HI    = 0000h         ;

BOOT_DRIVE      = 0             ; we'll be writing to drive A:
BOOT_HEAD       = 0             ; head 0 is the boot head
BOOT_CYLSECT    = 0001h         ; a word value with the following format
                                ; bits 15-8     low bits of cylinder
                                ; bits 7-6      high two bits of cylinder
                                ; bits 5-0      sector
NUM_SECTORS     = 1             ; number of sector to write to disk

        model small
        .386
        .stack  STACKSIZE
        .code
;**********************************************************************
; program code start
;**********************************************************************
Start:
; parse the command line args
        mov     cl,byte ptr [DGROUP:CMD_LINE_LEN]  ; read the length byte
        ; NOTE that the command line length isn't really part of the
        ; DGROUP group, but DS currently points to the PSP, and if we
        ; omit the DGROUP override, the assembler thinks we're trying
        ; to load a constant instead of the contents of the memory loc.
        ; In other words, it's ugly but it has a purpose.
        or      cl,cl                   ; check for zero
        jz      Usage                   ; no command line args
        mov     si,CMD_LINE             ;
        mov     al,' '                  ;
        repe    cmpsb                   ; burn off leading spaces
        mov     dx,si                   ; save that starting point
        repne   cmpsb                   ; scan for next space (if any)
        cmp     byte ptr [si],SPACE     ; if it's > space char,
        ja      skip                    ; skip the nul termination
        mov     byte ptr [si],NUL       ; terminate with a NUL char
skip:
; first, open the file
        DosInt  DOS_OPEN_HANDLE, READ_ONLY_DENY_NONE
        mov     si,seg DGROUP           ;
        mov     ds,si                   ;
        mov     es,si                   ; point 'em all over there
        mov     si,offset err_fopen     ; can't open input file
        jc      ErrorExit
; the file's open, so move the file pointer to offset 7C00h
        mov     bx,ax                   ; fetch the file handle
        mov     cx,FILE_OFFS_HI
        mov     dx,FILE_OFFS_LO
        DosInt  DOS_MOVE_HANDLE, WHENCE_BEGIN
        mov     si,offset err_fmove     ;
        jc      ErrorExit               ;
; read the data
        mov     cx,SECTORSIZE           ; max number of bytes to read
        mov     dx,offset buffer        ; point ds:dx to buffer
        DosInt  DOS_READ_HANDLE         ;
        mov     si,offset err_fread     ;
        jc      ErrorExit               ;
; close the file
        DosInt  DOS_CLOSE_HANDLE        ; close this file

; now write it out to the floppy disk's boot sector
        mov     bx,offset buffer        ;
        mov     cx,BOOT_CYLSECT         ;
        mov     dx,(BOOT_HEAD SHL 8) OR (BOOT_DRIVE)
        GenericInt BDISK_WRITE_SECTOR, NUM_SECTORS
        int     BDISK_INT
        mov     si,offset err_write     ;
        jc      ErrorExit               ;
        mov     si,offset msg_ok        ;
ErrorExit:
        mov     cx,[si]                 ;
        inc     si                      ;
        inc     si                      ;
        mov     dx,si                   ;
        mov     bx,1                    ; write to stdout
        DosInt  DOS_WRITE_HANDLE        ; write to that file
        DosInt  DOS_TERMINATE, 0

Usage:
        mov     si,seg DGROUP           ;
        mov     ds,si                   ; load correct data segment
        mov     si,offset use_msg
        jmp     ErrorExit               ;


;**********************************************************************
; program data starts
;**********************************************************************
        .data
msgstruc macro msglabel, msgstring
        local alpha
msglabel dw      (alpha - $) - 2
         db      msgstring
alpha = $
endm

msgstruc err_fopen ,<"ERROR: couldn't open input file",CR,LF>
msgstruc err_fmove ,<"ERROR: unable to move file pointer",CR,LF>
msgstruc err_fread ,<"ERROR: couldn't read from input file",CR,LF>
msgstruc err_write ,<"ERROR: unable to write to floppy disk",CR,LF>
msgstruc msg_ok    ,<"Boot sector was successfully written to floppy",CR,LF>
msgstruc use_msg   ,<"Usage:  SECTOR infile.bin",CR,LF>

buffer  db      SECTORSIZE dup (?)    ; sector buffer
        end Start