view bootimg.asm @ 28:69c5556a61a6

add option _ZEROBYTES_MAX to limit single resb/times NASM has a hard limit of <= 7FFF_FFFFh bytes per resb or times. Values below this limit may also cause the NASM process to be killed when it runs out of memory. 4000_0000h (1 GiB) seems to be a reasonable default.
author C. Masloch <pushbx@ulukai.org>
date Fri, 30 Oct 2020 19:22:34 +0100
parents 3e6d92d02159
children ce23da97f859
line wrap: on
line source
%if 0

Create a boot image file (default: 1440 KiB diskette) in NASM
 2019, by C. Masloch

Usage of the works is permitted provided that this
instrument is retained with the works, so that any entity
that uses the works is notified of this instrument.

DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.

%endif

%if 0

_PAYLOADFILE must be defined to a list of strings and/or keywords.
Keywords are accepted only as (non-string) tokens. The available
keywords are:

::empty		Ignored. Can be used to create an image without files.

::chdir		Change into subdirectory. May be followed by a ..
		 (dotdot) keyword to back out of a directory.
		 Otherwise, the filename of the subdirectory to
		 create (relative to the current directory),
		 as a string, is expected in the next list item.

File and directory names are checked for duplicates. If a
string like ::chdir,'foo',::chdir,..,::chdir,'foo' is used,
the subdirectory will be detected as a duplicate. That is,
it is not valid to create/enter the same subdirectory twice.

The order of the files' directory entries and cluster chains in
the file system is determined by the order they were specified
in the list. Particularly, in FAT12 and FAT16 images, the first
two files specified will be placed consecutively at the start
of the data area, which may be necessary for some loaders.
As this is not known to be required by any FAT32 loaders,
the FAT32 root directory counts as the first file.

Files and directories are always written into a single run of
clusters each, that is, there is no fragmentation.

Some things are not supported yet:

* File dates and times.

* Attributes other than the directory bit.

* A volume label directory entry.

* Automatic choice of a volume serial number.

* Long file names.

* Initialise the FSINFO sector entries for FAT32.

* Backup boot sectors for FAT32.

* Boot sector loaders that are not exactly 512 bytes long.

* Automatic calculation of some parameters such as SPF.


A directory is always terminated with one or more directory
entries that are filled with all-zeros.

To specify the _PAYLOADFILE list to NASM from a bash shell,
the quoting must be doubled, eg -D_PAYLOADFILE="'file.bin'"
(The outer quotes are parsed and swallowed by bash, while
the inner quotes are passed verbatim to NASM.)

If _BOOTFILE is specified (not the empty quoted string),
it is expected that the (E)BPB of that boot sector matches
the file system parameters specified to the bootimg.asm file.
If _BOOTFILE equals "" then a proper (E)BPB is created,
along with a default loader (which aborts with an error).

%endif


%include "lmacros3.mac"

	defaulting
	sectalign off

	numdef SPI, 2880	; sectors per image
	numdef BPS, 512		; bytes per sector
	numdef SPC, 1		; sectors per cluster
	numdef SPF, 9		; sectors per FAT
	numdef BPE, 12		; bits per entry
	numdef FSINFO, 1	; (only if FAT32) include an FSINFO sector
	numdef NUMFATS, 2	; number of FATs
	numdef NUMROOT, 224	; number of root directory entries
	numdef NUMRESERVED, 1	; number of reserved sectors
%if _BPE == 32 && _FSINFO
	numdef NUMRESERVED, 8	; number of reserved sectors
