; -*- fundamental -*- (asm-mode sucks)
; $Id: copybs.asm,v 1.4 2004/12/14 23:03:28 hpa Exp $
; -----------------------------------------------------------------------
;   
;   Copyright 1998-2004 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., 53 Temple Place Ste 330,
;   Boston MA 02111-1307, USA; either version 2 of the License, or
;   (at your option) any later version; incorporated herein by reference.
;
; -----------------------------------------------------------------------

;
; copybs.asm
;
; Small DOS program to copy the boot sector from a drive
; to a file
;
; Usage: copybs <drive>: <file>
;

		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 100h			; .COM format
_start:
		mov ax,3000h			; Get DOS version
		int 21h
		xchg al,ah
		mov [DOSVersion],ax
		cmp ax,0200h			; DOS 2.00 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
		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; now we should have at least one whitespace
; followed by a filename
;
got_colon:	jcxz bad_usage
		lodsb
		dec cx
		cmp al,' '
		ja bad_usage

skipspace:	jcxz bad_usage
		lodsb
		dec cx
		cmp al,' '
		jbe skipspace

		mov di,FileName
copyfile:	stosb
		jcxz got_cmdline
		lodsb
		dec cx
		cmp al,' '
		ja copyfile
		jmp short got_cmdline
		
;
; 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: copybs <drive>: <filename>', 0Dh, 0Ah, '$'

		section .bss
		alignb 4
FileName	resb 256

;
; Parsed the command line OK.  Get device parameter block to get the
; sector size.
;
		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

		section .bss
		alignb 2
SectorSize	resw 1

		section .text
got_cmdline:
		xor al,al			; Zero-terminate filename
		stosb

		mov dl,[DriveNo]
		inc dl				; 1-based
		mov ah,32h
		int 21h				; Get Drive Parameter Block
		
		and al,al
		jnz filesystem_error

		mov dx,[bx+dpbSectorSize]	; Save sector size
;
; Read the boot sector.
;
		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

		mov [SectorSize],dx		; Saved sector size from above

		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 [diBuffer+2],ax		; == DS
		mov bx,DISKIO
		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

;
; Open the file and write the boot sector to the file.
;
		mov dx,FileName
		mov cx,0020h			; Attribute = ARCHIVE
		mov ah,3Ch			; Create file
		int 21h
		jc file_write_error

		mov bx,ax
		push ax				; Handle

		mov cx,[SectorSize]
		mov dx,SectorBuffer
		mov ah,40h			; Write file
		int 21h
		jc file_write_error
		cmp ax,[SectorSize]
		jne file_write_error

		pop bx				; Handle
		mov ah,3Eh			; Close file
		int 21h
		jc file_write_error
;
; We're done!
;
		mov ax,4C00h			; exit(0)
		int 21h

;
; Error routine jump
;
filesystem_error:
		mov dx,msg_filesystem_err
		jmp short die
disk_read_error:
		mov dx,msg_read_err
		jmp short die
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

		section .data
msg_error:		db 'ERROR: $'
msg_ancient_err:	db 'DOS version 2.00 or later required', 0Dh, 0Ah, '$'
msg_filesystem_err:	db 'Filesystem not found on disk', 0Dh, 0Ah, '$'
msg_read_err:		db 'Boot sector read failed', 0Dh, 0Ah, '$'
msg_write_err:		db 'File write failed', 0Dh, 0Ah, '$'

		section .bss
		alignb 4
SectorBuffer:	resb 4096
