Mercurial > ecm > bootimg
view bootimg.asm @ 70:50b2a696666a
avoid %rep error when no filename specified
author | C. Masloch <pushbx@ulukai.org> |
---|---|
date | Mon, 14 Dec 2020 13:07:38 +0100 |
parents | 9cf2837317b5 |
children | 604f416c159f |
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. ::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. 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. * 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 _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 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 numdef NUMRESERVED, 1 ; number of reserved sectors %if _BPE == 32 && (_FSINFO || _BACKUP) numdef NUMRESERVED, 16 ; number of reserved sectors %endif numdef ALIGNDATA, 0, 16 ; align data to this sector boundary ; (done by increasing _NUMRESERVED) numdef WARN_TOOMANYFAT, 1 numdef WARN_ALIGNDATA, 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 BOOTFILE, "" strdef BOOTJUMPFILE, "" strdef BOOTCODEFILE, "" gendef PAYLOADFILE, "" gendef MAP, "" strdef OEM_NAME, " lDOS" strdef OEM_NAME_FILL, '_' strdef DEFAULT_LABEL, "NO NAME" numdef VOLUMEID, 0 numdef MBR, 0 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] %rep (%1) / (_ZEROBYTES_MAX) resb (_ZEROBYTES_MAX) %endrep resb (%1) % (_ZEROBYTES_MAX) [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 %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.nolist %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 %error FAT would be detected %1 (%[DATACLUSTERS] = %[string]h clusters) %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 || _BPE == 32 detectionerror as FAT12 %endif %elif (DATACLUSTERS + 2) < 10000h %if _BPE == 32 detectionerror as FAT16 %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) %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 _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 _BOOTFILE, "" incbin _BOOTFILE %else %ifnidn _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 %else dw 0 %endif %1_backup_sector: ; offset 32h (50) %if _BACKUP dw 6 %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 _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 at FSINFO.signature1 dd "RRaA" at FSINFO.reserved1 _fill 480 + 4, 0, %1_fsinfo 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 zerobytes (6 * _BPS) - ($ - $$) %endif %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 ; %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 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 = 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 %else %define string %1 %ifnstr string %defstr string string %endif incbin string %endif %assign filesize ($ - %3 %+ _start) %xdefine CHAINLIST CHAINLIST,%%file %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 deClusterHigh, dw %%file_start_cluster >> 16 at deClusterLow, dw %%file_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 deClusterHigh, dw %%file_start_cluster >> 16 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 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 isrename 0 %assign isfill 0 %push ROOT %define %$namelist "", "" %define %$dirname "/" %if _BPE == 32 setup_a_section ; add a section for root directory 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 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, %$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 ischdir 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 %elifidni %1, ::chdir %assign ischdir 1 %elifidni %1, ::rename %assign isrename 1 %elifidni %1, ::fill %assign isfill 1 %elifidni %1, ::endfill %assign isfill 4 %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 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 %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 %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 %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 %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 start cluster %rep %2 - 1 ; as many as are non-EOF entries %assign ii ii + 1 dumpfatentry ii ; link to next cluster %endrep %assign ii ii + 1 dumpfatentry EOFENTRY ; EOF 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