%endif
	numdef MEDIAID, 0F0h	; media ID
	numdef EOF, 15		; suffix of EOF entry marker

	numdef UNIT, 0		; load unit in BPB
	numdef CHS_SECTORS, 18	; CHS geometry field for sectors
	numdef CHS_HEADS, 2	; CHS geometry field for heads
	numdef HIDDEN, 0	; number of hidden sectors

	strdef BOOTFILE, ""
	gendef PAYLOADFILE, ""
	gendef MAP, ""

	strdef OEM_NAME,	"    lDOS"
	strdef OEM_NAME_FILL,	'_'
	strdef DEFAULT_LABEL,	"NO NAME"
	numdef VOLUMEID,	0

	numdef MBR,			0
	gendef MBR_PART_TYPE,		fat12
	numdef MBR_DOSEMU_IMAGE_HEADER,	0
	numdef MBR_GAP_SIZE_SECTORS,	0, 2048
	numdef MBR_GAP_SIZE_CYLINDERS,	1
	numdef MBR_ADD_GAP_TO_HIDDEN,	1


	numdef ZEROBYTES_RESB,		1
	numdef ZEROBYTES_MAX,		4000_0000h
		; Using the resb trick to zero takes about 1/40th
		;  the time of using times (for a 512 MiB image).
%if _ZEROBYTES_RESB
	%imacro zerobytes 1.nolist
[warning -zeroing]
 %rep (%1) / (_ZEROBYTES_MAX)
	resb (_ZEROBYTES_MAX)
 %endrep
	resb (%1) % (_ZEROBYTES_MAX)
[warning *zeroing]
	%endmacro
%else
	%imacro zerobytes 1.nolist
 %rep (%1) / (_ZEROBYTES_MAX)
	times (_ZEROBYTES_MAX) db 0
 %endrep
	times (%1) % (_ZEROBYTES_MAX) db 0
	%endmacro
%endif

ptEmpty:		equ 0
ptFAT12:		equ 1
ptFAT16_16BIT_CHS:	equ 4
ptExtendedCHS:		equ 5
ptFAT16_CHS:		equ 6
ptFAT32_CHS:		equ 0Bh
ptFAT32:		equ 0Ch
ptFAT16:		equ 0Eh
ptExtended:		equ 0Fh
ptLinux:		equ 83h
ptExtendedLinux:	equ 85h

%if _MBR
 %if _MBR_GAP_SIZE_SECTORS == 0
  %if _MBR_GAP_SIZE_CYLINDERS == 0
   %error Invalid gap size specified
  %else
   %assign _MBR_GAP_SIZE_SECTORS _MBR_GAP_SIZE_CYLINDERS * _CHS_HEADS * _CHS_SECTORS
  %endif
 %endif
 %if _MBR_ADD_GAP_TO_HIDDEN
  %assign _HIDDEN _HIDDEN + _MBR_GAP_SIZE_SECTORS
 %endif
 %ifidni _MBR_PART_TYPE,	fat12
  %assign _MBR_PART_TYPE	ptFAT12
 %elifidni _MBR_PART_TYPE,	fat16_16bit_chs
  %assign _MBR_PART_TYPE	ptFAT16_16BIT_CHS
 %elifidni _MBR_PART_TYPE,	fat16_chs
  %assign _MBR_PART_TYPE	ptFAT16_CHS
 %elifidni _MBR_PART_TYPE,	fat32_chs
  %assign _MBR_PART_TYPE	ptFAT32_CHS
 %elifidni _MBR_PART_TYPE,	fat32
  %assign _MBR_PART_TYPE	ptFAT32
 %elifidni _MBR_PART_TYPE,	fat16
  %assign _MBR_PART_TYPE	ptFAT16
 %endif
 %assign _MBR_PART_TYPE _MBR_PART_TYPE
 %ifnnum _MBR_PART_TYPE
  %error Invalid partition type specified
 %endif
 %if _MBR_PART_TYPE == 0
  %error Invalid partition type specified
 %endif
%endif


%ifnidn _MAP, ""
 [map all _MAP]
%endif

%ifidn _PAYLOADFILE, ""
  %error No payload files specified!
%endif


%if _BPE == 12
%elif _BPE == 16
%elif _BPE == 32
%else
 %error Invalid BPE (_BPE)
%endif

%if _BPE != 32
 %assign ROOTSECTORS (_NUMROOT * 32 + _BPS - 1) / _BPS
