%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 names and/or keywords. The filenames and keywords are separated by commas. Filenames are accepted as tokens or strings. A plain filename that does not belong to a preceeding keyword is taken as a file to add to the image in the image's current directory. The entire name is interpreted as a source pathname, passed to the incbin directive in full, which in turn searches NASM's working directory and the directories specified to NASM with -I switches. The last component of the name, after the last slash or backslash, is converted into allcaps and taken as the target filename. 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. ::fragment Followed by number of fragments, followed by length (in number of clusters) of each fragment. Note that if the file is shorter than the sum of fragment lengths plus 1 cluster then it will have NUL bytes appended so as to make sure the last cluster contains one byte, and all prior clusters are full. ::fragmentsame Followed by number of fragments, followed by one value that specifies the length of all the fragments. ::nextfragment Places next fragment of a fragmented file. At the end of the image all remaining fragments are placed. (The exact order that fragments appear in is not yet fixed; only the first fragment is certain.) ::nextfragments Followed by number of fragments to place. Places the next fragments of fragmented files. The number is allowed to be higher than the amount of fragments that remain to be placed; this is handled the same as if that exact amount was specified. ::rename Followed by source pathname, then a target filename. ::fill Followed by length, byte value, target filename. ::endfill Followed by length, byte value, target filename. Filename is written to the directory as usual. The FAT chain is allocated after all other non-::endfill files however. Additionally, if the byte value is zero, the chain is allocated after all non-zero-value ::endfill files. If _FULL=0 then the zero-value ::endfill data will not be written at all, only the FAT entries are allocated. If the value is given as 256 this is treated as zero but not handled specifically to make use of _FILL=0 like it is for the literal value 0. ::attrib Followed by attribute byte value. Sets what attributes to use for the following directory entries. ATTR_DIRECTORY and ATTR_VOLLABEL are invalid to use for this. The _ATTRIB def is used to set the initial default. The valid values are: ATTR_READONLY, ATTR_HIDDEN, ATTR_SYSTEM, ATTR_ARCHIVE, and any combination of those. ::date Followed by numeric date value, eg 20110308. The _FILEDATE def is used to set the initial default. If _USE_FILE_DATETIME is set, this defaults to NASM's __DATE_NUM__, else it defaults to 1980_00_00. ::time Followed by numeric time value, eg 113842. The _FILETIME def is used to set the initial default. If _USE_FILE_DATETIME is set, this defaults to NASM's __TIME_NUM__, else it defaults to 0. ::setdirdatetime Special case: Set root directory datetime to current setting, instead of using default _FILEDATE _FILETIME. (Only affects subdir dotdot entries.) 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: * A volume label directory entry. * Automatic choice of a volume serial number. * Long file names. * 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. If _BOOTPATCHFILE is specified (not the empty quoted string), then it is used with incbin to extract the boot loader's jump instruction (3 bytes) and the boot code that's to be placed after the BPBN (size depends on FAT type). The (E)BPB, including BPBN, is then created by this script to match the filesystem that is being written. Else, 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). If _BOOTJUMPFILE and _BOOTCODEFILE are specified but _BOOTFILE is not then these files are used to initialise the first 512 bytes of the boot sector(s), excepting the (E)BPB. The size of the jump file has to match 3 bytes or 11 bytes. The size of the code file has to match the amount of bytes remaining after the BPBN fields to fill up 512 bytes, or two bytes less (for an 0AA55h signature), or four bytes less (for an 0AA55_0000h signature). If the FAT type is FAT32 and _FSINFO is set, then the def _BOOTINFOFILE can be specified. If it is defined to the quoted string "::bootpatchfile" then the FSIBOOT loader is extracted from the file specified in _BOOTPATCHFILE, for 484 bytes that start at offset 512 in the file. It is expected that the first 4 bytes of this area contain the FSINFO signature "RRaA". Otherwise, if a file is specified as _BOOTINFOFILE (not the empty quoted string) that file must be exactly 484 bytes long. The first four bytes should again contain the FSINFO signature reading "RRaA". Otherwise the 480-bytes FSIBOOT area is zero-filled. === Examples Empty default (90mm 1440 KiB FAT12 diskette) image: nasm bootimg.asm -I ../lmacros/ -o bootimg.img -D_PAYLOADFILE=::empty A FAT16 image with some fragmented data files, and automatic calculation of a large enougn or too large FAT: nasm a.asm -o a.bin nasm a.asm -o b.bin -DCONTENT="'b'" nasm a.asm -o minus.bin -DCONTENT=-1 nasm bootimg.asm -o bootimg.img -I ../lmacros/ \ -D_BPE=16 -D_SPC=1 -D_SPI=$(( 1024 * 2 * 32 )) \ -D_SPF=$(( (1024 * 2 * 32 / 1 * 2 + 511) / 512 )) \ -D_NUMROOT=512 -D_ALIGNDATA \ -D_PAYLOADFILE=::fragmentsame,15,1,a.bin,\ ::fragmentsame,7,1,b.bin,\ ::nextfragments,1024,minus.bin lDOS boot tests can also specify a custom _PAYLOADFILE def: ./test.sh diskette -D_PAYLOADFILE="::fragmentsame,3,1,testwrit.sys,\ result.txt,::nextfragment,::chdir,dir,::nextfragment,::rename,result.txt,result2" A FAT32 image in an MBR partition, with a FAT32+FSIBOOT loader read at build time, and some files loaded into it: nasm bootimg.asm \ -D_WARN_DEFAULT_OFF=1 -D_WARN_TOOMANYFAT=0 -D_WARN_ALIGNDATA=0 \ -D_MBR -D_ALIGNDATA -D_CHS_HEADS=8 -D_CHS_SECTORS=8 \ -D_MBR_PART_TYPE=ptFAT32 \ -D_BPE=32 -D_SPC=1 -D_SPI=69632 -D_SPF=544 -D_NUMROOT=224 \ -o hdimage.img -l hdimage.lst \ -D_PAYLOADFILE=testwrit.sys,result.txt,::chdir,dir \ -D_BOOTPATCHFILE="'boot32tw.bin'" -D_UNIT=80h \ -I ../lmacros/ \ -D_BOOTINFOFILE=::bootpatchfile -D_MBRPATCHFILE=oldmbr.bin A FAT32 image with auto-calculated FAT size, depicting some uses of attributes and filetimes, wrapped into a dosemu2 image of an MBR-partitioned unit: nasm -D_ATTRIB=ATTR_SYSTEM -I ../lmacros/ \ bootimg.asm -D_BPS=512 \ -D_PAYLOADFILE=::empty,::rename,"'test.dat'",test.bin,\ ::time,030826,::setdirdatetime,::time,012638,\ ::attrib,ATTR_HIDDEN+ATTR_READONLY+ATTR_SYSTEM,\ "'test.dat'",::chdir,testdir,"'test.dat'" \ -D_BPE=32 -D_SPI="(34 * 2 * 1024)" \ -D_SPF="((_SPI / _SPC * _BPE / 8 + _BPS - 1) / _BPS)" \ -D_SPC=1 -D_CHS_HEADS=8 -D_CHS_SECTORS=8 \ -D_MBR -D_MBR_DOSEMU_IMAGE_HEADER \ -D_BOOTPATCHFILE="'boot32tw.bin'" -D_BOOTINFOFILE="'::bootpatchfile'" \ -D_USE_FILE_DATETIME=1 -o part.img && mdir -i part.img@@80s -a ::. %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 BACKUP, 1 ; (only if FAT32) include a boot sector backup numdef FSI_INIT_FREE, 1 ; initialise FSINFO amount free clusters numdef NUMFATS, 2 ; number of FATs numdef NUMROOT, 224 ; number of root directory entries %assign _SP512 (512 + _BPS - 1) / _BPS ; (512 + 32 - 1) / 32 = 16 ; (512 + 64 - 1) / 64 = 8 ; (512 + 512 - 1) / 512 = 1 ; (512 + 1024 - 1) / 1024 = 1 numdef NUMRESERVED, _SP512 ; number of reserved sectors %if _BPE == 32 && (_FSINFO || _BACKUP) numdef NUMRESERVED, _SP512 * 16 ; number of reserved sectors %endif numdef ALIGNDATA, 0, 16 ; align data to this sector boundary ; (done by increasing _NUMRESERVED) numdef FILLROOT, 1 numdef WARN_TOOMANYFAT, 1 numdef WARN_ALIGNDATA, 1 numdef WARN_FILLROOT, 1 numdef WARN_SMALL32, 1 numdef WARN_DEFAULT_OFF, 0 %if _WARN_DEFAULT_OFF numdef WARN_TOOMANYFAT, 0 numdef WARN_ALIGNDATA, 0 numdef WARN_FILLROOT, 0 numdef WARN_SMALL32, 0 %endif numdef ERROR_SMALL32, 1 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 BOOTPATCHFILE, "" strdef BOOTFILE, "" strdef BOOTJUMPFILE, "" strdef BOOTCODEFILE, "" strdef BOOTINFOFILE, "" gendef PAYLOADFILE, "" gendef MAP, "" strdef OEM_NAME, " lDOS" strdef OEM_NAME_FILL, '_' strdef DEFAULT_LABEL, "NO NAME" numdef VOLUMEID, 0 numdef ATTRIB, 0 numdef FILEDATE, 1980_00_00 numdef FILETIME, 0 numdef USE_FILE_DATETIME, 0 %if _USE_FILE_DATETIME numdef FILEDATE, __DATE_NUM__ numdef FILETIME, __TIME_NUM__ %endif numdef MBR, 0 strdef MBRPATCHFILE, "" strdef MBRCODEFILE, "" gendef MBR_PART_TYPE, fat %+ _BPE 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 MBR_ADD_GAP_TO_ALIGN, 1 numdef FULL, 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 %if %1 < 0 %error Negative count given to zerobytes %else [warning -zeroing] [warning -other] %rep (%1) / (_ZEROBYTES_MAX) resb (_ZEROBYTES_MAX) %endrep resb (%1) % (_ZEROBYTES_MAX) [warning *other] [warning *zeroing] %endif %endmacro %else %imacro zerobytes 1.nolist %if %1 < 0 %error Negative count given to zerobytes %else %rep (%1) / (_ZEROBYTES_MAX) times (_ZEROBYTES_MAX) db 0 %endrep times (%1) % (_ZEROBYTES_MAX) db 0 %endif %endmacro %endif numdef FILLBYTES_MAX, 4000_0000h %imacro fillbytes 2.nolist %ifn %2 zerobytes %1 %else %rep (%1) / (_FILLBYTES_MAX) times (_FILLBYTES_MAX) db %2 %endrep times (%1) % (_FILLBYTES_MAX) db %2 %endif %endmacro %imacro zerobytes_if_full 1.nolist %if _FULL zerobytes %1 %endif %endmacro 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 %if _FILLROOT %assign ii (_NUMROOT + _BPS / 32 - 1) & ~(_BPS / 32 - 1) %if _WARN_FILLROOT && ii != _NUMROOT %warning Expanding root to ii entries to fill last sector %endif %assign _NUMROOT ii %endif %assign ROOTSECTORS (_NUMROOT * 32 + _BPS - 1) / _BPS %else %assign ROOTSECTORS 0 %endif %assign FATSECTORS _NUMFATS * _SPF %if _ALIGNDATA %assign ii 0 %if _MBR && _MBR_ADD_GAP_TO_ALIGN %assign ii ii + _MBR_GAP_SIZE_SECTORS %endif %assign ii ii + _NUMRESERVED + ROOTSECTORS + FATSECTORS %assign ii (ii + (_ALIGNDATA)) % (_ALIGNDATA) %assign ii ((_ALIGNDATA) - ii) % (_ALIGNDATA) %if ii %if _WARN_ALIGNDATA %warning Adding ii reserved sectors for %[_ALIGNDATA]-sector alignment %endif %endif %assign _NUMRESERVED _NUMRESERVED + ii %endif %assign DATASECTORS (_SPI - _NUMRESERVED - FATSECTORS - ROOTSECTORS) %assign DATACLUSTERS (DATASECTORS / _SPC) %assign NUMENTRIES _SPF * _BPS * 8 / _BPE %assign NUMSECTORS _SPF %assign NUMNEEDED (DATACLUSTERS + 2) %assign SECNEEDED (NUMNEEDED * _BPE / 8 + _BPS - 1) / _BPS %if NUMENTRIES < NUMNEEDED %error Too few FAT sectors specified \ (NUMENTRIES entries (NUMSECTORS sectors), NUMNEEDED needed (SECNEEDED sectors)) %elif NUMENTRIES >= (NUMNEEDED + _BPS * 8 / _BPE) %if _WARN_TOOMANYFAT %warning Too many FAT sectors specified \ (NUMENTRIES entries (NUMSECTORS sectors), NUMNEEDED needed (SECNEEDED sectors)) %endif %endif %macro detectionerror 1-2.nolist 0 %assign len 8 %assign ii 0 %assign sum 0 %define string "" %rep len %assign shift ((len - 1 - ii) * 4) %assign digit (DATACLUSTERS >> shift) & 15 %assign sum sum + digit %if sum %substr digit "0123456789ABCDEF" digit + 1 %strcat string string,digit %endif %assign ii ii + 1 %endrep %ifn sum %define string "0" %endif %deftok string string %if %2 && !_ERROR_SMALL32 %if _WARN_SMALL32 %warning FAT would be detected %1 (%[DATACLUSTERS] = %[string]h clusters) %endif %else %error FAT would be detected %1 (%[DATACLUSTERS] = %[string]h clusters) %endif %endmacro %if (DATACLUSTERS + 2) > 0FFF0h %if _BPE == 12 || _BPE == 16 detectionerror as FAT32 %endif %elif (DATACLUSTERS + 2) > 0FF0h %if _BPE == 12 detectionerror as FAT16 %endif %endif %if (DATACLUSTERS + 2) < 1000h %if _BPE == 16 detectionerror as FAT12 %endif %if _BPE == 32 detectionerror as FAT12, 1 %endif %elif (DATACLUSTERS + 2) < 10000h %if _BPE == 32 detectionerror as FAT16, 1 %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 BS bsJump: resb 3 bsOEM: resb 8 bsBPB: endstruc struc EBPB ; BPB sec bpbBytesPerSector: resw 1 ; offset 00h 0Bh bpbSectorsPerCluster: resb 1 ; offset 02h 0Dh bpbReservedSectors: resw 1 ; offset 03h 0Eh bpbNumFATs: resb 1 ; offset 05h 10h bpbNumRootDirEnts: resw 1 ; offset 06h 11h -- 0 for FAT32 bpbTotalSectors: resw 1 ; offset 08h 13h bpbMediaID: resb 1 ; offset 0Ah 15h bpbSectorsPerFAT: resw 1 ; offset 0Bh 16h -- 0 for FAT32 bpbCHSSectors: resw 1 ; offset 0Dh 18h bpbCHSHeads: resw 1 ; offset 0Fh 1Ah bpbHiddenSectors: resd 1 ; offset 11h 1Ch bpbTotalSectorsLarge: resd 1 ; offset 15h 20h bpbNew: ; offset 19h 24h ebpbSectorsPerFATLarge: resd 1 ; offset 19h 24h ebpbFSFlags: resw 1 ; offset 1Dh 28h ebpbFSVersion: resw 1 ; offset 1Fh 2Ah ebpbRootCluster: resd 1 ; offset 21h 2Ch ebpbFSINFOSector: resw 1 ; offset 25h 30h ebpbBackupSector: resw 1 ; offset 27h 32h ebpbReserved: resb 12 ; offset 29h 34h ebpbNew: ; offset 35h 40h endstruc struc BPBN ; ofs B16 S16 B32 S32 bpbnBootUnit: resb 1 ; 00h 19h 24h 35h 40h resb 1 ; 01h 1Ah 25h 36h 41h bpbnExtBPBSignature: resb 1 ; 02h 1Bh 26h 37h 42h -- 29h for valid BPBN bpbnSerialNumber: resd 1 ; 03h 1Ch 27h 38h 43h bpbnVolumeLabel: resb 11 ; 07h 20h 2Bh 3Ch 47h bpbnFilesystemID: resb 8 ; 12h 2Bh 36h 47h 52h endstruc ; 1Ah 33h 3Eh 4Fh 5Ah 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) %imacro checkattrib 0 %if _ATTRIB & (ATTR_VOLLABEL | ATTR_DIRECTORY) %error Invalid attribute: _ATTRIB %endif %endmacro checkattrib %imacro convertdatetime 0 %push %assign %$year _FILEDATE / 1_00_00 %assign %$month _FILEDATE / 1_00 % 100 %assign %$day _FILEDATE % 100 %if %$year < 1980 %assign _FATFILEDATE 0 << 9 | 1 << 5 | 1 %elif (%$year - 1980) >= 128 %assign _FATFILEDATE 127 << 9 | 12 << 5 | 31 %else %assign _FATFILEDATE (%$year - 1980) << 9 | %$month << 5 | %$day %endif %assign %$hour _FILETIME / 1_00_00 %assign %$minute _FILETIME / 1_00 % 100 %assign %$second _FILETIME % 100 %assign _FATFILETIME %$hour << 11 | %$minute << 5 | %$second >> 1 %pop %endmacro convertdatetime %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: %ifnidn _MBRPATCHFILE, "" incbin _MBRPATCHFILE, 0, 512 - 2 - 4 * 16 %elifnidn _MBRCODEFILE, "" incbin _MBRCODEFILE %else 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 %endif 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 %imacro emit_boot 2 addsection %1_boot, STARTSFOLLOWS vstart=7C00h %define STARTSFOLLOWS follows=%1_boot %1_boot_start: %ifnidn _BOOTPATCHFILE, "" %assign USEBOOTFILE 0 %elifnidn _BOOTFILE, "" %assign USEBOOTFILE 1 %else %assign USEBOOTFILE 0 %endif %if USEBOOTFILE incbin _BOOTFILE %else %ifnidn _BOOTPATCHFILE, "" incbin _BOOTPATCHFILE, 0, 3 %elifnidn _BOOTJUMPFILE, "" incbin _BOOTJUMPFILE %else jmp strict short %1_boot_after_bpb nop %endif %if ($ - $$) == bsOEM %1_oem_id: ; offset 03h (03) fill 8,_OEM_NAME_FILL,db _OEM_NAME %endif %if ($ - $$) != bsBPB %error Invalid boot jump file length, must be 11 or 3 %endif %1_bytes_per_sector: ; offset 0Bh (11) dw _BPS %1_sectors_per_cluster: ; offset 0Dh (13) db _SPC & 255 %1_num_reserved_sectors:; offset 0Eh (14) dw _NUMRESERVED %1_num_fats: ; offset 10h (16) db _NUMFATS %1_num_root_dir_ents: ; offset 11h (17) %if _BPE != 32 dw _NUMROOT %else dw 0 %endif %1_total_sectors: ; offset 13h (19) %if _SPI < 1_0000h dw _SPI %else dw 0 %endif %1_media_id: ; offset 15h (21) db _MEDIAID %1_sectors_per_fat: ; offset 16h (22) %if _BPE != 32 dw _SPF %else dw 0 %endif %1_sectors_per_track: ; offset 18h (24) dw _CHS_SECTORS %1_heads: ; offset 1Ah (26) dw _CHS_HEADS %1_hidden_sectors: ; offset 1Ch (28) dd _HIDDEN %1_total_sectors_large: ; offset 20h (32) %if _SPI >= 1_0000h dd _SPI %else dd 0 %endif ; Extended BPB ; offset 24h (36) %if _BPE == 32 %1_sectors_per_fat_large: ; offset 24h (36) dd _SPF %1_fsflags: ; offset 28h (40) dw 0 %1_fsversion: ; offset 2Ah (42) dw 0 %1_root_cluster: ; offset 2Ch (44) dd rootcluster %1_fsinfo_sector: ; offset 30h (48) %if _FSINFO dw (%1_fsinfo - %1_boot_start) / _BPS %else dw 0 %endif %1_backup_sector: ; offset 32h (50) %if _BACKUP dw (_boot_end_2 - _boot_start) / _BPS %else dw 0 %endif times 12 db 0 ; offset 34h (52) reserved ; Extended BPB ; offset 40h (64) %endif %1_boot_unit: db _UNIT db 0 %1_ext_bpb_signature: db 29h %1_serial_number: dd _VOLUMEID %1_volume_label: fill 11,32,db _DEFAULT_LABEL %1_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 %1_boot_after_bpb: %ifnidn _BOOTPATCHFILE, "" incbin _BOOTPATCHFILE, ($ - $$), 512 - 2 - ($ - $$) %elifnidn _BOOTCODEFILE, "" incbin _BOOTCODEFILE %else cli cld xor ax, ax mov es, ax mov ds, ax mov ss, ax mov sp, 7C00h sti mov si, %1_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 %1_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 %endif %endif %if ($ - $$) == 508 dw 0 %endif %if ($ - $$) == 510 dw 0AA55h %endif %1_boot_end: %if (%1_boot_end - %1_boot_start) != 512 %ifnidn _BOOTFILE, "" %error Boot file has wrong size, must be 512, 510, or 508 %elifnidn _BOOTCODEFILE, "" %assign numberexpected1 512 - (%1_boot_after_bpb - $$) %assign numberexpected2 numberexpected1 - 2 %assign numberexpected3 numberexpected1 - 4 %error Boot code file has wrong size, must be numberexpected1, numberexpected2, or numberexpected3 %else %error Internal error, default boot sector loader has wrong size %endif %endif %if _BPS > 512 _fill _BPS - 4, 0, %1_boot_start dd 0AA55_0000h %endif %if _BPE == 32 && _FSINFO %1_fsinfo: istruc FSINFO %ifnidn _BOOTINFOFILE, "" %ifidni _BOOTINFOFILE, "::bootpatchfile" incbin _BOOTPATCHFILE, 512, 484 %else incbin _BOOTINFOFILE %endif %else at FSINFO.signature1 dd "RRaA" at FSINFO.reserved1 _fill 480 + 4, 0, %1_fsinfo %endif %if $ - %1_fsinfo != FSINFO.signature2 %error Boot info file has wrong size %endif at FSINFO.signature2 dd "rrAa" at FSINFO.numberfree %if _FSI_INIT_FREE && %2 dd DATACLUSTERS - USEDCLUSTERS %else dd -1 ; number of free clusters %endif 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 %if _BACKUP && _BPE == 32 %if _BPS < 512 zerobytes (6 * 512) - ($ - $$) %else zerobytes (6 * _BPS) - ($ - $$) %endif %endif %1_boot_end_2: %assign bootused bootused + ($ - $$) %endmacro 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 %assign bootused 0 emit_boot ,1 %if _BACKUP && _BPE == 32 emit_boot backup,0 %endif zerobytes (_NUMRESERVED * _BPS) - bootused addsection fat, STARTSFOLLOWS align=_BPS ; %1 = name for the equ to receive the start cluster, ; _start_cluster will be appended ; also name for the equ from which to read end entry, ; _chain_end 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 secondentry_chain_end equ EOFENTRY 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) ; %$parentcluster = data cluster number of parent dir (0 if root is parent) ; %$parentattrib = attrib of parent dir (0 if root is parent) ; %$parentfiletime = file time of parent dir ; %$parentfiledate = file date of parent dir %macro incdir 3.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 | _ATTRIB at deClusterHigh, dw %%dir_start_cluster >> 16 at deTime, dw _FATFILETIME at deDate, dw _FATFILEDATE at deClusterLow, dw %%dir_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 | %$$parentattrib at deClusterHigh, dw %$$parentcluster >> 16 at deTime, dw %$$parentfiletime at deDate, dw %$$parentfiledate at deClusterLow, dw %$$parentcluster & 0FFFFh at deSize, dd 0 iend %endif %%dir_chain_end equ EOFENTRY %xdefine CHAINLIST CHAINLIST,%%dir %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 | _ATTRIB at deClusterHigh, dw %%dir_start_cluster >> 16 at deTime, dw _FATFILETIME at deDate, dw _FATFILEDATE at deClusterLow, dw %%dir_start_cluster & 0FFFFh at deSize, dd 0 iend %else rootcluster equ %%dir_start_cluster %endif %2 %+ _start_cluster equ %%dir_start_cluster %endmacro ; %1 = source pathname ; %2 = offset behind data already read ; %3 = fixed, 0 ; %4 = size of fragment in amount clusters, nonzero ; %5 only defined if more fragments to follow ; ; Recreates the FRAGMENTLIST smacro by defining ; it to 0 then appending %5 and subsequent ; parameters, if any. ; Sets FRAGMENTOFFSET to the next offset after ; the data possibly read in by this mmacro. ; If the file is shorter, this mmacro will fill ; the space with appended NUL bytes to use up ; the specified amount clusters. %macro incbinpart 4-*.nolist %%start: incbin %1, %2, %4 * _BPS * _SPC %assign FRAGMENTOFFSET %2 + %4 * _BPS * _SPC %define FRAGMENTLIST 0 %rotate 4 %rep %0 - 4 %xdefine FRAGMENTLIST FRAGMENTLIST, %1 %rotate 1 %endrep %if ($ - %%start) < %4 * _BPS * _SPC %assign TAIL %4 * _BPS * _SPC - ($ - %%start) %warning File %1 is shorter than expected, appending TAIL NUL bytes times TAIL db 0 %endif %endmacro %macro nextfragment 6-*.nolist usesection %1 %1 %+ _start: %3_chain_end equ %%fragment_start_cluster ; equate prior fragment's end entry to start of this fragment %xdefine %%sourcename %4 ; source pathname to read from %xdefine %%offset %5 ; offset to -> after already read data %xdefine %%oldfragmentlist FRAGMENTLIST ; if we have one preserve the new FRAGMENTLIST %xdefine FRAGMENTLIST %6 ; set up our remaining as the current list ; In this mmacro we eat the next fragment to allocate. ; %1 = section name, %2 = "none" token, %3 = fragment ; identifier for prior fragment (macro-local name), ; %4 = pathname, %5 = file offset, %6 = FRAGMENTLIST ; (which starts with 0 and may have embedded commas). ; So %7 and up are the parameters of the subsequent ; fragment, if any, in groups of four. %define NEXTFRAGMENTS none ; reset the list to a none token %rotate 6 ; rotate past the section name, none, ; and all four of our current fragment %assign COUNTER 1 %rep %0 - 6 ; repeat for all remaining parameters %if COUNTER % 4 %xdefine NEXTFRAGMENTS NEXTFRAGMENTS, %1 ; append this parameter %else %xdefine NEXTFRAGMENTS NEXTFRAGMENTS, {%1} ; this is a FRAGMENTLIST, so we make ; sure to pass along curly braces %endif %rotate 1 ; next parameter %assign COUNTER COUNTER + 1 %endrep ; note: rotated 6 + %0 - 6 times equals ; %0 times, all back to the beginning %ifidn FRAGMENTLIST, 0 ; if no further fragment %%start: incbin %%sourcename, %%offset ; include remaining data %%fragment_chain_end equ EOFENTRY ; tell FAT cnain allocation to end after this %if ($ - %%start) == 0 %warning File %%sourcename is shorter than expected, appending a NUL byte db 0 ; for proper FAT chain size mustn't be zero %endif %3_next_size equ ($ - %1 %+ _start) ; communicate to prior fragment what its ; next size (ie, this fragment's) is %else incbinpart %%sourcename, %%offset, FRAGMENTLIST ; incbin a part, specified by this mmacro's ; %4, and re-create FRAGMENTLIST %xdefine NEXTFRAGMENTS \ NEXTFRAGMENTS, %%fragment, %%sourcename, FRAGMENTOFFSET, \ {FRAGMENTLIST} ; remember at the end of NEXTFRAGMENTS list %define FRAGMENTLIST 0 ; reset FRAGMENTLIST %3_next_size equ ($ - %1 %+ _start) + %%fragment_next_size ; communicate to prior fragment what its ; next size (ie, this fragment's) is ; this includes the size of the fragment ; that's subsequent to this one, so it ; includes this fragment and all the ; fragments that are still to come. %endif %xdefine CHAINLIST CHAINLIST,%%fragment ; remember base name for this fragment's chain %xdefine FRAGMENTLIST %%oldfragmentlist ; preserve current FRAGMENTLIST for new file %endmacro ; %1 = source filename, may include a directory ; (the directory is used to locate ; the file on the host system) ; %2 = target filename, may include a directory ; (directory is stripped, base name is ; used in all-caps to store to the image) ; %3 = data section name to use ; %4 = data section name of directory to write to ; %5 = nonzero if filling data ; %6 = size of data to fill ; %7 = value to fill ; isrename = nonzero if ::rename ; isfill = nonzero if ::fill %macro incfile 4-7.nolist 0 usesection %3 %3 %+ _start: %if %5 fillbytes %6, %7 %%fragment_chain_end equ EOFENTRY %%filesize equ ($ - %3 %+ _start) %else %define string %1 %ifnstr string %defstr string string %endif %xdefine %%sourcename string %ifidn FRAGMENTLIST, 0 incbin %%sourcename %%fragment_chain_end equ EOFENTRY %%filesize equ ($ - %3 %+ _start) %else incbinpart %%sourcename, 0, FRAGMENTLIST %xdefine NEXTFRAGMENTS \ NEXTFRAGMENTS, %%fragment, %%sourcename, FRAGMENTOFFSET, \ {FRAGMENTLIST} %define FRAGMENTLIST 0 %%filesize equ ($ - %3 %+ _start) + %%fragment_next_size %endif %endif %xdefine CHAINLIST CHAINLIST,%%fragment %define string %2 %ifnstr string %defstr string string %endif parsename %2, !!(isrename || isfill) usesection %4 istruc DIRENTRY at deName, fill 8,32,db %$$name at deExt, fill 3,32,db %$$ext at deAttrib, db _ATTRIB at deClusterHigh, dw %%fragment_start_cluster >> 16 at deTime, dw _FATFILETIME at deDate, dw _FATFILEDATE at deClusterLow, dw %%fragment_start_cluster & 0FFFFh at deSize, dd %%filesize iend %endmacro ; %1 = filename ; %2 = data section name of directory to write to ; %3 = fill length ; %4 = fill value %imacro incendfill 4.nolist %if %4 %if %4 == 256 %xdefine LISTENDFILL LISTENDFILL, %%file, %3, 0 %else %xdefine LISTENDFILL LISTENDFILL, %%file, %3, %4 %endif %else %xdefine LISTENDFILLZEROS LISTENDFILLZEROS, %%file, %3, %4 %endif %define string %1 %ifnstr string %defstr string string %endif parsename %1, 1 usesection %2 istruc DIRENTRY at deName, fill 8,32,db %$$name at deExt, fill 3,32,db %$$ext at deAttrib, db _ATTRIB at deClusterHigh, dw %%file_start_cluster >> 16 at deTime, dw _FATFILETIME at deDate, dw _FATFILEDATE at deClusterLow, dw %%file_start_cluster & 0FFFFh at deSize, dd %3 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 %if ischdir %error Subsubdirectory not supported, use ::chdir twice %elif isrename %error Subdirectory not supported in ::rename target %elif isfill %error Subdirectory not supported in ::fill target %else %error Subdirectory not supported %endif %endif %substr string string length -ii + 2, -1 %exitrep %endif %ifidn cc,"\" %if %2 %if ischdir %error Subsubdirectory not supported, use ::chdir twice %elif isrename %error Subdirectory not supported in ::rename target %elif isfill %error Subdirectory not supported in ::fill target %else %error Subdirectory not supported %endif %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 %define DIRLIST none %define CHAINLIST none %define FRAGMENTLIST 0 %define NEXTFRAGMENTS none %define LISTENDFILL none, none, none %define LISTENDFILLZEROS none, none, none %macro multiincfile 1-*.nolist %assign filescount 0 %define ff fixed_root %assign chainindex 0 %assign ischdir 0 %assign isfragment 0 %assign isfragmentsame 0 %assign isnextfragments 0 %assign isrename 0 %assign isfill 0 %assign isattrib 0 %assign isdatetime 0 %assign isdate 0 %assign istime 0 %push ROOT %define %$namelist "", "" %define %$dirname "/" %if _BPE == 32 setup_a_section ; add a section for root directory incdir "", data %+ chainindex, none %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 %assign %$parentattrib 0 %assign %$parentfiletime _FATFILETIME %assign %$parentfiledate _FATFILEDATE %rep %0 %if ischdir %ifidni %1, .. %ifctx ROOT %error Cannot back out over root %else %pop %endif %else setup_a_section ; add a section for a directory incdir %1, data %+ chainindex, %$parent %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 %$parentattrib _ATTRIB %assign %$parentfiletime _FATFILETIME %assign %$parentfiledate _FATFILEDATE %assign chainindex chainindex + 1 %endif %assign ischdir 0 %elif isfragment == -1 %assign isfragment %1 %elif isfragment == -2 %assign isfragmentsame %1 %assign isfragment 0 %elif isfragment %ifn %1 %error Zero clusters fragment is invalid %endif %assign ONEFRAGMENT %1 %xdefine FRAGMENTLIST FRAGMENTLIST, ONEFRAGMENT %assign isfragment isfragment - 1 %elif isfragmentsame %ifn %1 %error Zero clusters fragment is invalid %endif %assign ONEFRAGMENT %1 %rep isfragmentsame %xdefine FRAGMENTLIST FRAGMENTLIST, ONEFRAGMENT %endrep %assign isfragmentsame 0 %elif isnextfragments %rep %1 %ifidn NEXTFRAGMENTS, none %exitrep %else setup_a_section ; add a section for next data fragment nextfragment data %+ chainindex, NEXTFRAGMENTS %assign chainindex chainindex + 1 %endif %endrep %assign isnextfragments 0 %elif isrename == 1 %xdefine renamesource %1 %assign isrename 2 %elif isrename == 2 setup_a_section ; add a section for a file incfile renamesource, %1, data %+ chainindex, %$parent %assign chainindex chainindex + 1 %assign isrename 0 %elif isfill == 1 || isfill == 4 %xdefine filllength %1 %assign isfill isfill + 1 %elif isfill == 2 || isfill == 5 %xdefine fillvalue %1 %assign isfill isfill + 1 %elif isfill == 3 setup_a_section ; add a section for a file incfile "", %1, data %+ chainindex, %$parent, 1, filllength, fillvalue %assign chainindex chainindex + 1 %assign isfill 0 %elif isfill == 6 incendfill %1, %$parent, filllength, fillvalue %assign isfill 0 %elif isattrib %assign _ATTRIB %1 checkattrib %assign isattrib 0 %elif isdatetime == 1 %assign _FILEDATE %1 %assign isdatetime 2 %elif isdatetime == 2 %assign _FILETIME %1 convertdatetime %assign isdatetime 0 %elif isdate %assign _FILEDATE %1 convertdatetime %assign isdate 0 %elif istime %assign _FILETIME %1 convertdatetime %assign istime 0 %elifidni %1, ::chdir %assign ischdir 1 %elifidni %1, ::fragment %assign isfragment -1 %elifidni %1, ::fragmentsame %assign isfragment -2 %elifidni %1, ::nextfragment %ifidn NEXTFRAGMENTS, none %error No next fragments to write %endif setup_a_section ; add a section for next data fragment nextfragment data %+ chainindex, NEXTFRAGMENTS %assign chainindex chainindex + 1 %elifidni %1, ::nextfragments %assign isnextfragments 1 %elifidni %1, ::rename %assign isrename 1 %elifidni %1, ::fill %assign isfill 1 %elifidni %1, ::endfill %assign isfill 4 %elifidni %1, ::attrib %assign isattrib 1 %elifidni %1, ::datetime %assign isdatetime 1 %elifidni %1, ::date %assign isdate 1 %elifidni %1, ::time %assign istime 1 %elifidni %1, ::setdirdatetime %assign %$parentfiletime _FATFILETIME %assign %$parentfiledate _FATFILEDATE %elifnidni %1, ::empty %ifidn %1, "" %error Invalid filename %elifempty %1 %error Invalid filename %else setup_a_section ; add a section for a file incfile %1, %1, data %+ chainindex, %$parent %assign chainindex chainindex + 1 %endif %endif %rotate 1 %endrep %if ischdir %error No directory to create specified %endif %if isfragment %error No fragment list specified %endif %if isfragmentsame %error No fragment size specified %endif %if isnextfragments %error No amount of fragments specified %endif %if isrename == 1 %error No rename source specified %endif %if isrename == 2 %error No rename target specified %endif %if isfill == 1 || isfill == 4 %error No fill length specified %endif %if isfill == 2 || isfill == 5 %error No fill value specified %endif %if isfill == 3 || isfill == 6 %error No fill target specified %endif %if isattrib %error No attributes value specified %endif %if isdatetime == 1 %error No datetime date value specified %endif %if isdatetime == 2 %error No datetime time value specified %endif %if isdate %error No date value specified %endif %if istime %error No time value specified %endif %rep 1024 %ifctx ROOT %exitrep %else %pop %endif %endrep %pop %endmacro multiincfile _PAYLOADFILE %rep 1024 %ifidn NEXTFRAGMENTS, none %exitrep %else setup_a_section ; add a section for next data fragment nextfragment data %+ chainindex, NEXTFRAGMENTS %assign chainindex chainindex + 1 %endif %endrep %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 %macro allocateendfill 3-*.nolist %ifnidni %1, none %error Expected a none entry to start chain list %endif %if %0 % 3 %error Expected an amount of chain list entries divisible by 3 %endif ; ii as left from allocatechains %rep (%0 - 3) / 3 %rotate 3 setup_a_section usesection data %+ ii data %+ ii %+ _start: %if isendfillzeros ; zerobytes_if_full (%2 + _BPS * _SPC - 1) & ~(_BPS * _SPC - 1) zerobytes_if_full %2 %else fillbytes %2, %3 %endif align _BPS * _SPC, db 0 %assign clusters (%2 + _BPS * _SPC - 1) / (_BPS * _SPC) %assign data_used data_used + (clusters * _BPS * _SPC) addfatchain %1,clusters %1_chain_end equ EOFENTRY %assign ii ii + 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 %assign isendfillzeros 0 allocateendfill LISTENDFILL %assign isendfillzeros 1 allocateendfill LISTENDFILLZEROS addsection empty, follows= %+ ff vstart=0 usesection fixed_root %if _BPE != 32 times 32 db 0 %endif zerobytes ROOTSECTORS * _BPS - ($ - $$) USEDCLUSTERS equ data_used / (_BPS * _SPC) usesection empty %if data_used > (DATASECTORS * _BPS) %error Too much data used %endif zerobytes_if_full DATASECTORS * _BPS - data_used %if _MBR addsection partition_end_padding, follows=empty zerobytes_if_full (CYLINDERS * _CHS_HEADS * _CHS_SECTORS \ - _MBR_GAP_SIZE_SECTORS - _SPI) \ * _BPS %endif %if _BPE == 12 %macro dumpfatentry 1.nolist %ifempty EVENENTRY %xdefine EVENENTRY %1 %else db (EVENENTRY) & 0FFh db ((EVENENTRY) >> 8) \ | (((%1) & 0Fh) << 4) db ((%1) >> 4) %define EVENENTRY %endif %endmacro %elif _BPE == 16 %define dumpfatentry dw %elif _BPE == 32 %define dumpfatentry dd %else %error Invalid bpe %endif %macro expandfatchains 2-*.nolist %define EVENENTRY dumpfatentry (_MEDIAID | (MAXENTRY - 15)) ; first entry has media ID byte %assign ii 1 ; first entry is at index 0, ; next is index 1 %rep %0 / 2 ; for each pair %if %2 %1_start_cluster equ ii ; equ for chain start cluster %rep %2 - 1 ; as many as are non-end entries %assign ii ii + 1 dumpfatentry ii ; link to next cluster %endrep %assign ii ii + 1 dumpfatentry %1_chain_end ; end of contiguous chain entry %else %1_start_cluster equ 0 ; empty chain, start cluster is 0 %endif %rotate 2 ; rotate to next pair %endrep %ifnempty EVENENTRY ; dumpfatentry 0 dw EVENENTRY %if 0 Edge case result with dumpfatentry 0 here: bootimg$ nasm bootimg.asm \ -D_PAYLOADFILE=::endfill,"(_SPI - 1 - 1 - 1) * 512",0,zeros.bin \ -I ../lmacros/ -D_FULL=0 -D_BPE=12 -D_SPI=342 -D_SPF="1" \ -D_NUMFATS=1 -D_NUMROOT=16 bootimg.asm:1201: error: Negative count given to zerobytes bootimg.asm:1205: error: Internal error in FAT filling Correct behaviour with dw EVENENTRY: bootimg$ nasm bootimg.asm \ -D_PAYLOADFILE=::endfill,"(_SPI - 1 - 1 - 1) * 512",0,zeros.bin \ -I ../lmacros/ -D_FULL=0 -D_BPE=12 -D_SPI=342 -D_SPF="1" \ -D_NUMFATS=1 -D_NUMROOT=16 Correctly rejecting too small FAT: bootimg$ time nasm bootimg.asm \ -D_PAYLOADFILE=::endfill,"(_SPI - 1 - 1 - 1) * 512",0,zeros.bin \ -I ../lmacros/ -D_FULL=0 -D_BPE=12 -D_SPI=343 -D_SPF="1" \ -D_NUMFATS=1 -D_NUMROOT=16 bootimg.asm:272: error: Too few FAT sectors specified (341 entries, 342 needed) bootimg.asm:1221: error: Negative count given to zerobytes bootimg.asm:1225: error: Internal error in FAT filling %endif %endif %endmacro usesection fat %assign jj 0 %rep _NUMFATS fat%[jj]: expandfatchains FATCHAINS zerobytes _SPF * _BPS - ($ - fat%[jj]) %assign jj jj+1 %endrep %if FATSECTORS * _BPS - ($ - $$) %error Internal error in FAT filling %endif