%else
 %assign ROOTSECTORS 0
%endif
%assign FATSECTORS _NUMFATS * _SPF
%assign DATASECTORS (_SPI - _NUMRESERVED - FATSECTORS - ROOTSECTORS)
%assign DATACLUSTERS (DATASECTORS / _SPC)
%assign NUMENTRIES _SPF * _BPS * 8 / _BPE
%if NUMENTRIES < (DATACLUSTERS + 2)
 %error Too few FAT sectors specified
%endif
%if (DATACLUSTERS + 2) > 0FF0h
 %if _BPE == 12
  %error FAT would be detected as FAT16
 %endif
%endif
%if (DATACLUSTERS + 2) < 1000h
 %if _BPE == 16
  %error FAT would be detected as FAT12
 %endif
%endif


%if _EOF < 8 || _EOF > 15
 %error Invalid EOF value (_EOF)
%endif

%if _BPE != 32
 %assign MAXENTRY (1 << _BPE) - 1
%else
 %assign MAXENTRY (1 << 28) - 1
%endif
%assign EOFSTARTENTRY MAXENTRY - 7
%assign EOFENDENTRY MAXENTRY
%assign BADENTRY MAXENTRY - 8
%assign EOFENTRY MAXENTRY - 15 + _EOF


	struc PARTINFO_CHS_TUPLE
pictHead:		resb 1
pictSectorLow6:
pictCylinderHigh2:	resb 1
pictCylinderLow8:	resb 1
	endstruc

	%macro chs_tuple 1.nolist
%assign %%sector (%1) % _CHS_SECTORS
%assign %%i (%1) / _CHS_SECTORS
%assign %%head %%i % _CHS_HEADS
%assign %%cylinder %%i / _CHS_HEADS
%if %%cylinder >= 1024
 %assign %%cylinder 1023
%endif
		istruc PARTINFO_CHS_TUPLE
at pictHead,		db %%head
at pictSectorLow6
at pictCylinderHigh2,	db (%%sector + 1) | ((%%cylinder >> (8 - 6)) & 0C0h)
at pictCylinderLow8,	db (%%cylinder & 0FFh)
		iend
	%endmacro

	struc PARTINFO
piBoot:		resb 1
piStartCHS:	resb 3		; PARTINFO_CHS_TUPLE
piType:		resb 1
piEndCHS:	resb 3		; PARTINFO_CHS_TUPLE
piStart:	resd 1
piLength:	resd 1
	endstruc

	struc DIRENTRY
deName:		resb 8
deExt:		resb 3
deAttrib:	resb 1
		resb 8
deClusterHigh:	resw 1
deTime:		resw 1
deDate:		resw 1
deClusterLow:	resw 1
deSize:		resd 1
	endstruc

ATTR_READONLY	equ 1
ATTR_HIDDEN	equ 2
ATTR_SYSTEM	equ 4
ATTR_VOLLABEL	equ 8
ATTR_DIRECTORY	equ 10h
ATTR_ARCHIVE	equ 20h

	struc DOSEMU_IMAGE_HEADER
; This structure is parsed when determining the geometry for
;  a hard disk image. It is documented in the dosemu2 sources at:
; https://github.com/dosemu2/dosemu2/blob/f41c0f267fec/src/include/disks.h#L132
dihSig:		resb 7		; "DOSEMU",0 or 0Eh,"DEXE"
dihCHSHeads:	resd 1		; (note the misalignment for these)
dihCHSSectors:	resd 1
dihCHSCylinders:resd 1
dihHeaderSize:	resd 1
dihDummy:	resb 1
dihDEXEFlags:	resd 1
	endstruc
dih_our_size:	equ 8192	; (16 * 512)


%define STARTSFOLLOWS start=0

%if _MBR
CYLINDERS equ (_MBR_GAP_SIZE_SECTORS + _SPI + _CHS_HEADS * _CHS_SECTORS - 1) \
	/ (_CHS_HEADS * _CHS_SECTORS)


 %if _MBR_DOSEMU_IMAGE_HEADER
	addsection dosemu_image_header, STARTSFOLLOWS
  %define STARTSFOLLOWS follows=dosemu_image_header
	istruc DOSEMU_IMAGE_HEADER
at dihSig,		asciz "DOSEMU"
at dihCHSHeads,		dd _CHS_HEADS
at dihCHSSectors,	dd _CHS_SECTORS
at dihCHSCylinders,	dd CYLINDERS
at dihHeaderSize,	dd dih_our_size
	iend
	times dih_our_size - ($ - $$) db 0
 %endif


	addsection mbr, STARTSFOLLOWS vstart=7C00h
 %define STARTSFOLLOWS follows=mbr

mbr_start:
	cli
	cld
	xor ax, ax
	mov es, ax
	mov ds, ax
	mov ss, ax
	mov sp, 7C00h
	sti
	mov si, mbr_message
@@:
	lodsb
	test al, al
	jz @F
	mov ah, 0Eh
	mov bh, [462h]
	mov bl, 7
	int 10h
	jmp @B

@@:
	xor ax, ax
	int 13h
	xor ax, ax
	int 16h
	int 19h

mbr_message:
	db "Unable to boot, MBR loader not written.",13,10
	db 13,10
	db "Press any key to reboot.",13,10
	db 0

	times (512 - 2 - 4 * 16) - ($ - $$) db 0

%assign TOTAL_MBR_SECTORS CYLINDERS * _CHS_HEADS * _CHS_SECTORS
mbr_partition_table:
	istruc PARTINFO
at piBoot,	db 80h			; active primary partition
at piStartCHS
		chs_tuple _MBR_GAP_SIZE_SECTORS
at piType,	db _MBR_PART_TYPE
at piEndCHS
		chs_tuple (TOTAL_MBR_SECTORS - 1)
at piStart,	dd _MBR_GAP_SIZE_SECTORS
at piLength,	dd TOTAL_MBR_SECTORS - _MBR_GAP_SIZE_SECTORS
	iend

	times (512 - 2) - ($ - $$) db 0
mbr_signature:
	dw 0AA55h
	align _BPS, db 0


	addsection gap, STARTSFOLLOWS
 %define STARTSFOLLOWS follows=gap

	zerobytes (_MBR_GAP_SIZE_SECTORS - 1) * _BPS
%endif


	addsection boot, STARTSFOLLOWS vstart=7C00h
boot_start:
%ifnidn _BOOTFILE, ""
	incbin _BOOTFILE
%else
	jmp strict short boot_after_bpb
	nop

oem_id:			; offset 03h (03) - not used by this code
	fill 8,_OEM_NAME_FILL,db _OEM_NAME
bytes_per_sector:	; offset 0Bh (11)
	dw _BPS
sectors_per_cluster:	; offset 0Dh (13)
	db _SPC
num_reserved_sectors:	; offset 0Eh (14)
	dw _NUMRESERVED
num_fats:		; offset 10h (16)
	db _NUMFATS
num_root_dir_ents:	; offset 11h (17)
%if _BPE != 32
	dw _NUMROOT
%else
	dw 0
%endif
total_sectors:		; offset 13h (19) - not used by this code
%if _SPI < 1_0000h
	dw _SPI		; (= 2880)
%else
	dw 0
%endif
media_id:		; offset 15h (21) - not used by this code
	db _MEDIAID
sectors_per_fat:	; offset 16h (22)
%if _BPE != 32
	dw _SPF
%else
	dw 0
%endif
sectors_per_track:	; offset 18h (24)
	dw _CHS_SECTORS
heads:			; offset 1Ah (26)
	dw _CHS_HEADS
hidden_sectors:		; offset 1Ch (28)
	dd _HIDDEN
total_sectors_large:	; offset 20h (32) - not used by this code
	dd _SPI

; Extended BPB		; offset 24h (36)

%if _BPE == 32
sectors_per_fat_large:	; offset 24h (36)
	dd _SPF
fsflags:		; offset 28h (40)
	dw 0
fsversion:		; offset 2Ah (42)
	dw 0
root_cluster:		; offset 2Ch (44)
	dd rootcluster
fsinfo_sector:		; offset 30h (48)
%if _FSINFO
	dw 1
%else
	dw 0
%endif
backup_sector:		; offset 32h (50)
	dw 0

	times 12 db 0	; offset 34h (52) reserved

; Extended BPB		; offset 40h (64)
%endif

boot_unit:		db _UNIT
			db 0
ext_bpb_signature:	db 29h
serial_number:		dd _VOLUMEID
volume_label:		fill 11,32,db _DEFAULT_LABEL
filesystem_identifier:
%if _BPE == 12
	fill 8,32,db "FAT12"
%elif _BPE == 16
	fill 8,32,db "FAT16"
%elif _BPE == 32
	fill 8,32,db "FAT32"
%else
 %error Invalid BPE
%endif


boot_after_bpb:
	cli
	cld
	xor ax, ax
	mov es, ax
	mov ds, ax
	mov ss, ax
	mov sp, 7C00h
	sti
	mov si, boot_message
@@:
	lodsb
	test al, al
	jz @F
	mov ah, 0Eh
	mov bh, [462h]
	mov bl, 7
	int 10h
	jmp @B

@@:
	xor ax, ax
	int 13h
	xor ax, ax
	int 16h
	int 19h

boot_message:
	db "Unable to boot, loader not written.",13,10
	db 13,10
	db "Press any key to reboot.",13,10
	db 0

	times 508 - ($ - $$) db 0
	dw 0
	dw 0AA55h
%endif
boot_end:

%if (boot_end - boot_start) != 512
 %error Boot file has wrong size
%endif
	align _BPS, db 0

%if _BPE == 32 && _FSINFO
	struc FSINFO	; FAT32 FSINFO sector layout
.signature1:	resd 1		; 41615252h ("RRaA") for valid FSINFO
.reserved1:			; former unused, initialized to zero by FORMAT
.fsiboot:	resb 480	; now used for FSIBOOT
.signature2:	resd 1		; 61417272h ("rrAa") for valid FSINFO
.numberfree:	resd 1		; FSINFO: number of free clusters or -1
.nextfree:	resd 1		; FSINFO: first free cluster or -1
.reserved2:	resd 3		; unused, initialized to zero by FORMAT
.signature3:	resd 1		; AA550000h for valid FSINFO or FSIBOOT
	endstruc

fsinfo:
	istruc FSINFO
at FSINFO.signature1
	dd "RRaA"
at FSINFO.reserved1
	_fill 480 + 4, 0, fsinfo
at FSINFO.signature2
	dd "rrAa"
at FSINFO.numberfree
	dd -1		; number of free clusters
at FSINFO.nextfree
	dd -1		; first free cluster
at FSINFO.reserved2
	times 3 dd 0
at FSINFO.signature3
	dd 0_AA55_0000h
	iend
 %if _BPS > 512
	times (_BPS - 512 - 4) db 0
	dd 0_AA55_0000h
 %endif
%endif

	zerobytes (_NUMRESERVED * _BPS) - ($ - $$)


	addsection fat, follows=boot align=_BPS


		; %1 = name for the equ to receive the start cluster,
		;	_start_cluster will be appended
		; %2 = how many clusters/entries to allocate
		;	(if zero, %1_start_cluster equ will be zero)
	%macro addfatchain 2.nolist
%xdefine FATCHAINS FATCHAINS,%1,%2
	%endmacro
%define FATCHAINS secondentry,1

		; %1 = numeric value to put into next entry
	%macro addfatentry 1.nolist
%xdefine FATENTRIES FATENTRIES,%1
	%endmacro


	%macro expandfatchains 2-*.nolist
%assign FATENTRIES (_MEDIAID | (MAXENTRY - 15))
				; first entry has media ID byte
%assign ii 1			; FATENTRIES has entry at index 0,
				;  next is index 1
%rep %0 / 2			; for each pair
 %if %2
  %1_start_cluster equ ii	; equ for start cluster
  %rep %2 - 1			; as many as are non-EOF entries
   %assign ii ii + 1
   addfatentry ii		; link to next cluster
  %endrep
  %assign ii ii + 1
  addfatentry EOFENTRY		; EOF entry
 %else
  %1_start_cluster equ 0	; empty chain, start cluster is 0
 %endif
 %rotate 2			; rotate to next pair
%endrep
	%endmacro


	addsection fixed_root, follows=fat align=_BPS


		; %1 = directory name ("" if is root)
		; %2 = data section name to use
		; %3 = data section name of dir to write to (none if is root)
		; %4 = data cluster number of parent dir (0 if root is parent)
	%macro incdir 4.nolist
	usesection %2
%2 %+ _start:
%ifnidni %1, ""
	istruc DIRENTRY
at deName,		fill 8,32,db "."
at deExt,		fill 3,32,db 32
at deAttrib,		db ATTR_DIRECTORY
at deClusterHigh,	dw %%file_start_cluster >> 16
at deClusterLow,	dw %%file_start_cluster & 0FFFFh
at deSize,		dd 0
	iend
	istruc DIRENTRY
at deName,		fill 8,32,db ".."
at deExt,		fill 3,32,db 32
at deAttrib,		db ATTR_DIRECTORY
at deClusterHigh,	dw %4 >> 16
at deClusterLow,	dw %4 & 0FFFFh
at deSize,		dd 0
	iend
%endif

%xdefine CHAINLIST CHAINLIST,%%file

%ifnidni %3, none

%define string %1
%ifnstr string
 %defstr string string
%endif
	parsename %1, 1

	usesection %3
	istruc DIRENTRY
at deName,		fill 8,32,db %$$name
at deExt,		fill 3,32,db %$$ext
at deAttrib,		db ATTR_DIRECTORY
at deClusterHigh,	dw %%file_start_cluster >> 16
at deClusterLow,	dw %%file_start_cluster & 0FFFFh
at deSize,		dd 0
	iend
%else
 rootcluster equ %%file_start_cluster
%endif
%2 %+ _start_cluster equ %%file_start_cluster
	%endmacro


		; %1 = filename, may include a directory
		;	(the directory is only used to locate
		;	the file on the host system, the file
		;	will be written to the image with only
		;	its base name used, and in all-caps.)
		; %2 = data section name to use
		; %3 = data section name of directory to write to
	%macro incfile 3.nolist
	usesection %2
%2 %+ _start:
%define string %1
%ifnstr string
 %defstr string string
%endif
incbin string
%assign filesize ($ - %2 %+ _start)

%xdefine CHAINLIST CHAINLIST,%%file

	parsename %1

	usesection %3
	istruc DIRENTRY
at deName,		fill 8,32,db %$$name
at deExt,		fill 3,32,db %$$ext
at deClusterHigh,	dw %%file_start_cluster >> 16
at deClusterLow,	dw %%file_start_cluster & 0FFFFh
at deSize,		dd filesize
	iend
	%endmacro

		; %1 = filename, with directory as on host side
		; string = stringified filename, still with host path
		; %2 = if slash disallowed in pathname
		; returns result in %$name, %$ext, and %$namelist
	%macro parsename 1-2.nolist 0
%strlen length string
%assign ii 1
%rep length
 %substr cc string length - ii + 1
 %ifidn cc,"/"
  %if %2
   %error Subsubdirectory not supported, use ::chdir twice
  %endif
  %substr string string length -ii + 2, -1
  %exitrep
 %endif
 %ifidn cc,"\"
  %if %2
   %error Subsubdirectory not supported, use ::chdir twice
  %endif
  %substr string string length -ii + 2, -1
  %exitrep
 %endif
 %assign ii ii + 1
%endrep
%strlen length string
%assign ii 1
%define %$name ""
%define %$ext ""
%assign dotyet 0
%rep length
 %substr cc string ii
 %assign ii ii + 1
 %if cc >= 'a' && cc <= 'z'
  %substr cc "ABCDEFGHIJKLMNOPQRSTUVWXYZ" (cc - 'a' + 1)
 %endif
 %ifn dotyet
  %ifidn cc,"."
   %assign dotyet 1
  %else
   %strlen ll %$name
   %if ll >= 8
    %error Too long name part in %1
    %exitrep
   %endif
   checkchar %1,cc
   %strcat %$name %$name,cc
  %endif
 %else
  %strlen ll %$ext
  %if ll >= 3
   %error Too long ext part in %1
   %exitrep
  %else
   checkchar %1,cc,"."
   %strcat %$ext %$ext,cc
  %endif
 %endif
%endrep
%ifidn %$name,""
 %error Invalid empty name part in %1
%endif

	addnametonamelist %$namelist
	%endmacro

	%macro checkchar 2-3.nolist 0
%if %2 <= ' ' || %2 >= 128 || \
	%2 == '/' || %2 == '\' || \
	%2 == '"' || %2 == %3
 %error Invalid character (%2) in name (%1)
%endif
	%endmacro

		; %$name = (string) filename part, 1 to 8 characters, all caps
		; %$ext = (string) file extension part, 0 to 3 characters, all caps
		; %3 = first saved name part in name list
		; %4 = first saved extension part in name list
		; %5, %6 = next filename in name list
		; %$namelist = name list (pairs of name parts, extension parts)
	%macro addnametonamelist 2-*.nolist
%ifnidn %1, ""
 %error Expected first list entry to be empty
%elifnidn %2, ""
 %error Expected first list entry to be empty
%elif %0 & 1
 %error Expected list to contain even number of entries
%endif
%rotate 2
%rep (%0 - 2) / 2
 %ifidn %1, %$name
  %ifidn %2, %$ext
   %error Duplicate filename %$name %+ . %+ %$ext in directory %$dirname
   %exitrep
  %endif
 %endif
 %rotate 2
%endrep
%xdefine %$namelist %$namelist, %$name, %$ext
	%endmacro


	%macro setup_a_section 0.nolist
	addsection data %+ filescount, follows= %+ ff vstart=0
  %xdefine ff data %+ filescount
  %assign filescount filescount + 1
	%endmacro

	%macro count_files_setup_sections 1-*.nolist
%assign filescount 0
%define ff fixed_root
%assign chdir 0
%rep %0
 %if chdir
  %ifnidni %1, ..		; (not if backing out of directory)
	setup_a_section		; add a section for a directory
  %endif
  %assign chdir 0
 %elifidni %1, ::chdir
  %assign chdir 1		; note next list entry is directory name
 %elifnidni %1, ::empty
	setup_a_section		; add a section for a file
 %endif
 %rotate 1
%endrep
%if chdir
 %error No directory to create specified
%endif
%if _BPE == 32
	setup_a_section		; add a section for root directory
%endif
	addsection empty, follows= %+ ff vstart=0
	%endmacro
	count_files_setup_sections _PAYLOADFILE


%define DIRLIST none
%define CHAINLIST none
	%macro multiincfile 1-*.nolist
%assign chainindex 0
%assign chdir 0
%push ROOT
%define %$namelist "", ""
%define %$dirname "/"
%if _BPE == 32
	incdir "", data %+ chainindex, none, 0
 %xdefine nextdir data %+ chainindex
 %xdefine DIRLIST DIRLIST,nextdir
 %xdefine %$parent nextdir
; %xdefine %$parentcluster %$parent %+ _start_cluster
 %define %$parentcluster 0
 %assign chainindex chainindex + 1
%else
 %define %$parent fixed_root
 %define %$parentcluster 0
%endif
%rep %0
 %if chdir
  %ifidni %1, ..
   %ifctx ROOT
    %error Cannot back out over root
   %else
    %pop
   %endif
  %else
	incdir %1, data %+ chainindex, %$parent, %$parentcluster
   %push SUB
   %define %$namelist "", ""
   %strcat %$dirname %$$dirname, %$$name, ".", %$$ext, "/"
   %xdefine nextdir data %+ chainindex
   %xdefine DIRLIST DIRLIST,nextdir
   %xdefine %$parent nextdir
   %xdefine %$parentcluster %$parent %+ _start_cluster
   %assign chainindex chainindex + 1
  %endif
  %assign chdir 0
 %elifidni %1, ::chdir
  %assign chdir 1
 %elifnidni %1, ::empty
	incfile %1, data %+ chainindex, %$parent
  %assign chainindex chainindex + 1
 %endif
 %rotate 1
%endrep
%if chdir
 %error No directory to create specified
%endif
%rep 1024
 %ifctx ROOT
  %exitrep
 %else
  %pop
 %endif
%endrep
%pop
	%endmacro
	multiincfile _PAYLOADFILE


	%macro extenddirectories 1-*.nolist
%ifnidni %1, none
 %error Expected a none entry to start dir list
%endif
%rotate 1
%rep %0 - 1
	usesection %1
	times 32 db 0
		; Insure at least one entry. Makes things
		;  easier to handle for the loader too.
		;  (Can depend on a zero-filled entry
		;  that terminates the directory.)
 %rotate 1
%endrep
	%endmacro

	extenddirectories DIRLIST


	%macro allocatechains 1-*.nolist
%ifnidni %1, none
 %error Expected a none entry to start chain list
%endif
%rotate 1
%assign ii 0
%rep %0 - 1
	usesection data %+ ii
	align _BPS * _SPC, db 0
 %assign clusters ($ - data %+ ii %+ _start) / (_BPS * _SPC)
	addfatchain %1,clusters
 %assign ii ii + 1
 %rotate 1
%endrep
	%endmacro

	allocatechains CHAINLIST


%assign data_used 0
%assign ii 0
%rep filescount
	usesection data %+ ii
	align _BPS * _SPC, db 0
data %+ ii %+ _end:
 %assign data_used data_used \
	+ (data %+ ii %+ _end - data %+ ii %+ _start)
 %assign ii ii + 1
%endrep


	usesection fixed_root
%if _BPE != 32
	times 32 db 0
%endif
	zerobytes ROOTSECTORS * _BPS - ($ - $$)


	usesection empty
	zerobytes DATASECTORS * _BPS - data_used


%if _MBR
	addsection partition_end_padding, follows=empty
	zerobytes (CYLINDERS * _CHS_HEADS * _CHS_SECTORS \
			- _MBR_GAP_SIZE_SECTORS - _SPI) \
		* _BPS
%endif


%if _BPE == 32
 %define dumpfatentries dd
%elif _BPE == 16
 %define dumpfatentries dw
%elif _BPE == 12
	%macro dumpfatentries 2-*.nolist
%if (%0 & 1) == 1
 %error Expected even number of entries
%endif
%rep %0 / 2
	db (%1) & 0FFh
	db ((%1) >> 8) \
		| (((%2) & 0Fh) << 4)
	db ((%2) >> 4)
 %rotate 2
%endrep
	%endmacro
%else
 %error Invalid bits per entry (_BPE)
%endif


	expandfatchains FATCHAINS


	%macro assigncluster 2-*.nolist
%assign cluster %0
	%endmacro

	assigncluster FATENTRIES
%if _BPE == 12 && (cluster & 1) == 1
	addfatentry 0
%endif


	usesection fat
%assign ii 0
%rep _NUMFATS
fat%[ii]:
	dumpfatentries FATENTRIES
	zerobytes _SPF * _BPS - ($ - fat%[ii])
%assign ii ii+1
%endrep
%if FATSECTORS * _BPS - ($ - $$)
 %error Internal error in FAT filling
%endif