diff --git a/test/ldosboot/boot.asm b/test/ldosboot/boot.asm
new file mode 100644
index 0000000..cd20c63
--- /dev/null
+++ b/test/ldosboot/boot.asm
@@ -0,0 +1,1854 @@
+
+%if 0
+
+File system boot sector loader code for FAT12 or FAT16
+
+Adapted from 2002-11-26 fatboot.zip/fat12.asm,
+ released as public domain by Chris Giese
+
+Public domain by C. Masloch, 2012
+
+%endif
+
+
+%include "lmacros2.mac"
+
+%ifndef _MAP
+%elifempty _MAP
+%else ; defined non-empty, str or non-str
+ [map all _MAP]
+%endif
+
+ defaulting
+
+ numdef FAT16, 0 ; 0 = FAT12, 1 = FAT16
+ strdef OEM_NAME, " lDOS"
+ strdef OEM_NAME_FILL, '_'
+ strdef DEFAULT_LABEL, "lDOS"
+ numdef VOLUMEID, 0
+
+ strdef LOAD_NAME, "LDOS"
+ strdef LOAD_EXT, "COM" ; name of file to load
+ numdef LOAD_ADR, 02000h ; where to load
+ numdef LOAD_MIN_PARA, paras(1536)
+ numdef LOAD_NON_FAT, 0, 2048 ; use FAT-less loading (value is amount bytes)
+ numdef EXEC_SEG_ADJ, 0 ; how far cs will be from _LOAD_ADR
+ numdef EXEC_OFS, 400h ; what value ip will be
+ numdef CHECKOFFSET, 1020
+ numdef CHECKVALUE, "lD"
+ numdef LOAD_DIR_SEG, 0 ; => where to store dir entry (0 if nowhere)
+ numdef ADD_SEARCH, 0 ; whether to search second file
+ strdef ADD_NAME, ""
+ strdef ADD_EXT, "" ; name of second file to search
+ numdef ADD_DIR_SEG, 0 ; => where to store dir entry (0 if nowhere)
+
+ gendef _ADR_DIRBUF, end -start+7C00h ; 07E00h
+ gendef _ADR_FATBUF, end -start+7C00h ; 07E00h
+
+ numdef QUERY_GEOMETRY, 1 ; query geometry via 13.08 (for CHS access)
+ numdef USE_PART_INFO, 1 ; use ds:si-> partition info from MBR, if any
+ numdef USE_AUTO_UNIT, 1 ; use unit passed from ROM-BIOS in dl
+ numdef RPL, 1 ; support RPL and do not overwrite it
+ numdef RPL_GRACE_AREA, 130 * 1024
+ ; alternative RPL support,
+ ; assume RPL fits in this area
+ numdef CHS, 1 ; support CHS (if it fits)
+ numdef LBA, 1 ; support LBA (if available)
+ numdef LBA_33_BIT, 1 ; support 33-bit LBA
+ numdef LBA_CHECK_NO_33, 1 ; else: check that LBA doesn't carry
+
+ numdef RELOCATE, 0 ; relocate the loader to top of memory
+ numdef SET_BL_UNIT, 0 ; if to pass unit in bl as well
+ numdef SET_DL_UNIT, 0 ; if to pass unit in dl
+ numdef SET_AXBX_DATA, 0 ; if to pass first data sector in ax:bx
+ numdef SET_DSSI_DPT, 0 ; if to pass DPT address in ds:si
+ numdef PUSH_DPT, 0 ; if to push DPT address
+ numdef MEMORY_CONTINUE, 1 ; if to just execute when memory full
+ numdef SET_DI_CLUSTER, 0 ; if to pass first load file cluster in di
+ numdef DIRBUF_500, 0 ; if to load root dir sector(s) to 0:500h
+ numdef DIR_ENTRY_500, 0 ; if to copy directory entry to 0:500h
+ numdef DIR_ENTRY_520, 0 ; if to copy next directory entry to 0:520h
+ numdef TURN_OFF_FLOPPY, 0 ; if to turn off floppy motor after loading
+ numdef DATASTART_HIDDEN,0 ; if to add hidden sectors to data_start
+ numdef LBA_SET_TYPE, 0 ; if to set third byte to LBA partition type
+ numdef SET_LOAD_SEG, 1 ; if to set load_seg (word [ss:bp - 6])
+ numdef SET_FAT_SEG, 1 ; if to set fat_seg (word [ss:bp - 8])
+ numdef SET_CLUSTER, 1 ; if to set first_cluster (word [ss:bp - 16])
+ numdef ZERO_ES, 0 ; if to set es = 0 before jump
+ numdef ZERO_DS, 0 ; if to set ds = 0 before jump
+
+ numdef FIX_SECTOR_SIZE, 0 ; fix sector size (0 = disable, else = sector size)
+ numdef FIX_SECTOR_SIZE_SKIP_CHECK, 0 ; don't check sector size
+ numdef FIX_CLUSTER_SIZE, 0 ; fix cluster size
+ numdef FIX_CLUSTER_SIZE_SKIP_CHECK, 0 ; don't check cluster size
+ numdef NO_LIMIT, 0 ; allow using more memory than a boot sector
+ ; also will not write 0AA55h signature!
+ numdef LBA_SKIP_CHECK, 1 ; don't use proper LBA extensions check
+ numdef LBA_RETRY, 0 ; retry LBA reads one time
+ numdef CHS_RETRY, 1 ; retry CHS reads one time
+ numdef CHS_RETRY_REPEAT,16 ; retry CHS reads multiple times
+ ; (value of the def is used as count)
+
+ numdef MEDIAID, 0F0h ; media ID
+ 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
+ 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 NUMFATS, 2 ; number of FATs
+ numdef NUMROOT, 224 ; number of root directory entries
+ numdef NUMRESERVED, 1 ; number of reserved sectors
+
+%if _FAT16
+ ; Unlike the 1440 KiB diskette image defaults for the FAT12
+ ; loader we just fill the FAT16 BPB with zeros by default.
+ numdef MEDIAID, 0 ; media ID
+ numdef UNIT, 0 ; load unit in BPB
+ numdef CHS_SECTORS, 0 ; CHS geometry field for sectors
+ numdef CHS_HEADS, 0 ; CHS geometry field for heads
+ numdef HIDDEN, 0 ; number of hidden sectors
+ numdef SPI, 0 ; sectors per image
+ numdef BPS, 0 ; bytes per sector
+ numdef SPC, 0 ; sectors per cluster
+ numdef SPF, 0 ; sectors per FAT
+ numdef NUMFATS, 0 ; number of FATs
+ numdef NUMROOT, 0 ; number of root directory entries
+ numdef NUMRESERVED, 0 ; number of reserved sectors
+%endif
+
+%if _DIRBUF_500
+ gendef _ADR_DIRBUF, 500h
+%endif
+
+
+ numdef COMPAT_FREEDOS, 0 ; partial FreeDOS load compatibility
+ numdef COMPAT_IBM, 0 ; partial IBMDOS load compatibility
+ numdef COMPAT_MS7, 0 ; partial MS-DOS 7 load compatibility
+ numdef COMPAT_MS6, 0 ; partial MS-DOS 6 load compatibility
+ numdef COMPAT_LDOS, 0 ; lDOS load compatibility
+ numdef COMPAT_KERNEL7E, 0 ; kernel at 0:7E00h load compatibility
+
+%if (!!_COMPAT_FREEDOS + !!_COMPAT_IBM + \
+ !!_COMPAT_MS7 + !!_COMPAT_MS6 + \
+ !!_COMPAT_LDOS || _COMPAT_KERNEL7E) > 1
+ %error At most one set must be selected.
+%endif
+
+%if _COMPAT_FREEDOS
+ strdef LOAD_NAME, "KERNEL"
+ strdef LOAD_EXT, "SYS"
+ numdef LOAD_ADR, 00600h
+ numdef LOAD_MIN_PARA, paras(512)
+ numdef EXEC_SEG_ADJ, 0
+ numdef EXEC_OFS, 0
+
+ numdef CHECKVALUE, 0
+ numdef SET_LOAD_SEG, 0
+ numdef SET_FAT_SEG, 0
+ numdef SET_CLUSTER, 0
+
+ numdef SET_BL_UNIT, 1
+ numdef MEMORY_CONTINUE, 0
+ numdef RELOCATE, 1
+ ; The FreeDOS load protocol mandates that the entire file be loaded.
+%endif
+
+%if _COMPAT_IBM
+ strdef LOAD_NAME, "IBMBIO"
+ strdef LOAD_EXT, "COM"
+ numdef LOAD_ADR, 00700h
+ numdef LOAD_MIN_PARA, paras(512)
+ numdef EXEC_SEG_ADJ, 0
+ numdef EXEC_OFS, 0
+ numdef LOAD_DIR_SEG, 50h
+ numdef ADD_SEARCH, 1
+ strdef ADD_NAME, "IBMDOS"
+ strdef ADD_EXT, "COM"
+ numdef ADD_DIR_SEG, 52h
+ ; Note: The IBMBIO.COM directory entry must be stored at
+ ; 0:500h, and the IBMDOS.COM directory entry at 0:520h.
+
+ numdef CHECKVALUE, 0
+ numdef SET_LOAD_SEG, 0
+ numdef SET_FAT_SEG, 0
+ numdef SET_CLUSTER, 0
+
+ numdef SET_DL_UNIT, 1
+ numdef MEMORY_CONTINUE, 1
+ ; 3 sectors * 512 BpS should suffice. We load into 700h--7A00h,
+ ; ie >= 6000h bytes (24 KiB), <= 7300h bytes (28.75 KiB).
+ numdef SET_AXBX_DATA, 1
+ numdef DATASTART_HIDDEN,1
+ numdef SET_DSSI_DPT, 1
+ numdef PUSH_DPT, 1
+%endif
+
+%if _COMPAT_MS7
+ strdef LOAD_NAME, "IO"
+ strdef LOAD_EXT, "SYS"
+ numdef LOAD_ADR, 00700h
+ numdef LOAD_MIN_PARA, paras(1024)
+ numdef EXEC_SEG_ADJ, 0
+ numdef EXEC_OFS, 200h
+
+ numdef CHECKVALUE, 0
+ numdef SET_LOAD_SEG, 0
+ numdef SET_FAT_SEG, 0
+ numdef SET_CLUSTER, 0
+
+ numdef SET_DL_UNIT, 1
+ numdef SET_DSSI_DPT, 0
+ numdef PUSH_DPT, 1
+ numdef MEMORY_CONTINUE, 1
+ ; 4 sectors * 512 BpS should suffice. We load into 700h--7A00h,
+ ; ie >= 6000h bytes (24 KiB), <= 7300h bytes (28.75 KiB).
+ numdef SET_DI_CLUSTER, 1
+ numdef DATASTART_HIDDEN,1
+ numdef LBA_SET_TYPE, 1
+%endif
+
+%if _COMPAT_MS6
+ strdef LOAD_NAME, "IO"
+ strdef LOAD_EXT, "SYS"
+ numdef LOAD_ADR, 00700h
+ numdef LOAD_MIN_PARA, paras(512)
+ numdef EXEC_SEG_ADJ, 0
+ numdef EXEC_OFS, 0
+ numdef LOAD_DIR_SEG, 50h
+ numdef ADD_SEARCH, 1
+ strdef ADD_NAME, "MSDOS"
+ strdef ADD_EXT, "SYS"
+ numdef ADD_DIR_SEG, 52h
+ ; Note: The IO.SYS directory entry must be stored at
+ ; 0:500h, and the MSDOS.SYS directory entry at 0:520h.
+
+ numdef CHECKVALUE, 0
+ numdef SET_LOAD_SEG, 0
+ numdef SET_FAT_SEG, 0
+ numdef SET_CLUSTER, 0
+
+ numdef SET_DL_UNIT, 1
+ numdef MEMORY_CONTINUE, 1
+ ; 3 sectors * 512 BpS should suffice. We load into 700h--7A00h,
+ ; ie >= 6000h bytes (24 KiB), <= 7300h bytes (28.75 KiB).
+ numdef SET_AXBX_DATA, 1
+ numdef DATASTART_HIDDEN,1
+ numdef SET_DSSI_DPT, 1
+ numdef PUSH_DPT, 1
+%endif
+
+%if _COMPAT_LDOS
+ strdef LOAD_NAME, "LDOS"
+ strdef LOAD_EXT, "COM"
+ numdef LOAD_ADR, 02000h
+ numdef LOAD_MIN_PARA, paras(1536)
+ numdef EXEC_SEG_ADJ, 0
+ numdef EXEC_OFS, 400h
+ numdef CHECKOFFSET, 1020
+ numdef CHECKVALUE, "lD"
+
+ numdef SET_DL_UNIT, 0
+ numdef SET_CLUSTER, 1
+ numdef SET_FAT_SEG, 1
+ numdef SET_LOAD_SEG, 1
+ numdef MEMORY_CONTINUE, 1
+ numdef DATASTART_HIDDEN,0
+%endif
+
+%if _COMPAT_KERNEL7E
+ strdef OEM_NAME, "KERNEL7E"
+ strdef LOAD_NAME, "KERNEL7E"
+ strdef LOAD_EXT, "BIN"
+ numdef LOAD_ADR, 07E00h
+ numdef LOAD_MIN_PARA, paras(512)
+ numdef EXEC_SEG_ADJ, -7E0h
+ numdef EXEC_OFS, 7E00h
+ numdef CHECKVALUE, 0
+
+ numdef SET_DL_UNIT, 1
+ numdef SET_BL_UNIT, 0
+ numdef SET_CLUSTER, 0
+ numdef SET_FAT_SEG, 0
+ numdef SET_LOAD_SEG, 0
+ numdef MEMORY_CONTINUE, 0
+ numdef DATASTART_HIDDEN,0
+
+ gendef _ADR_FATBUF, 4000h
+ gendef _ADR_DIRBUF, 4000h
+ numdef RPL, 0
+ numdef ZERO_ES, 1
+%endif
+
+
+%if 0
+
+Notes about partial load compatibilities
+
+* FreeDOS:
+ * Relocates to an address other than 27A00h (1FE0h:7C00h)
+ * A lot of options between _USE_PART_INFO, _QUERY_GEOMETRY, _CHS, _LBA,
+ and/or _RPL need to be disabled to make the loader fit
+* IBMDOS:
+* MS-DOS 6:
+ * Does not actually relocate DPT, just provide its address
+ * A lot of options between _USE_PART_INFO, _QUERY_GEOMETRY, _CHS,
+ and/or _LBA need to be disabled to make the loader fit
+* MS-DOS 7:
+ * Does not actually relocate DPT, just provide its address
+ * Does not contain message table used by loader
+
+%endif
+
+%if _SET_BL_UNIT && _SET_AXBX_DATA
+ %error Cannot select both of these options!
+%endif
+
+%if _DIR_ENTRY_520
+ %assign _DIR_ENTRY_500 1
+%endif
+
+%if _DIRBUF_500 && _ADD_SEARCH
+ %error Cannot select both of these options!
+%endif
+
+%if _ADD_SEARCH
+ %if _LOAD_DIR_SEG == 0 || _ADD_DIR_SEG == 0
+ %error Assuming dir segs should be set if add search set
+ %endif
+%endif
+
+%if _RPL
+ %assign _RPL_GRACE_AREA 0
+%endif
+
+
+%assign LOADLIMIT 0A0000h
+%assign POSITION 07C00h
+
+%if _FIX_SECTOR_SIZE
+ %assign i 5
+ %rep 13-5
+ %if (1 << i) != (_FIX_SECTOR_SIZE)
+ %assign i i+1
+ %endif
+ %endrep
+ %if (1 << i) != (_FIX_SECTOR_SIZE)
+ %error Invalid sector size _FIX_SECTOR_SIZE
+ %endif
+%endif
+
+%if _FIX_CLUSTER_SIZE
+ %if _FIX_CLUSTER_SIZE > 256
+ %error Invalid cluster size _FIX_CLUSTER_SIZE
+ %endif
+ %assign i 0
+ %rep 8-0
+ %if (1 << i) != (_FIX_CLUSTER_SIZE)
+ %assign i i+1
+ %endif
+ %endrep
+ %if (1 << i) != (_FIX_CLUSTER_SIZE)
+ %warning Non-power-of-two cluster size _FIX_CLUSTER_SIZE
+ %endif
+%endif
+
+
+; 512-byte stack (minus the variables).
+ADR_STACK_LOW equ 7C00h - 200h ; 07A00h
+
+%if _DIRBUF_500
+ gendef _ADR_DIRBUF, 500h
+%elif _RELOCATE
+ gendef _ADR_DIRBUF, _LOAD_ADR
+%endif
+
+; one-sector directory buffer. Assumes sectors are no larger than 8 KiB
+ADR_DIRBUF equ __ADR_DIRBUF
+
+%if ! _RELOCATE
+; this used to be a two-sector FAT buffer -- two sectors because FAT12
+; entries are 12 bits and may straddle a sector boundary.
+; however, with the FAT12 loaded completely, the buffer only needs to hold
+; one 8 KiB sector, two 4 KiB sectors, three 2 KiB sectors, six 1 KiB sectors,
+; or twelve 512 byte sectors.
+; this shares its area with the directory buffer as they
+; are not simultaneously used. (if not _DIRBUF_500.)
+ADR_FATBUF equ __ADR_FATBUF
+%endif
+
+; start of unused memory after loader:
+ADR_END equ end -start+7C00h
+%if ! _RELOCATE
+ %if (ADR_FATBUF + 8192) > ADR_END
+ ADR_FREE_FROM equ (ADR_FATBUF + 8192) ; 09E00h
+ %else
+ ADR_FREE_FROM equ ADR_END ; 07E00h
+ %endif
+
+; end of unused memory before loader:
+ %if ADR_FATBUF < ADR_STACK_LOW
+ADR_FREE_UNTIL equ ADR_FATBUF
+ %else
+ADR_FREE_UNTIL equ ADR_STACK_LOW
+ %endif
+
+ %if ((ADR_FATBUF + 8192 - 1) & ~0FFFFh) != (ADR_FATBUF & ~0FFFFh)
+ %warning Possibly crossing 64 KiB boundary while reading FAT
+ %endif
+%endif
+
+%if ((ADR_DIRBUF + 8192 - 1) & ~0FFFFh) != (ADR_DIRBUF & ~0FFFFh)
+ %warning Possibly crossing 64 KiB boundary while reading directory
+%endif
+
+%if _RELOCATE
+ ADR_FREE_FROM equ 0 ; make next conditional true
+ ADR_FREE_UNTIL equ 0
+%endif
+%if _LOAD_ADR >= ADR_FREE_FROM
+ ; If reading on a sector size boundary, no crossing can occur.
+ ; Check for all possible sector sizes (32 B to 8 KiB). If one
+ ; of them fails display a warning, including the minimum size.
+ %assign SECSIZECHECK 32
+ %assign EXITREP 0
+ %rep 256
+ %ifn EXITREP
+ %if _LOAD_ADR & (SECSIZECHECK - 1)
+ %warning Possibly crossing 64 KiB boundary while reading file (sector size >= SECSIZECHECK)
+ %assign EXITREP 1
+ %exitrep
+ %endif
+ %if SECSIZECHECK == 8192
+ %assign EXITREP 1
+ %exitrep
+ %endif
+ %assign SECSIZECHECK SECSIZECHECK * 2
+ %endif
+ %endrep
+%else
+ ; If loading below the boot sector, address 1_0000h is never reached.
+%endif
+
+
+%if (_LOAD_ADR & 0Fh)
+ %error Load address must be on a paragraph boundary
+%endif
+
+%if _LOAD_ADR > LOADLIMIT
+ %error Load address must be in LMA
+%elif _LOAD_ADR < 00500h
+ %error Load address must not overlap IVT or BDA
+%endif
+
+%if ! _RELOCATE
+ %if _LOAD_ADR > (POSITION-512) && _LOAD_ADR < (POSITION+512)
+ %error Load address must not overlap loader
+ %endif
+
+ %if ADR_FATBUF > LOADLIMIT
+ %error FAT buffer address must be in LMA
+ %elif ADR_FATBUF < 00500h
+ %error FAT buffer address must not overlap IVT or BDA
+ %elif (ADR_FATBUF + 8192) > (POSITION-512) && ADR_FATBUF < (POSITION+512)
+ %error FAT buffer address must not overlap loader
+ %endif
+%endif
+
+%if ADR_DIRBUF > LOADLIMIT
+ %error Dir buffer address must be in LMA
+%elif ADR_DIRBUF < 00500h
+ %error Dir buffer address must not overlap IVT or BDA
+%elif (ADR_DIRBUF + 8192) > (POSITION-512) && ADR_DIRBUF < (POSITION+512)
+ %error Dir buffer address must not overlap loader at initial position
+%endif
+
+%if ((_EXEC_SEG_ADJ<<4)+_EXEC_OFS) < 0
+ %error Execution address must be in loaded file
+%elif ((_EXEC_SEG_ADJ<<4)+_EXEC_OFS+_LOAD_ADR) > LOADLIMIT
+ %error Execution address must be in LMA
+%endif
+
+%if (_EXEC_OFS & ~0FFFFh)
+ %error Execution offset must fit into 16 bits
+%endif
+
+%if (_EXEC_SEG_ADJ > 0FFFFh || _EXEC_SEG_ADJ < -0FFFFh)
+ %error Execution segment adjustment must fit into 16 bits
+%endif
+
+
+%if !_CHS && _QUERY_GEOMETRY
+ %warning No CHS support but querying geometry anyway
+%endif
+
+%if !_CHS && !_LBA
+ %error Either CHS or LBA or both must be enabled
+%endif
+
+
+%if 0
+
+There is some logic inside MS-DOS's hard disk partition initialisation
+code that sets up several requirements for us to fulfil. Otherwise,
+it will not accept the information given in the BPB (using default
+information based on the length as specified by MBR/EPBR instead) or
+make the whole file system inaccessible except for formatting. Both of
+those are undesirable of course. Some/all(?) checks are documented on
+pages 601,602 in "DOS Internals", Geoff Chappell 1994, as follows:
+
+* First three bytes contain either "jmp sho xx\ nop" or "jmp ne xx".
+* Media descriptor field >= 0F0h.
+* Bytes per sector field == 512.
+* Sectors per cluster field a power of 2 (1,2,4,8,16,32,64,128).
+* OEM name "version" (last three to four characters)
+ * must be "20.?", "10.?" (? = wildcard), but no other with "0.?",
+ * otherwise, must be "2.0", or
+ * 2nd-to-last,3rd-to-last character codes together > "3.", or
+ * those == "3.", last character code > "0"
+
+To stay compatible to those, display a warning here if the name
+itself would disqualify our boot sector already.
+
+%endif
+
+%push
+%strlen %$len _OEM_NAME_FILL
+%if %$len != 1
+ %error Specified OEM name fill must be 1 character
+%endif
+%strlen %$len _OEM_NAME
+%define %$nam _OEM_NAME
+%if %$len > 8
+ %error Specified OEM name is too long
+%else
+ %assign %$warn 0
+ %rep 8 - %$len
+ %strcat %$nam %$nam,_OEM_NAME_FILL
+ %endrep
+ %substr %$prefix %$nam 5 ; "wvxyZa.b", get "Z"
+ %substr %$major %$nam 6,7 ; "wvxyzA.b", get "A."
+ %substr %$minor %$nam 8 ; "wvxyza.B", get "B"
+ %if %$major == "0."
+ %ifn %$prefix == "1" || %$prefix == "2"
+ %assign %$warn 1
+ %endif
+ %elifn %$major == "2." && %$minor == "0"
+ %if %$major < "3."
+ %assign %$warn 1
+ %elif %$major == "3." && %$minor < "1"
+ %assign %$warn 1
+ %endif
+ %endif
+ %if %$warn
+ %warning Specified OEM name fails MS-DOS's validation
+ %endif
+%endif
+%pop
+
+
+ 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
+
+
+; use byte-offset addressing from BP for smaller code
+%define VAR(x) ((x) - start) + bp
+
+
+ cpu 8086
+; bootsector loaded at address 07C00h, addressable using 0000h:7C00h
+ org POSITION
+start:
+
+
+%define _LASTVARIABLE start
+ %macro nextvariable 2-3.nolist
+%1 equ (_LASTVARIABLE - %2)
+%define _LASTVARIABLE %1
+%ifidn %3, relocatestart
+ %define _RELOCATESTART %1
+%elifempty %3
+%else
+ %error Invalid third parameter
+%endif
+ %endmacro
+
+; Variables
+
+; (dword) sector where the first cluster's data starts
+ nextvariable data_start, 4
+
+; (word) current load segment (points behind last loaded data)
+ nextvariable load_seg, 2
+
+; (word) segment of FAT buffer
+; for FAT12 this holds the entire FAT
+; for FAT16 this holds the sector given by wo[fat_sector]
+; for FAT32 this holds the sector given by dwo[fat_sector]
+ nextvariable fat_seg, 2
+
+; (word for FAT16) currently loaded sector-in-FAT, -1 if none
+ nextvariable fat_sector, 4
+
+; (word for FAT12/FAT16) first cluster of load file
+ nextvariable first_cluster, 4, relocatestart
+
+ADR_STACK_START equ _LASTVARIABLE -start+POSITION
+
+%ifn _FIX_CLUSTER_SIZE
+; (word) actual sectors per cluster
+ nextvariable adj_sectors_per_cluster, 2, relocatestart
+%endif
+
+%ifn _FIX_SECTOR_SIZE
+; (word) number of 16-byte paragraphs per sector
+ nextvariable para_per_sector, 2, relocatestart
+%endif
+
+%ifn ! _RELOCATE && _LOAD_ADR < ADR_FREE_UNTIL && _FIX_SECTOR_SIZE
+; (word) segment of last available memory for sector
+ nextvariable last_available_sector, 2
+%endif
+
+lowest_variable equ _LASTVARIABLE
+
+
+ jmp strict short skip_bpb
+%if !_CHS && _LBA_SET_TYPE
+ db 0Eh ; LBA-enabled FAT16 FS partition type
+%else
+ nop ; default: no LBA
+%endif
+
+
+; BIOS Parameter Block (BPB)
+;
+; Installation will use the BPB already present in your file system.
+; These values must be initialised when installing the loader.
+;
+; The values shown here work only with 1440 KiB disks (CHS=80:2:18)
+
+oem_id: ; offset 03h (03) - not used by this code
+ fill 8,_OEM_NAME_FILL,db _OEM_NAME
+bytes_per_sector: ; offset 0Bh (11) - refer to _FIX_SECTOR_SIZE !
+ dw _BPS
+sectors_per_cluster: ; offset 0Dh (13) - refer to _FIX_CLUSTER_SIZE !
+ db _SPC & 255
+fat_start:
+num_reserved_sectors: ; offset 0Eh (14)
+ dw _NUMRESERVED
+num_fats: ; offset 10h (16)
+ db _NUMFATS
+num_root_dir_ents: ; offset 11h (17)
+ dw _NUMROOT
+total_sectors: ; offset 13h (19) - not used by this code
+%if _SPI < 1_0000h
+ dw _SPI
+%else
+ dw 0
+%endif
+media_id: ; offset 15h (21) - not used by this code
+ db _MEDIAID
+sectors_per_fat: ; offset 16h (22)
+ dw _SPF
+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
+%if _SPI >= 1_0000h
+ dd _SPI
+%else
+ dd 0
+%endif
+
+; Extended BPB
+
+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: fill 8,32,db "FAT1",'2'+4*!!_FAT16
+
+
+; Initialised data
+
+load_name:
+ fill 8,32,db _LOAD_NAME
+ fill 3,32,db _LOAD_EXT
+%if _ADD_SEARCH
+add_name:
+ fill 8,32,db _ADD_NAME
+ fill 3,32,db _ADD_EXT
+
+ ; align 2
+ ; This happens to be aligned anyway. But even if
+ ; it didn't, we'd rather save that byte than use
+ ; it to align these fields. So comment this out.
+filename:
+ dw add_name
+dirseg:
+ dw _ADD_DIR_SEG
+%endif
+
+
+ numdef TMPINC, 0
+
+%if _TMPINC
+ [list -]
+%endif
+ %imacro errorhandler 0
+%if _TMPINC
+ %include "error.tmp"
+ [list -]
+%else
+; === error.tmp ===
+read_sector.err:
+ mov al, 'R' ; Disk 'R'ead error
+%if ! _MEMORY_CONTINUE || _RELOCATE || _LOAD_ADR >= ADR_FREE_FROM
+ db __TEST_IMM16 ; (skip mov)
+error_filetoobig:
+error_memory:
+ mov al,'M' ; Not enough 'M'emory
+%endif
+
+error:
+%if _RELOCATE
+ mov bx, 7
+ mov ds, bx
+ mov bh, [462h - 70h]
+%else
+ mov bh, [462h]
+ mov bl, 7
+%endif
+ mov ah, 0Eh
+ int 10h ; display character
+ mov al, 07h
+ int 10h ; beep!
+
+ xor ax, ax ; await key pressed
+ int 16h
+
+ int 19h ; re-start the boot process
+; === eof ===
+%endif
+%if _TMPINC
+ [list +]
+%endif
+ %endmacro
+
+
+ %imacro readhandler 0
+%if _TMPINC
+ %include "read.tmp"
+ [list -]
+%else
+; === read.tmp ===
+ ; INP: dx:ax = sector
+ ; OUT: only if successful
+ ; dx:ax = incremented
+ ; bx => behind read sector
+ ; es = ADR_FATBUF>>4 = ADR_DIRBUF>>4
+ ; CHG: -
+%if ! _RELOCATE
+ %if ADR_DIRBUF == ADR_FATBUF
+read_sector_dirbuf:
+ %endif
+read_sector_fatbuf:
+ mov bx, ADR_FATBUF>>4
+%endif
+
+ ; Read a sector using Int13.02 or Int13.42
+ ;
+ ; INP: dx:ax = sector number within partition
+ ; bx => buffer
+ ; (_LBA) ds = ss
+ ; OUT: If unable to read,
+ ; ! jumps to error instead of returning
+ ; If sector has been read,
+ ; dx:ax = next sector number (has been incremented)
+ ; bx => next buffer (bx = es+word[para_per_sector])
+ ; es = input bx
+ ; CHG: -
+read_sector:
+ push dx
+ push cx
+ push ax
+ push si
+
+ push bx
+
+; DX:AX==LBA sector number
+; add partition start (= number of hidden sectors)
+ add ax,[VAR(hidden_sectors + 0)]
+ adc dx,[VAR(hidden_sectors + 2)]
+ %if (!_LBA || !_LBA_33_BIT) && _LBA_CHECK_NO_33
+ jc .err
+ %endif
+%if _LBA ; +70 bytes (with CHS, +63 bytes without CHS)
+ %if _LBA_33_BIT
+ sbb si, si ; -1 if was CY, 0 else
+ neg si ; 1 if was CY, 0 else
+ %endif
+ xor cx, cx
+ push cx ; highest word = 0
+ %if _LBA_33_BIT
+ push si ; bit 32 = 1 if operating in 33-bit space
+ %else
+ push cx ; second highest word = 0
+ %endif
+ push dx
+ push ax ; = qword sector number
+ push bx
+ push cx ; bx => buffer
+ inc cx
+ push cx ; word number of sectors to read
+ mov cl, 10h
+ push cx ; word size of disk address packet
+ mov si, sp ; ds:si -> disk address packet (on stack)
+
+ %if _LBA_SKIP_CHECK ; -14 bytes
+ mov dl, [VAR(boot_unit)]
+ %else
+ mov ah, 41h
+ mov dl, [VAR(boot_unit)]
+ mov bx, 55AAh
+ stc
+ int 13h ; 13.41.bx=55AA extensions installation check
+ jc .no_lba
+ cmp bx, 0AA55h
+ jne .no_lba
+ test cl, 1 ; support bitmap bit 0
+ jz .no_lba
+ %endif
+
+%if _LBA_RETRY
+ mov ah, 42h
+ int 13h ; 13.42 extensions read
+ jnc .lba_done
+
+ xor ax, ax
+ int 13h
+
+ ; have to reset the LBAPACKET's lpCount, as the handler may
+ ; set it to "the number of blocks successfully transferred".
+ ; (in any case, the high byte is still zero.)
+ mov byte [si + 2], 1
+%endif
+
+ mov ah, 42h
+ int 13h
+ %if _LBA_SKIP_CHECK && _CHS
+ jc .lba_check_error_1
+ %else
+.cy_err:
+ jc .lba_error
+ %endif
+
+.lba_done:
+%if _CHS && _LBA_SET_TYPE
+ mov byte [bp + 2], 0Eh ; LBA-enabled FAT16 FS partition type
+%endif
+ add sp, 10h
+ pop bx
+ mov es, bx
+%if _CHS
+ jmp short .chs_done
+%endif
+
+.lba_error: equ .err
+
+ %if !_CHS
+.no_lba: equ .err
+ %else
+ %if _LBA_SKIP_CHECK
+.lba_check_error_1:
+ cmp ah, 1 ; invalid function?
+ jne .lba_error ; no, other error -->
+ ; try CHS instead
+.cy_err: equ .err
+ %endif
+.no_lba:
+ add sp, 8
+ pop cx ; cx = low word of sector
+ pop ax
+ pop dx ; dx:ax = middle two words of sector
+ ; Here dx <= 1 if _LBA_33_BIT, else zero.
+ ; If dx is nonzero then the CHS calculation
+ ; should fail. If CHS sectors is equal to 1
+ ; (very unusual) then the div may fail. Else,
+ ; we will detect a cylinder > 1023 eventually.
+ pop si ; discard highest word of qword
+ %endif
+%endif
+
+%if !_LBA
+.cy_err: equ .err
+%endif
+
+%if _CHS ; +70 bytes
+; dx:ax = LBA sector number, (if _LBA) cx = 0
+; divide by number of sectors per track to get sector number
+; Use 32:16 DIV instead of 64:32 DIV for 8088 compatability
+; Use two-step 32:16 divide to avoid overflow
+ %if !_LBA
+ xchg cx, ax ; cx = low word of sector, clobbers ax
+ xchg ax, dx ; ax = high word of sector, clobbers dx
+ xor dx, dx ; dx:ax = high word of sector
+ %else
+ ; from the .no_lba popping we already have:
+ ; cx = low word of sector
+ ; dx:ax = high word of sector
+ %endif
+ div word [VAR(sectors_per_track)]
+ xchg cx,ax
+ div word [VAR(sectors_per_track)]
+ xchg cx,dx
+
+; DX:AX=quotient, CX=remainder=sector (S) - 1
+; divide quotient by number of heads
+ xchg bx, ax ; bx = low word of quotient, clobbers ax
+ xchg ax, dx ; ax = high word of quotient, clobbers dx
+ xor dx, dx ; dx = 0
+ div word [VAR(heads)]
+ ; ax = high / heads, dx = high % heads
+ xchg bx, ax ; bx = high / heads, ax = low quotient
+ div word [VAR(heads)]
+
+; bx:ax=quotient=cylinder (C), dx=remainder=head (H)
+; move variables into registers for INT 13h AH=02h
+ mov dh, dl ; dh = head
+ inc cx ; cl5:0 = sector
+ xchg ch, al ; ch = cylinder 7:0, al = 0
+ shr ax, 1
+ shr ax, 1 ; al7:6 = cylinder 9:8
+ ; bx has bits set iff it's > 0, indicating a cylinder >= 65536.
+ or bl, bh ; collect set bits from bh
+ or cl, al ; cl7:6 = cylinder 9:8
+ ; ah has bits set iff it was >= 4, indicating a cylinder >= 1024.
+ or bl, ah ; collect set bits from ah
+ mov dl,[VAR(boot_unit)] ; dl = drive
+.nz_err:
+ jnz .err ; error if cylinder >= 1024 -->
+ ; ! bx = 0 (for 13.02 call)
+
+; we call INT 13h AH=02h once for each sector. Multi-sector reads
+; may fail if we cross a track or 64K boundary
+ pop es
+%if _CHS_RETRY_REPEAT
+ mov si, _CHS_RETRY_REPEAT + 1
+.loop_chs_retry_repeat:
+ mov ax, 0201h
+ int 13h ; read one sector
+ jnc .done
+ xor ax, ax
+ int 13h ; reset disk
+ dec si ; another attempt ?
+ jnz .loop_chs_retry_repeat ; yes -->
+ jmp .err
+%else
+ %if _CHS_RETRY
+ mov ax, 0201h
+ int 13h ; read one sector
+ jnc .done
+; reset drive
+ xor ax, ax
+ int 13h
+ %endif
+; try read again
+ mov ax, 0201h
+ int 13h
+ %if _LBA_SKIP_CHECK
+ inc bx
+ jc .nz_err
+ %else
+ jc .cy_err
+ %endif
+%endif
+
+.done:
+; increment segment
+ mov bx, es
+%endif
+
+.chs_done:
+%if _FIX_SECTOR_SIZE
+ add bx, _FIX_SECTOR_SIZE >> 4
+%else
+ add bx, [VAR(para_per_sector)]
+%endif
+
+ pop si
+ pop ax
+ pop cx
+ pop dx
+; increment LBA sector number
+ inc ax
+ jne @F
+ inc dx
+@@:
+
+ retn
+; === eof ===
+%endif
+%if _TMPINC
+ [list +]
+%endif
+ %endmacro
+%if _TMPINC
+ [list +]
+%endif
+
+
+; Code
+
+skip_bpb:
+ cli
+ cld
+ xor cx, cx
+ mov bp, start ; magic bytes - checked by instsect
+ mov ss, cx
+ mov sp, ADR_STACK_START
+%if _USE_AUTO_UNIT
+ mov [VAR(boot_unit)], dl; magic bytes - checked by instsect
+%else
+ mov dl, [VAR(boot_unit)]; magic bytes - checked by instsect
+%endif
+
+ ; Note: es is left uninitialised here until the first call to
+ ; read_sector if the below conditional is false.
+%if _USE_PART_INFO ; +19 bytes
+ mov es, cx
+; Note: Award Medallion BIOS v6.0 (ASUS MED 2001 ACPI BIOS Revision 1009)
+; loads from a floppy disk drive with ds:si = 0F000h:0A92Dh ->
+; FF FF FF FF 08 00 08 01 FF FF FF FF FF FF FF FF, which was detected
+; as a valid partition table entry by this handling. Therefore, we
+; only accept partition information when booting from a hard disk now.
+ test dl, dl ; floppy ?
+ jns @F ; don't attempt detection -->
+; Check whether an MBR left us partition information.
+; byte[ds:si] bit 7 means active and must be set if valid.
+ cmp byte [si], cl ; flags for xx-00h (result is xx), SF = bit 7
+ jns @F ; xx < 80h, ie info invalid -->
+; byte[ds:si+4] is the file system type. Check for valid one.
+ cmp byte [si+4], cl ; is it zero?
+ je @F ; yes, info invalid -->
+; Info valid, trust their hidden sectors over hardcoded.
+; Assume the movsw instructions won't run with si = FFFFh.
+ mov di, hidden_sectors ; -> BPB field
+ add si, 8 ; -> partition start sector in info
+ movsw
+ movsw ; overwrite BPB field with value from info
+@@:
+%endif
+ mov ds, cx
+ sti
+
+
+%if _QUERY_GEOMETRY ; +27 bytes
+; test dl, dl ; floppy?
+; jns @F ; don't attempt query, might fail -->
+ ; Note that while the original PC BIOS doesn't support this function
+ ; (for its diskettes), it does properly return the error code 01h.
+ ; https://sites.google.com/site/pcdosretro/ibmpcbios (IBM PC version 1)
+ mov ah, 08h
+ ; xor cx, cx ; initialise cl to 0
+ ; Already from prologue cx = 0.
+ stc ; initialise to CY
+ int 13h ; query drive geometry
+ jc @F ; apparently failed -->
+ and cx, 3Fh ; get sectors
+ jz @F ; invalid (S is 1-based), don't use -->
+ mov [VAR(sectors_per_track)], cx
+ mov cl, dh ; cx = maximum head number
+ inc cx ; cx = number of heads (H is 0-based)
+ mov [VAR(heads)], cx
+@@:
+%endif
+
+%if _FIX_SECTOR_SIZE
+ %if !_FIX_SECTOR_SIZE_SKIP_CHECK
+ cmp word [VAR(bytes_per_sector)], _FIX_SECTOR_SIZE
+ mov al, 'S'
+ jne error
+ %endif
+ mov bx, _FIX_SECTOR_SIZE >> 5
+ %if _FIX_CLUSTER_SIZE
+ %if !_FIX_CLUSTER_SIZE_SKIP_CHECK
+ cmp byte [VAR(sectors_per_cluster)], _FIX_CLUSTER_SIZE & 0FFh
+ mov al, 'C'
+ jne error
+ %endif
+ %else
+; calculate some values that we need:
+; adjusted sectors per cluster (store in a word,
+; and decode EDR-DOS's special value 0 meaning 256)
+ xor ax, ax
+ mov al, [VAR(sectors_per_cluster)]
+ dec al
+ inc ax
+ push ax ; push into word [VAR(adj_sectors_per_cluster)]
+ %endif
+ mov ch, 0 ; ! ch = 0
+%else
+; 16-byte paragraphs per sector
+ mov bx,[VAR(bytes_per_sector)]
+ mov cx,4 ; ! ch = 0
+ shr bx,cl
+ %if _FIX_CLUSTER_SIZE
+ %if !_FIX_CLUSTER_SIZE_SKIP_CHECK
+ cmp byte [VAR(sectors_per_cluster)], _FIX_CLUSTER_SIZE & 0FFh
+ mov al, 'C'
+ jne error
+ %endif
+ %else
+; calculate some values that we need:
+; adjusted sectors per cluster (store in a word,
+; and decode EDR-DOS's special value 0 meaning 256)
+ ; ! ch = 0
+ mov cl, [VAR(sectors_per_cluster)]
+ ; therefore cx = sectors_per_cluster
+ dec cl
+ inc cx
+ push cx ; push into word [VAR(adj_sectors_per_cluster)]
+ dec cx ; ! ch = 0
+ %endif
+ push bx ; push into word [VAR(para_per_sector)]
+
+; 32-byte FAT directory entries per sector
+ shr bx, 1 ; /2 = 32-byte entries per sector
+%endif
+
+; number of sectors used for root directory (store in CX)
+ mov si, [VAR(num_root_dir_ents)]
+ mov ax, bx
+ ; The ax value here is the last value of bx, which is set
+ ; by the shr instruction. Therefore, it cannot be higher
+ ; than 7FFFh, so this cwd instruction always zeros dx.
+ cwd
+ dec ax ; rounding up
+ add ax, si ; from BPB
+ adc dx, dx ; account for overflow (dx was zero)
+ div bx ; get number of root sectors
+ xchg ax, cx ; cx = number of root secs, ! ah = 0
+
+; first sector of root directory
+ mov al,[VAR(num_fats)] ; ! ah = 0, hence ax = number of FATs
+ mul word [VAR(sectors_per_fat)]
+ add ax,[VAR(num_reserved_sectors)]
+ adc dl, dh ; account for overflow (dh was and is 0)
+
+ xor di, di
+
+; first sector of disk data area:
+ add cx, ax
+ adc di, dx
+ mov [VAR(data_start)], cx
+ mov [VAR(data_start+2)], di
+
+next_dir_search:
+%if _ADD_SEARCH
+ push dx
+ push ax
+ push si
+%endif
+
+; Scan root directory for file. We don't bother to check for deleted
+; entries (E5h) or entries that mark the end of the directory (00h).
+ ; number of root entries in si here
+next_sect:
+ mov cx, bx ; entries per sector as loop counter
+%if ! _RELOCATE
+ %if ADR_DIRBUF == ADR_FATBUF
+ call read_sector_dirbuf
+ %else
+ mov bx, ADR_DIRBUF>>4
+ call read_sector
+ %endif
+%else
+ mov bx, ADR_DIRBUF>>4
+ call read_sector
+%endif
+ mov bx, cx ; restore bx for next iteration later
+
+ xor di, di ; es:di-> first entry in this sector
+next_ent:
+ push si
+ push di
+ push cx
+%if _ADD_SEARCH
+ mov si, [VAR(filename)]
+%else
+ mov si, load_name ; ds:si-> name to match
+%endif
+ mov cx, 11 ; length of padded 8.3 FAT filename
+ repe cmpsb ; check entry
+ pop cx
+ pop di
+ pop si
+ lea di, [di + DIRENTRY_size]
+ je found_it ; found entry -->
+
+ dec si ; count down entire root's entries
+ loopnz next_ent ; count down sector's entries (jumps iff si >0 && cx >0)
+ jnz next_sect ; (jumps iff si >0 && cx ==0)
+ ; ends up here iff si ==0
+ ; ie all root entries checked unsuccessfully
+%if 0
+
+qemu prior to 2020-08 has a bug which affects the above
+conditionals. The bug is that if NZ is set (like when the
+branch to the label found_it is not taken) and then another
+instruction sets ZR (like the dec si at the end of the root
+directory) and then loopnz is used which sets cx to zero
+then after the loopnz FL will be NZ leading to the jnz branch
+to be taken. Eventually the entire load unit is traversed and
+qemu returns error 01h when trying to read past the end of
+the unit (at least for 1440 KiB diskettes).
+
+The bug can be worked around in two ways as done by lDebug:
+
+https://hg.pushbx.org/ecm/ldebug/rev/c95e2955bbca
+
+https://hg.pushbx.org/ecm/ldebug/rev/c84047f15d9c
+
+However, both cost a few bytes each. Therefore the proper
+fix is considered to be updating qemu. Error behaviour occurs
+when a file is not found. In an unlikely case, another sector
+in the data area may hold a match for the searched entry.
+Otherwise, the read eventually fails and the loader aborts
+with an R error (instead of the expected F error).
+
+Reference: https://bugs.launchpad.net/qemu/+bug/1888165
+
+%endif
+
+ mov al,'F' ; File not 'F'ound
+ jmp error
+
+found_it:
+%if _ADD_SEARCH || _LOAD_DIR_SEG
+ mov cx, 32
+ mov ax, _LOAD_DIR_SEG
+ sub di, cx ; es:di -> dir entry
+ %if _ADD_SEARCH
+ xchg ax, word [VAR(dirseg)]
+ %endif
+ push ds
+ mov si, di
+ push di
+ push es
+ pop ds ; ds:si -> dir entry
+ mov es, ax
+ xor di, di ; es:di -> destination
+ rep movsb ; store dir entry
+ push ds
+ pop es
+ pop di ; restore es:di -> dir entry
+ pop ds
+ %if _ADD_SEARCH
+%if ((load_name - start) & 0FF00h) == ((add_name - start) & 0FF00h)
+ mov byte [VAR(filename)], (load_name - start + 7C00h) & 255
+%else
+ mov word [VAR(filename)], load_name
+%endif
+ ; update name to second iteration's
+%if (_LOAD_DIR_SEG & 255) != (_ADD_DIR_SEG & 255)
+ cmp al, _ADD_DIR_SEG & 255
+%elif (_LOAD_DIR_SEG) != (_ADD_DIR_SEG)
+ cmp ax, _ADD_DIR_SEG ; was first iteration ?
+%else
+ %error Must not store directory entries to same segment
+%endif
+ pop si
+ pop ax
+ pop dx ; restore root start and count
+ ; (bx still holds entries per sector)
+ je next_dir_search ; jump to search load file next -->
+ %endif
+ %if _RELOCATE
+ push word [es:di + deClusterLow]
+ ; (word on stack) = first cluster number
+ %endif
+%else
+ %if _DIR_ENTRY_500 ; +24 bytes, probably
+ mov cx, 32
+ push ds
+ push es
+ xchg si, di
+ sub si, cx
+ push es
+ pop ds ; ds:si -> directory entry
+ xor ax, ax
+ mov es, ax
+ mov di, 500h ; es:di -> 0:500h
+ %if _DIR_ENTRY_520
+ rep movsw ; move to here (two directory entries)
+ %else
+ rep movsb ; move to here
+ %endif
+ pop es
+ pop ds
+ xchg si, di ; es:di -> behind (second) directory entry
+ %endif
+ %if _RELOCATE
+ push word [es:di + deClusterLow - DIRENTRY_size \
+ - (DIRENTRY_size * !!_DIR_ENTRY_520)]
+ ; (word on stack) = first cluster number
+ %endif
+%endif
+
+%if _RELOCATE || _LOAD_ADR >= ADR_FREE_FROM
+; Get conventional memory size and store it
+ int 12h
+ mov cl, 6
+ shl ax, cl
+ %if _RPL ; +31 bytes
+ xchg dx, ax
+ lds si, [4 * 2Fh]
+ add si, 3
+ lodsb
+ cmp al, 'R'
+ jne .no_rpl
+ lodsb
+ cmp al, 'P'
+ jne .no_rpl
+ lodsb
+ cmp al, 'L'
+ jne .no_rpl
+ mov ax, 4A06h
+ int 2Fh
+.no_rpl:
+ push ss
+ pop ds
+ xchg ax, dx
+ %endif
+ %if _RELOCATE
+ %if _LOAD_NON_FAT
+ sub ax, (_RPL_GRACE_AREA + 20 * 1024 \
+ + 512 + 7C00h) >> 4
+ %else
+ sub ax, (_RPL_GRACE_AREA + 20 * 1024 \
+ + 8192 + (8192-16) + 512 + 7C00h) >> 4
+ %endif
+ ; RPL grace area preserved for RPL
+ ; 20 KiB: reserved for iniload
+ ; 8 KiB: FAT buffer
+ ; 8 KiB - 16 B: to allow rounding down FAT buffer position
+ ; 512: sector
+ ; 7C00h: stack and to allow addressing with 7C00h in bp
+ ;
+ ; Note also that by addressing the stack and sector
+ ; with bp at 7C00h, and insuring this subtraction doesn't
+ ; underflow, makes sure that we do not overwrite the IVT or
+ ; BDA. (However, we assume that ax is at least 60h or so.)
+ ;
+ ; The FAT buffer segment is masked so that the actual buffer
+ ; is stored on an 8 KiB boundary. This is to ensure that
+ ; the buffer doesn't possibly cross a 64 KiB DMA boundary.
+ jc .error_memory_j_CY
+ cmp ax, (end -start+7C00h - ADR_STACK_LOW + 15) >> 4
+ ; This check is to ensure that the start of the destination
+ ; for the relocation (stack, sector) is
+ ; above-or-equal the end of the source for the relocation.
+ ; That is, to avoid overwriting any of the source with the
+ ; string move instruction (which for simplicity is always UP).
+.error_memory_j_CY:
+ jb error_memory
+ %if ! _LOAD_NON_FAT
+ mov bx, ((8192 - 16) + 512 + 7C00h)>>4
+ add bx, ax
+; this is like calculating the following for the bx value:
+; ((LMA_top - 20 KiB - 8 KiB - (8 KiB - 16 B) - 512 - 7C00h) + \
+; ((8 KiB - 16 B) + 512 + 7C00h))>>4
+; == (LMA_top - 20 KiB - 8 KiB)>>4
+ and bx, ~ ((8192>>4) - 1)
+ mov word [VAR(fat_seg)], bx
+ %endif
+
+ mov di, relocated
+ push ax
+ push di ; -> relocation target label relocated
+ ; (We cannot use a near call here to push the target IP
+ ; because we do not control whether the ROM-BIOS loader
+ ; entered us at 0:7C00h or 7C0h:0. So we need to create
+ ; the correct offset manually here.)
+
+ mov es, ax ; => destination
+ mov si, sp ; ds:si = ss:_RELOCATESTART - 2 - 4
+ mov di, si ; es:di -> destination for stack low
+ mov cx, (end - (_RELOCATESTART - 2 - 4)) >> 1
+ ; end is the top of used memory
+ ; _RELOCATESTART is the lowest filled stack frame slot
+ ; 2 is for the first cluster word on the stack
+ ; 4 is for the additional slots taken by the return address
+ rep movsw ; relocate stack, sector
+ retf ; jump to relocated code
+
+
+ readhandler
+
+ errorhandler
+
+
+relocated:
+ mov ss, ax
+ mov ds, ax ; relocate these
+ add ax, (ADR_STACK_LOW) >> 4 ; (rounding down) => behind available
+
+ pop si
+ %else
+ sub ax, (_RPL_GRACE_AREA + 20 * 1024) >> 4
+ ; RPL grace area, plus
+ ; 20 KiB reserved for iniload
+ jb error_memory
+ %endif
+%elif _LOAD_ADR < ADR_FREE_UNTIL
+ %if !_FIX_SECTOR_SIZE
+ mov ax, ADR_FREE_UNTIL >> 4 ; rounding *down*
+ %endif
+%else
+ %error Load address within used memory
+%endif
+%if ! _RELOCATE && _LOAD_ADR < ADR_FREE_UNTIL && _FIX_SECTOR_SIZE
+ ; user of last_available_sector will hardcode the value!
+%else
+ %if _FIX_SECTOR_SIZE
+ sub ax, _FIX_SECTOR_SIZE >> 4
+ %else
+ sub ax, [VAR(para_per_sector)]
+ %endif
+ push ax ; push into word [VAR(last_available_sector)]
+%endif
+
+; get starting cluster of file
+%if ! _RELOCATE
+ %if _ADD_SEARCH || _LOAD_DIR_SEG
+ mov si,[es:di + deClusterLow]
+ %else
+ mov si,[es:di + deClusterLow - DIRENTRY_size \
+ - (DIRENTRY_size * !!_DIR_ENTRY_520)]
+ %endif
+%endif
+%if _SET_CLUSTER
+ mov word [VAR(first_cluster)], si
+%endif
+%if _SET_DI_CLUSTER
+ push si ; remember cluster for later
+%endif
+
+%if !_FAT16 && !_LOAD_NON_FAT
+; Load the entire FAT into memory. This is easily feasible for FAT12,
+; as the FAT can only contain at most 4096 entries.
+; (The exact condition should be "at most 4087 entries", or with a
+; specific FF7h semantic, "at most 4088 entries"; the more reliable
+; and portable alternative would be "at most 4080 entries".)
+; Thus, no more than 6 KiB need to be read, even though the FAT size
+; as indicated by word[sectors_per_fat] could be much higher. The
+; first loop condition below is to correctly handle the latter case.
+; (Sector size is assumed to be a power of two between 32 and 8192
+; bytes, inclusive. An 8 KiB buffer is necessary if the sector size
+; is 4 or 8 KiB, because reading the FAT can or will write to 8 KiB
+; of memory instead of only the relevant 6 KiB. This is always true
+; if the sector size is 8 KiB, and with 4 KiB sector size it is true
+; iff word[sectors_per_fat] is higher than one.)
+ mov di, 6 << 10 ; maximum size of FAT12 to load
+ mov cx, [VAR(sectors_per_fat)]
+ ; maximum size of this FS's FAT
+%if ! _RELOCATE && _LOAD_ADR < ADR_FREE_UNTIL && !_FIX_SECTOR_SIZE
+ ; Under these conditions, ax here is
+ ; below ADR_FREE_UNTIL >> 4 so the
+ ; following cwd instruction zeros dx.
+ cwd
+%else
+ xor dx, dx
+%endif
+ mov ax, [VAR(fat_start)]; = first FAT sector
+ %if _RELOCATE
+ ; bx already = FAT buffer segment here
+ %else
+ mov bx, ADR_FATBUF>>4
+ %if _SET_FAT_SEG
+ mov word [VAR(fat_seg)], bx
+ %endif
+ %endif
+@@:
+ call read_sector ; read next FAT sector
+%if _FIX_SECTOR_SIZE
+ sub di, _FIX_SECTOR_SIZE
+%else
+ sub di, [VAR(bytes_per_sector)]
+%endif
+ ; di = bytes still left to read
+ jbe @F ; if none -->
+ ; (jbe means jump if CF || ZF)
+ loop @B ; if any FAT sector still remains -->
+@@: ; one of the limits reached; FAT read
+%endif
+
+ mov bx, _LOAD_ADR>>4 ; => load address
+%if _FAT16 && !_LOAD_NON_FAT
+ mov di, -1 ; = no FAT sector read yet
+ %if _SET_FAT_SEG
+ %if ! _RELOCATE
+ mov word [VAR(fat_seg)], ADR_FATBUF>>4
+ %endif
+ mov word [VAR(fat_sector)], di
+ %endif
+%endif
+%if _LOAD_NON_FAT
+ %if _SET_FAT_SEG
+ %if _FAT16
+ or word [VAR(fat_sector)], -1
+ %else
+ and word [VAR(fat_seg)], 0
+ %endif
+ %endif
+%endif
+
+next_cluster:
+; convert 16-bit cluster value (in SI) to 32-bit LBA sector value (in DX:AX)
+; and get next cluster in SI
+
+
+ ; Converts cluster number to sector number
+ ; and finds next cluster in chain
+ ;
+ ; INP: si = valid cluster number
+ ; (!_FAT16) [ADR_FATBUF] = entire FAT as read from FS
+ ; (_FAT16) di = currently buffered FAT sector, -1 if none
+ ; OUT: If unable to read a FAT sector,
+ ; ! jumps to error instead of returning
+ ; If everything is okay,
+ ; si = next cluster number (or EOC value)
+ ; dx:ax = sector number
+ ; (_FAT16) di = currently buffered FAT sector
+ ; CHG: cx
+
+%if ! _LOAD_NON_FAT
+; prepare to load entry from FAT
+
+%if _FAT16
+ push si ; preserve cluster number for later
+
+; Multiply cluster number by 2.
+ xchg ax, si
+ ; xor dx, dx ; dx:ax = entry to load (0..FFF6h)
+ ; add ax, ax
+ ; adc dx, dx ; dx:ax = byte offset into FAT (0..131052)
+ cwd ; dx = FFFFh if ax >= 8000h, else = 0
+ add ax, ax ; ax = (2 * ax) & FFFFh
+ neg dx ; dx = 1 if 2 * ax overflowed, else = 0
+
+; Divide by sector size to split dx:ax into the remainder as the byte offset
+; into the FAT sector, and quotient as the sector offset into the FAT.
+ div word [VAR(bytes_per_sector)]
+ mov si, dx ; = byte offset in sector
+ ; dx = byte offset into sector (0..8190)
+ ; ax = sector offset into FAT (0..4095)
+
+; quotient in AX is FAT sector.
+; check the FAT buffer to see if this sector is already loaded
+; (simple disk cache; speeds things up a little --
+; actually, it speeds things up a lot)
+ cmp ax, di
+ je @F
+ mov di, ax
+ %if _SET_FAT_SEG
+ mov word [VAR(fat_sector)], ax
+ %endif
+
+; read the target FAT sector.
+ push bx
+ ; As noted above, the sector offset in ax here is
+ ; 0..4095 (ie, 4 Ki S * 32 B/S = 128 KiB of FAT data),
+ ; therefore this cwd instruction always zeros dx.
+ cwd
+ add ax, [VAR(fat_start)]
+ adc dx, dx ; (account for overflow)
+ %if _RELOCATE
+ mov bx, word [VAR(fat_seg)]
+ call read_sector
+ %else
+ call read_sector_fatbuf
+ %endif
+ pop bx
+@@:
+
+; get 16 bits from FAT
+ es lodsw
+ xchg ax, si
+ pop ax ; restore cluster number
+%else
+; FAT12 entries are 12 bits, bytes are 8 bits. Ratio is 3 / 2,
+; so multiply cluster number by 3 first, then divide by 2.
+ mov ax, si ; = cluster number (up to 12 bits set)
+ ; (remember cluster number in ax)
+ shl si, 1 ; = 2n (up to 13 bits set)
+ add si, ax ; = 2n+n = 3n (up to 14 bits set)
+ shr si, 1 ; si = byte offset into FAT (0..6129)
+ ; CF = whether to use high 12 bits
+ %if _RELOCATE
+ mov es, word [VAR(fat_seg)]
+ ; es lodsw
+ ; xchg ax, si
+ mov si, word [es:si]
+ %else
+; Use the calculated byte offset as an offset into the FAT
+; buffer, which holds all of the FAT's relevant data.
+ ; lea si, [ADR_FATBUF + si]
+ ; -> 16-bit word in FAT to load
+; get 16 bits from FAT
+ ; lodsw
+ ; xchg ax, si
+ mov si, [ADR_FATBUF + si]
+ ; = 16-bit word in FAT to load
+ %endif
+
+ mov cl, 4
+ jc @F ; iff CY after shift -->
+ shl si, cl ; shift up iff even entry
+@@:
+ shr si, cl ; shift down (clears top 4 bits)
+
+ ; (ax holds cluster number)
+%endif
+
+%else ; _LOAD_NON_FAT
+ mov di, _LOAD_NON_FAT
+ mov ax, word [VAR(adj_sectors_per_cluster)]
+ mov cx, ax
+ mul word [VAR(bytes_per_sector)]
+ test dx, dx
+ jnz @F
+ cmp ax, di
+ mov al, 'N'
+ jb error
+@@:
+ xchg ax, si
+%endif
+; adjust cluster number to make it 0-based
+ dec ax
+ dec ax
+
+%if ! _LOAD_NON_FAT
+%if _FIX_CLUSTER_SIZE
+ mov cx, _FIX_CLUSTER_SIZE
+%else
+ mov cx, [VAR(adj_sectors_per_cluster)]
+%endif
+%endif
+
+; convert from clusters to sectors
+ mul cx
+ add ax, [VAR(data_start)]
+ adc dx, [VAR(data_start)+2]
+ ; dx:ax = sector number
+
+; xxx - this will always load an entire cluster (e.g. 64 sectors),
+; even if the file is shorter than this
+@@:
+ %if _LOAD_ADR < ADR_FREE_UNTIL && _FIX_SECTOR_SIZE
+ cmp bx, (ADR_FREE_UNTIL >> 4) - (_FIX_SECTOR_SIZE >> 4)
+ %else
+ cmp bx, [VAR(last_available_sector)]
+ %endif
+%if _MEMORY_CONTINUE
+ ja @F
+%else
+ ja error_filetoobig
+%endif
+%if _FAT16 && ! _LOAD_NON_FAT
+ push es ; (must preserve ADR_FATBUF reference)
+%endif
+ call read_sector
+%if _FAT16 && ! _LOAD_NON_FAT
+ pop es
+%endif
+%if _SET_LOAD_SEG
+ mov [VAR(load_seg)], bx ; => after last read data
+%endif
+%if _LOAD_NON_FAT
+ sub di, word [VAR(bytes_per_sector)]
+ ja @B
+%else
+ loop @B
+
+%if _FAT16
+; FFF7h: bad cluster
+; FFF8h-FFFFh: end of cluster chain
+ cmp si, 0FFF7h
+%else
+; 0FF7h: bad cluster
+; 0FF8h-0FFFh: end of cluster chain
+ cmp si, 0FF7h
+%endif
+ jb next_cluster
+%endif ; _LOAD_NON_FAT
+@@:
+
+%if _LOAD_MIN_PARA
+ cmp bx, (_LOAD_ADR >> 4) + _LOAD_MIN_PARA
+ mov al, 'E'
+ jb error
+%endif
+
+%if _TURN_OFF_FLOPPY
+; turn off floppy motor
+ mov dx,3F2h
+ mov al,0
+ out dx,al
+%endif
+
+; Set-up registers for and jump to loaded file
+; Already: ss:bp-> boot sector containing BPB
+%if _CHECKVALUE
+CHECKLINEAR equ _LOAD_ADR + _CHECKOFFSET
+ %if ! _RELOCATE && CHECKLINEAR <= (64 * 1024 - 2)
+ cmp word [CHECKLINEAR], _CHECKVALUE
+ %else
+ mov ax, CHECKLINEAR >> 4
+ mov es, ax
+ cmp word [es:CHECKLINEAR & 15], _CHECKVALUE
+ %endif
+ mov al, 'V' ; check 'V'alue mismatch
+ jne error
+%endif
+%if _SET_DL_UNIT
+ mov dl, [VAR(boot_unit)]; set dl to unit
+%endif
+%if _SET_DI_CLUSTER && (_PUSH_DPT || _SET_DSSI_DPT)
+ pop cx
+%endif
+%if _DATASTART_HIDDEN
+ mov bx, [VAR(hidden_sectors + 0)]
+ mov ax, [VAR(hidden_sectors + 2)]
+ add word [VAR(data_start + 0)], bx
+ adc word [VAR(data_start + 2)], ax
+%endif
+%if _SET_BL_UNIT
+ %if _SET_DL_UNIT
+ mov bl, dl ; set bl to unit, too
+ %else
+ mov bl, [VAR(boot_unit)]; set bl to unit
+ %endif
+%endif
+%if _PUSH_DPT || _SET_DSSI_DPT
+ %ifn _SET_DSSI_DPT ; (implying that only _PUSH_DPT is set)
+ %if _RELOCATE
+ xor ax, ax
+ mov es, ax
+ mov di, 1Eh * 4
+ les si, [es:di]
+ push es
+ push si
+ push ax
+ push di
+ %else
+ mov di, 1Eh*4
+ les si, [di] ; -> original (also current) DPT
+ push es
+ push si ; original (also current) DPT address
+ push ss
+ push di ; 0000h:0078h (address of 1Eh IVT entry)
+ %endif
+ %else
+ %if _RELOCATE
+ xor ax, ax
+ mov ds, ax
+ %endif
+ mov di, 1Eh*4
+ lds si, [di] ; -> original (also current) DPT
+ %if _PUSH_DPT
+ push ds
+ push si ; original (also current) DPT address
+ %if _RELOCATE
+ push ax
+ push di
+ %else
+ push ss
+ push di ; 0000h:0078h (address of 1Eh IVT entry)
+ %endif
+ %endif
+ %endif
+%else
+ %if _RELOCATE && (_ZERO_ES || _ZERO_DS)
+ xor ax, ax
+ %endif
+%endif
+%if _RELOCATE
+ %if _ZERO_ES
+ mov es, ax
+ %endif
+ %if _ZERO_DS
+ mov ds, ax
+ %endif
+%endif
+
+%if _SET_AXBX_DATA
+ mov bx, [VAR(data_start)]
+ mov ax, [VAR(data_start+2)]
+%endif
+%if _SET_DI_CLUSTER
+ %if _PUSH_DPT || _SET_DSSI_DPT
+ mov di, cx
+ %else
+ pop di
+ %endif
+%endif
+%if ! _RELOCATE
+ %if _ZERO_ES
+ push ss
+ pop es
+ %endif
+%endif
+ ; ss:bp-> boot sector with BPB
+ jmp (_LOAD_ADR>>4)+_EXEC_SEG_ADJ:_EXEC_OFS
+
+
+%if ! _RELOCATE
+ errorhandler
+
+ readhandler
+%endif
+
+
+%if !_NO_LIMIT
+available:
+ _fill 508,38,start
+
+signatures:
+ dw 0
+; 2-byte magic bootsector signature
+ dw 0AA55h
+
+%assign num signatures-available
+%assign fatbits 12
+%if _FAT16
+ %assign fatbits 16
+%endif
+%warning FAT%[fatbits]: num bytes still available.
+%endif
+
+end:
diff --git a/test/ldosboot/boot32.asm b/test/ldosboot/boot32.asm
new file mode 100644
index 0000000..540596e
--- /dev/null
+++ b/test/ldosboot/boot32.asm
@@ -0,0 +1,1730 @@
+
+%if 0
+
+File system boot sector loader code for FAT32
+
+Adapted from 2002-11-26 fatboot.zip/fat12.asm,
+ released as public domain by Chris Giese
+
+Public domain by C. Masloch, 2012
+
+%endif
+
+
+%include "lmacros2.mac"
+
+%ifndef _MAP
+%elifempty _MAP
+%else ; defined non-empty, str or non-str
+ [map all _MAP]
+%endif
+
+ defaulting
+
+ strdef OEM_NAME, " lDOS"
+ strdef OEM_NAME_FILL, '_'
+ strdef DEFAULT_LABEL, "lDOS"
+ numdef VOLUMEID, 0
+ strdef FSIBOOTNAME, "FSIBOOT4"
+ ; used to set experimental name
+ ; strdef FSIBOOTNAME, "FSIBEX02"
+
+ strdef LOAD_NAME, "LDOS"
+ strdef LOAD_EXT, "COM" ; name of file to load
+ numdef LOAD_ADR, 02000h ; where to load
+ numdef LOAD_MIN_PARA, paras(1536)
+ numdef EXEC_SEG_ADJ, 0 ; how far cs will be from _LOAD_ADR
+ numdef EXEC_OFS, 400h ; what value ip will be
+ numdef CHECKOFFSET, 1020
+ numdef CHECKVALUE, "lD"
+ numdef LOAD_DIR_SEG, 0 ; => where to store dir entry (0 if nowhere)
+ numdef ADD_SEARCH, 0 ; whether to search second file
+ strdef ADD_NAME, ""
+ strdef ADD_EXT, "" ; name of second file to search
+ numdef ADD_DIR_SEG, 0 ; => where to store dir entry (0 if nowhere)
+
+ numdef QUERY_GEOMETRY, 1 ; query geometry via 13.08 (for CHS access)
+ numdef USE_PART_INFO, 1 ; use ds:si-> partition info from MBR, if any
+ numdef USE_AUTO_UNIT, 1 ; use unit passed from ROM-BIOS in dl
+ numdef RPL, 1 ; support RPL and do not overwrite it
+ numdef CHS, 1 ; support CHS (if it fits)
+ numdef LBA, 1 ; support LBA (if available)
+ numdef LBA_33_BIT, 1 ; support 33-bit LBA
+ numdef LBA_CHECK_NO_33, 1 ; else: check that LBA doesn't carry
+
+ numdef RELOCATE, 0 ; relocate the loader to top of memory
+ numdef SET_DL_UNIT, 0 ; if to pass unit in dl
+ numdef SET_BL_UNIT, 0 ; if to pass unit in bl as well
+ numdef SET_AXBX_DATA, 0 ; if to pass first data sector in ax:bx
+ numdef SET_DSSI_DPT, 0 ; if to pass DPT address in ds:si
+ numdef PUSH_DPT, 0 ; if to push DPT address
+ numdef MEMORY_CONTINUE, 1 ; if to just execute when memory full
+ numdef SET_SIDI_CLUSTER,0 ; if to pass first load file cluster in si:di
+ numdef TURN_OFF_FLOPPY, 0 ; if to turn off floppy motor after loading
+ numdef DATASTART_HIDDEN,0 ; if to add hidden sectors to data_start
+ numdef LBA_SET_TYPE, 0 ; if to set third byte to LBA partition type
+ numdef SET_LOAD_SEG, 1 ; if to set load_seg (word [ss:bp - 6])
+ numdef SET_FAT_SEG, 1 ; if to set fat_seg (word [ss:bp - 8])
+ numdef SET_CLUSTER, 1 ; if to set first_cluster (dword [ss:bp - 16])
+ numdef ZERO_ES, 0 ; if to set es = 0 before jump
+ numdef ZERO_DS, 0 ; if to set ds = 0 before jump
+
+ numdef FIX_SECTOR_SIZE, 0 ; fix sector size (0 = disable, else = sector size)
+ numdef FIX_SECTOR_SIZE_SKIP_CHECK, 0 ; don't check sector size
+ numdef FIX_CLUSTER_SIZE, 0 ; fix cluster size
+ numdef FIX_CLUSTER_SIZE_SKIP_CHECK, 0 ; don't check cluster size
+ numdef NO_LIMIT, 0 ; allow using more memory than a boot sector
+ ; also will not write 0AA55h signature!
+ numdef LBA_SKIP_CHECK, 1 ; don't use proper LBA extensions check
+ numdef LBA_RETRY, 0 ; retry LBA reads one time
+ numdef CHS_RETRY, 1 ; retry CHS reads one time
+ numdef CHS_RETRY_REPEAT,16 ; retry CHS reads multiple times
+ ; (value of the def is used as count)
+
+ ; Unlike the 1440 KiB diskette image defaults for the FAT12
+ ; loader we just fill the BPB with zeros by default.
+ numdef MEDIAID, 0 ; media ID
+ numdef UNIT, 0 ; load unit in BPB
+ numdef CHS_SECTORS, 0 ; CHS geometry field for sectors
+ numdef CHS_HEADS, 0 ; CHS geometry field for heads
+ numdef HIDDEN, 0 ; number of hidden sectors
+ numdef SPI, 0 ; sectors per image
+ numdef BPS, 0 ; bytes per sector
+ numdef SPC, 0 ; sectors per cluster
+ numdef SPF, 0 ; sectors per FAT
+ numdef SECTOR_FSINFO, 0 ; FSINFO sector
+ numdef SECTOR_BACKUP, 0 ; backup boot sector
+ numdef CLUSTER_ROOT, 0 ; root directory first cluster
+ numdef NUMFATS, 0 ; number of FATs
+ numdef NUMRESERVED, 0 ; number of reserved sectors
+
+ numdef COMPAT_FREEDOS, 0 ; partial FreeDOS load compatibility
+ numdef COMPAT_IBM, 0 ; partial IBMDOS load compatibility
+ numdef COMPAT_MS7, 0 ; partial MS-DOS 7 load compatibility
+ numdef COMPAT_LDOS, 0 ; lDOS load compatibility
+
+%if (!!_COMPAT_FREEDOS + !!_COMPAT_IBM + !!_COMPAT_MS7 + !!_COMPAT_LDOS) > 1
+ %error At most one set must be selected.
+%endif
+
+%if _COMPAT_FREEDOS
+ strdef LOAD_NAME, "KERNEL"
+ strdef LOAD_EXT, "SYS"
+ numdef LOAD_ADR, 00600h
+ numdef LOAD_MIN_PARA, paras(512)
+ numdef EXEC_SEG_ADJ, 0
+ numdef EXEC_OFS, 0
+ numdef CHECKVALUE, 0
+
+ numdef SET_BL_UNIT, 1
+ numdef MEMORY_CONTINUE, 0
+ numdef RELOCATE, 1
+ ; The FreeDOS load protocol mandates that the entire file be loaded.
+%endif
+
+%if _COMPAT_IBM
+ strdef LOAD_NAME, "IBMBIO"
+ strdef LOAD_EXT, "COM"
+ numdef LOAD_ADR, 00700h
+ numdef LOAD_MIN_PARA, paras(512)
+ numdef EXEC_SEG_ADJ, 0
+ numdef EXEC_OFS, 0
+ numdef CHECKVALUE, 0
+ numdef LOAD_DIR_SEG, 50h
+ numdef ADD_SEARCH, 1
+ strdef ADD_NAME, "IBMDOS"
+ strdef ADD_EXT, "COM"
+ numdef ADD_DIR_SEG, 52h
+ ; Note: The IBMBIO.COM directory entry must be stored at
+ ; 0:500h, and the IBMDOS.COM directory entry at 0:520h.
+
+ numdef SET_DL_UNIT, 1
+ numdef MEMORY_CONTINUE, 1
+ ; 3 sectors * 512 BpS should suffice. We load into 700h--7A00h,
+ ; ie >= 6000h bytes (24 KiB), <= 7300h bytes (28.75 KiB).
+ numdef SET_AXBX_DATA, 1
+ numdef DATASTART_HIDDEN,1
+ numdef SET_DSSI_DPT, 1
+ numdef PUSH_DPT, 1
+%endif
+
+%if _COMPAT_MS7
+ strdef LOAD_NAME, "IO"
+ strdef LOAD_EXT, "SYS"
+ numdef LOAD_ADR, 00700h
+ numdef LOAD_MIN_PARA, paras(1024)
+ numdef EXEC_SEG_ADJ, 0
+ numdef EXEC_OFS, 200h
+ numdef CHECKVALUE, 0
+
+ numdef SET_DL_UNIT, 1
+ numdef SET_DSSI_DPT, 0
+ numdef PUSH_DPT, 1
+ numdef MEMORY_CONTINUE, 1
+ ; 4 sectors * 512 BpS should suffice. We load into 700h--7A00h,
+ ; ie >= 6000h bytes (24 KiB), <= 7300h bytes (28.75 KiB).
+ numdef SET_SIDI_CLUSTER,1
+ numdef DATASTART_HIDDEN,1
+ numdef LBA_SET_TYPE, 1
+%endif
+
+%if _COMPAT_LDOS
+ strdef LOAD_NAME, "LDOS"
+ strdef LOAD_EXT, "COM"
+ numdef LOAD_ADR, 02000h
+ numdef LOAD_MIN_PARA, paras(1536)
+ numdef EXEC_SEG_ADJ, 0
+ numdef EXEC_OFS, 400h
+ numdef CHECKOFFSET, 1020
+ numdef CHECKVALUE, "lD"
+
+ numdef SET_DL_UNIT, 0
+ numdef SET_CLUSTER, 1
+ numdef SET_FAT_SEG, 1
+ numdef SET_LOAD_SEG, 1
+ numdef MEMORY_CONTINUE, 1
+ numdef DATASTART_HIDDEN,0
+%endif
+
+%if 0
+
+Notes about partial load compatibilities
+
+* FreeDOS:
+ * Relocates to an address other than 27A00h (1FE0h:7C00h)
+* IBMDOS:
+ * Does not actually relocate DPT, just provide its address
+* MS-DOS 7:
+ * Does not actually relocate DPT, just provide its address
+ * Does not contain message table used by loader
+
+%endif
+
+%if _SET_BL_UNIT && _SET_AXBX_DATA
+ %error Cannot select both of these options!
+%endif
+
+
+%assign LOADLIMIT 0A0000h
+%assign POSITION 07C00h
+
+%if _FIX_SECTOR_SIZE
+ %assign i 5
+ %rep 13-5
+ %if (1 << i) != (_FIX_SECTOR_SIZE)
+ %assign i i+1
+ %endif
+ %endrep
+ %if (1 << i) != (_FIX_SECTOR_SIZE)
+ %error Invalid sector size _FIX_SECTOR_SIZE
+ %endif
+%endif
+
+%if _FIX_CLUSTER_SIZE
+ %if _FIX_CLUSTER_SIZE > 256
+ %error Invalid cluster size _FIX_CLUSTER_SIZE
+ %endif
+ %assign i 0
+ %rep 8-0
+ %if (1 << i) != (_FIX_CLUSTER_SIZE)
+ %assign i i+1
+ %endif
+ %endrep
+ %if (1 << i) != (_FIX_CLUSTER_SIZE)
+ %warning Non-power-of-two cluster size _FIX_CLUSTER_SIZE
+ %endif
+%endif
+
+
+%if (_LOAD_ADR & 0Fh)
+ %error Load address must be on a paragraph boundary
+%endif
+
+%if _LOAD_ADR > LOADLIMIT
+ %error Load address must be in LMA
+%elif _LOAD_ADR < 00500h
+ %error Load address must not overlap IVT or BDA
+%endif
+%if ! _RELOCATE
+ %if _LOAD_ADR > (POSITION-512) && _LOAD_ADR < (POSITION+1024)
+ %error Load address must not overlap loader
+ %endif
+%endif
+
+%if ((_EXEC_SEG_ADJ<<4)+_EXEC_OFS) < 0
+ %error Execution address must be in loaded file
+%elif ((_EXEC_SEG_ADJ<<4)+_EXEC_OFS+_LOAD_ADR) > LOADLIMIT
+ %error Execution address must be in LMA
+%endif
+
+%if (_EXEC_OFS & ~0FFFFh)
+ %error Execution offset must fit into 16 bits
+%endif
+
+%if (_EXEC_SEG_ADJ > 0FFFFh || _EXEC_SEG_ADJ < -0FFFFh)
+ %error Execution segment adjustment must fit into 16 bits
+%endif
+
+
+%if !_CHS && _QUERY_GEOMETRY
+ %warning No CHS support but querying geometry anyway
+%endif
+
+%if !_CHS && !_LBA
+ %error Either CHS or LBA or both must be enabled
+%endif
+
+
+%if 0
+
+There is some logic inside MS-DOS's hard disk partition initialisation
+code that sets up several requirements for us to fulfil. Otherwise,
+it will not accept the information given in the BPB (using default
+information based on the length as specified by MBR/EPBR instead) or
+make the whole file system inaccessible except for formatting. Both of
+those are undesirable of course. Some/all(?) checks are documented on
+pages 601,602 in "DOS Internals", Geoff Chappell 1994, as follows:
+
+* First three bytes contain either "jmp sho xx\ nop" or "jmp ne xx".
+* Media descriptor field >= 0F0h.
+* Bytes per sector field == 512.
+* Sectors per cluster field a power of 2 (1,2,4,8,16,32,64,128).
+* OEM name "version" (last three to four characters)
+ * must be "20.?", "10.?" (? = wildcard), but no other with "0.?",
+ * otherwise, must be "2.0", or
+ * 2nd-to-last,3rd-to-last character codes together > "3.", or
+ * those == "3.", last character code > "0"
+
+To stay compatible to those, display a warning here if the name
+itself would disqualify our boot sector already.
+
+%endif
+
+%push
+%strlen %$len _OEM_NAME_FILL
+%if %$len != 1
+ %error Specified OEM name fill must be 1 character
+%endif
+%strlen %$len _OEM_NAME
+%define %$nam _OEM_NAME
+%if %$len > 8
+ %error Specified OEM name is too long
+%else
+ %assign %$warn 0
+ %rep 8 - %$len
+ %strcat %$nam %$nam,_OEM_NAME_FILL
+ %endrep
+ %substr %$prefix %$nam 5 ; "wvxyZa.b", get "Z"
+ %substr %$major %$nam 6,7 ; "wvxyzA.b", get "A."
+ %substr %$minor %$nam 8 ; "wvxyza.B", get "B"
+ %if %$major == "0."
+ %ifn %$prefix == "1" || %$prefix == "2"
+ %assign %$warn 1
+ %endif
+ %elifn %$major == "2." && %$minor == "0"
+ %if %$major < "3."
+ %assign %$warn 1
+ %elif %$major == "3." && %$minor < "1"
+ %assign %$warn 1
+ %endif
+ %endif
+ %if %$warn
+ %warning Specified OEM name fails MS-DOS's validation
+ %endif
+%endif
+%pop
+
+; 512-byte stack (minus the variables).
+ADR_STACK_LOW equ 7C00h - 200h ; 07A00h
+
+ADR_FSIBOOT equ end -start+7C00h ; 07E00h
+
+%define _AFTER (ADR_FSIBOOT + 512)
+%if _RELOCATE
+ ; dynamic allocation of FAT buffer
+%else
+ %if _LOAD_ADR < 7C00h
+ ADR_FATBUF equ _AFTER
+ %define _AFTER (ADR_FATBUF + 8192)
+ %else
+ ADR_FATBUF equ 2000h
+ %endif
+%endif
+
+%if _ADD_SEARCH
+ %if _RELOCATE
+ ; dynamic allocation of dir buffer
+ %elif _LOAD_ADR < 7C00h
+ ADR_DIRBUF equ _AFTER
+ %define _AFTER (ADR_DIRBUF + 8192)
+ %else
+ ADR_DIRBUF equ 4000h
+ %endif
+%else
+ %if _RELOCATE
+ ADR_DIRBUF equ _LOAD_ADR ; 00600h
+ %elif _LOAD_ADR < 7C00h
+ ; one-sector directory buffer. Assumes sectors are no larger than 8 KiB
+ ADR_DIRBUF equ _AFTER ; 0A000h
+ %define _AFTER (ADR_DIRBUF + 8192)
+ %else
+ ADR_DIRBUF equ 4000h
+ %endif
+%endif
+ADR_FREE equ _AFTER ; 08000h
+
+%if ! _RELOCATE
+ %if ((ADR_FATBUF + 8192 - 1) & ~0FFFFh) != (ADR_FATBUF & ~0FFFFh)
+ %warning Possibly crossing 64 KiB boundary while reading FAT
+ %endif
+%endif
+
+%ifn _RELOCATE && _ADD_SEARCH
+ %if ((ADR_DIRBUF + 8192 - 1) & ~0FFFFh) != (ADR_DIRBUF & ~0FFFFh)
+ %warning Possibly crossing 64 KiB boundary while reading directory
+ %endif
+%endif
+
+%if _LOAD_ADR >= ADR_FREE || _RELOCATE
+ ; If reading on a sector size boundary, no crossing can occur.
+ ; Check for all possible sector sizes (32 B to 8 KiB). If one
+ ; of them fails display a warning, including the minimum size.
+ %assign SECSIZECHECK 32
+ %assign EXITREP 0
+ %rep 256
+ %ifn EXITREP
+ %if _LOAD_ADR & (SECSIZECHECK - 1)
+ %warning Possibly crossing 64 KiB boundary while reading file (sector size >= SECSIZECHECK)
+ %assign EXITREP 1
+ %exitrep
+ %endif
+ %if SECSIZECHECK == 8192
+ %assign EXITREP 1
+ %exitrep
+ %endif
+ %assign SECSIZECHECK SECSIZECHECK * 2
+ %endif
+ %endrep
+%else
+ ; If loading below the boot sector, address 1_0000h is never reached.
+%endif
+
+
+ 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
+
+ struc FSIBOOTG ; FSIBOOT general layout
+.signature: resq 1 ; 8 bytes that identify the FSIBOOT type
+.fsicode: resb 470 ; 470 bytes FSIBOOT type specific data or code
+.dirsearch: resw 1 ; 1 word -> directory search entrypoint
+ endstruc
+
+ struc DIRENTRY
+deName: resb 8
+deExt: resb 3
+deAttrib: resb 1
+dePlusSize: resb 1
+ resb 7
+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
+
+
+; use byte-offset addressing from BP for smaller code
+%define VAR(x) ((x) - start) + bp
+
+
+ cpu 8086
+; bootsector loaded at address 07C00h, addressable using 0000h:7C00h
+ org POSITION
+start:
+
+
+%define _LASTVARIABLE start
+ %macro nextvariable 2.nolist
+%1 equ (_LASTVARIABLE - %2)
+%define _LASTVARIABLE %1
+ %endmacro
+
+; Variables
+
+; (dword) sector where the first cluster's data starts
+ nextvariable data_start, 4
+
+; (word) current load segment (points behind last loaded data)
+ nextvariable load_seg, 2
+
+; (word) segment of FAT buffer
+; for FAT12 this holds the entire FAT
+; for FAT16 this holds the sector given by wo[fat_sector]
+; for FAT32 this holds the sector given by dwo[fat_sector]
+ nextvariable fat_seg, 2
+
+; (dword for FAT32) currently loaded sector-in-FAT, -1 if none
+ nextvariable fat_sector, 4
+
+; (dword for FAT32) first cluster of load file
+ nextvariable first_cluster, 4
+
+ADR_STACK_START equ _LASTVARIABLE -start+POSITION
+
+; (word) number of 16-byte paragraphs per sector
+ nextvariable para_per_sector, 2
+
+; (word) number of 32-byte directory entries per sector
+ nextvariable entries_per_sector, 2
+
+; (word) segment of last available memory for sector
+ nextvariable last_available_sector, 2
+
+; (word) actual sectors per cluster
+ nextvariable adj_sectors_per_cluster, 2
+
+; (word) paragraphs left to read
+ nextvariable paras_left, 2
+
+lowest_variable equ _LASTVARIABLE
+
+
+ jmp strict short skip_bpb
+%if !_CHS && _LBA_SET_TYPE
+ db 0Ch ; LBA-enabled FAT32 FS partition type
+%else
+ nop ; default: no LBA
+%endif
+
+
+; BIOS Parameter Block (BPB)
+;
+; Installation will use the BPB already present in your file system.
+; These values must be initialised when installing the loader.
+
+oem_id: ; offset 03h (03) - not used by this code
+ fill 8,_OEM_NAME_FILL,db _OEM_NAME
+bytes_per_sector: ; offset 0Bh (11) - refer to _FIX_SECTOR_SIZE !
+ dw _BPS
+sectors_per_cluster: ; offset 0Dh (13) - refer to _FIX_CLUSTER_SIZE !
+ db _SPC & 255
+fat_start:
+num_reserved_sectors: ; offset 0Eh (14)
+ dw _NUMRESERVED
+num_fats: ; offset 10h (16)
+ db _NUMFATS
+num_root_dir_ents: ; offset 11h (17)
+ dw 0
+total_sectors: ; offset 13h (19) - not used by this code
+%if _SPI < 1_0000h
+ dw _SPI
+%else
+ dw 0
+%endif
+media_id: ; offset 15h (21) - not used by this code
+ db _MEDIAID
+sectors_per_fat: ; offset 16h (22)
+ dw 0
+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
+%if _SPI >= 1_0000h
+ dd _SPI
+%else
+ dd 0
+%endif
+
+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 _CLUSTER_ROOT
+fsinfo_sector: ; offset 30h (48)
+ dw _SECTOR_FSINFO
+backup_sector: ; offset 32h (50)
+ dw _SECTOR_BACKUP
+
+ times 12 db 0 ; offset 34h (52) reserved
+
+; Extended BPB ; offset 40h (64)
+
+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: fill 8,32,db "FAT32"
+
+
+; Initialised data
+
+ align 2
+fsiboot_table: ; this table is used by the FSIBOOT stage
+.error: dw error
+ ; INP: al = error condition letter
+ ; ('B' = bad chain / FS error, 'F' = file not found,
+ ; 'R' = disk read error, 'M' = not enough memory,
+ ; 'E' = not enough data in file)
+.success: dw load_finish
+ ; INP: dword [ss:sp] = first cluster
+ ; Note: The first cluster dword is always filled in
+ ; by FSIBOOT; the option _SET_SIDI_CLUSTER only
+ ; affects usage in the primary loader.
+%if _MEMORY_CONTINUE
+.memory_full: dw load_finish_mc
+ ; INP: al = error condition letter ('M'),
+ ; dword [ss:sp] = current cluster number,
+ ; dword [ss:sp + 4] = first cluster, refer to .success
+%else
+.memory_full: dw error
+ ; refer to previous .memory_full comment
+%endif
+.read_sector: dw read_sector
+ ; INP: dx:ax = sector number within partition,
+ ; bx => buffer, (_LBA) ds = ss
+ ; OUT: dx:ax incremented, bx => incremented,
+ ; es = input bx, does not return if error occurred
+ ; CHG: none
+%if _RELOCATE && _ADD_SEARCH
+.dirbuf: dw 8192 >> 4
+ ; initialised to segment displacement from FAT buffer
+%else
+.dirbuf: dw ADR_DIRBUF>>4
+ ; => directory sector buffer (one sector)
+%endif
+.writedirentry: dw writedirentry.loaddir
+.filename: dw .load_name
+ ; -> name to search
+.minpara: dw _LOAD_MIN_PARA
+.loadseg: dw _LOAD_ADR>>4
+ ; => where to load
+
+.load_name: ; = blank-padded 11-byte filename to search for
+ fill 8,32,db _LOAD_NAME
+ fill 3,32,db _LOAD_EXT
+
+fsiboot_name:
+ fill 8, 32, db _FSIBOOTNAME
+
+
+; Code
+
+ ; Note that this may be entered with cs:ip = 07C0h:0 !
+skip_bpb:
+ cli
+ cld
+ xor cx, cx
+ mov bp, start ; magic bytes - checked by instsect
+ mov ss, cx
+ mov sp, ADR_STACK_START
+%if _USE_AUTO_UNIT
+ mov [VAR(boot_unit)], dl; magic bytes - checked by instsect
+%else
+ mov dl, [VAR(boot_unit)]; magic bytes - checked by instsect
+%endif
+
+ ; Note: es is left uninitialised here until the first call to
+ ; read_sector if the below conditional is false.
+%if _USE_PART_INFO ; +19 bytes
+ mov es, cx
+; Note: Award Medallion BIOS v6.0 (ASUS MED 2001 ACPI BIOS Revision 1009)
+; loads from a floppy disk drive with ds:si = 0F000h:0A92Dh ->
+; FF FF FF FF 08 00 08 01 FF FF FF FF FF FF FF FF, which was detected
+; as a valid partition table entry by this handling. Therefore, we
+; only accept partition information when booting from a hard disk now.
+ test dl, dl ; floppy ?
+ jns @F ; don't attempt detection -->
+; Check whether an MBR left us partition information.
+; byte[ds:si] bit 7 means active and must be set if valid.
+ cmp byte [si], cl ; flags for xx-00h (result is xx), SF = bit 7
+ jns @F ; xx < 80h, ie info invalid -->
+; byte[ds:si+4] is the file system type. Check for valid one.
+ cmp byte [si+4], cl ; is it zero?
+ je @F ; yes, info invalid -->
+; Info valid, trust their hidden sectors over hardcoded.
+; Assume the movsw instructions won't run with si = FFFFh.
+ mov di, hidden_sectors ; -> BPB field
+ add si, 8 ; -> partition start sector in info
+ movsw
+ movsw ; overwrite BPB field with value from info
+@@:
+%endif
+ mov ds, cx
+ sti
+
+
+%if _QUERY_GEOMETRY ; +27 bytes
+; test dl, dl ; floppy?
+; jns @F ; don't attempt query, might fail -->
+ ; Note that while the original PC BIOS doesn't support this function
+ ; (for its diskettes), it does properly return the error code 01h.
+ ; https://sites.google.com/site/pcdosretro/ibmpcbios (IBM PC version 1)
+ mov ah, 08h
+ ; xor cx, cx ; initialise cl to 0
+ ; Already from prologue cx = 0.
+ stc ; initialise to CY
+ int 13h ; query drive geometry
+ jc @F ; apparently failed -->
+ and cx, 3Fh ; get sectors
+ jz @F ; invalid (S is 1-based), don't use -->
+ mov [VAR(sectors_per_track)], cx
+ mov cl, dh ; cx = maximum head number
+ inc cx ; cx = number of heads (H is 0-based)
+ mov [VAR(heads)], cx
+@@:
+%endif
+
+
+; 16-byte paragraphs per sector
+ mov ax, [VAR(bytes_per_sector)]
+ mov cl, 4
+ shr ax, cl
+ push ax ; push into word [VAR(para_per_sector)]
+
+; 32-byte FAT directory entries per sector
+ shr ax, 1 ; /2 = 32-byte entries per sector
+ push ax ; push into word [VAR(entries_per_sector)]
+
+load_fsiboot:
+ cmp ax, 1024 >> 5 ; sector size is at least 1 KiB (large) ?
+ jae .loaded ; already loaded as part of the boot sector -->
+
+ ; If this code runs, then ax was below 1024 >> 5,
+ ; therefore this cwd instruction always zeros dx.
+ cwd
+
+ mov ax, [VAR(fsinfo_sector)]
+; inc ax
+; jz error_fsiboot ; was FFFFh, invalid -->
+; dec ax
+ ; FFFFh always fails the < num_reserved_sectors check
+ test ax, ax
+ jz @F ; is 0 ? invalid --> (ZR, NC)
+ cmp ax, [VAR(num_reserved_sectors)]
+@@: ; (ZR, NC if ax == 0)
+ jae error_fsiboot ; (jump if NC)
+ ; dx:ax = FSINFO sector (dx = 0 from cwd)
+ mov cx, 512
+ mov bx, ADR_FSIBOOT >> 4
+@@:
+ call read_sector
+ sub cx, [VAR(bytes_per_sector)]
+ ja @B ; read 512 bytes -->
+
+.loaded:
+ push ds
+ pop es
+ mov di, fsiboot.signature
+ mov si, fsiboot_name
+ mov cx, 4 ; size of fsiboot_name
+ repe cmpsw
+ jne error_fsiboot
+ ; Note that now es:di -> fsiboot.start
+
+%if _LOAD_ADR >= ADR_FREE || _RELOCATE
+; Get conventional memory size and store it
+ int 12h
+ mov cl, 6
+ shl ax, cl
+ %if _RPL ; +33 bytes
+ xchg dx, ax
+ lds si, [4 * 2Fh]
+ add si, 3
+ lodsb
+ cmp al, 'R'
+ jne .no_rpl
+ lodsb
+ cmp al, 'P'
+ jne .no_rpl
+ lodsb
+ cmp al, 'L'
+ jne .no_rpl
+ mov ax, 4A06h
+ int 2Fh
+.no_rpl:
+ push ss
+ pop ds
+ xchg ax, dx
+ %endif
+ %if _RELOCATE
+ %if _ADD_SEARCH
+ sub ax, (20 * 1024 + 8192 + 8192 + (8192-16) + 1024 + 7C00h) >> 4
+ %else
+ sub ax, (20 * 1024 + 8192 + (8192-16) + 1024 + 7C00h) >> 4
+ %endif
+ ; 20 KiB: reserved for iniload
+ ; 8 KiB: dir buffer (only if _ADD_SEARCH)
+ ; 8 KiB: FAT buffer
+ ; 8 KiB - 16 B: to allow rounding down position of buffers
+ ; 1 KiB: sector + FSIBOOT
+ ; 7C00h: stack and to allow addressing with 7C00h in bp
+ ;
+ ; Note also that by addressing the stack and sector and FSIBOOT
+ ; with bp at 7C00h, and insuring this subtraction doesn't
+ ; underflow, makes sure that we do not overwrite the IVT or
+ ; BDA. (However, we assume that ax is at least 60h or so.)
+ ;
+ ; The FAT buffer segment is masked so that the actual buffer
+ ; is stored on an 8 KiB boundary. This is to ensure that
+ ; the buffer doesn't possibly cross a 64 KiB DMA boundary.
+ jc error_fsiboot
+ cmp ax, (end_after_fsiboot -start+7C00h - ADR_STACK_LOW + 15) >> 4
+ ; This check is to ensure that the start of the destination
+ ; for the relocation (stack, sector, FSIBOOT) is
+ ; above-or-equal the end of the source for the relocation.
+ ; That is, to avoid overwriting any of the source with the
+ ; string move instruction (which for simplicity is always UP).
+ jb error_fsiboot
+
+ ; With _RELOCATE enabled, the FAT buffer segment
+ ; is fixed up by the primary loader here before
+ ; FSIBOOT is executed.
+ mov bx, ((8192 - 16) + 1024 + 7C00h)>>4
+; bx is initialised to the value
+; ((8192 - 16) + 1024 + 7C00h)>>4
+; so this is like calculating the following for its value:
+; ((LMA_top - 20 KiB - 8 KiB - 8 KiB{AS} - (8 KiB - 16 B) - 1 KiB - 7C00h) + \
+; ((8 KiB - 16 B) + 1 KiB + 7C00h))>>4
+; == (LMA_top - 20 KiB - 8 KiB - 8 KiB{AS})>>4
+; {AS} = only if _ADD_SEARCH
+ add bx, ax
+ and bx, ~ ((8192>>4) - 1) ; => FAT sector buffer (one sector)
+ %if _ADD_SEARCH
+; .dirbuf is initialised to 8192 >> 4
+ add word [VAR(fsiboot_table.dirbuf)], bx
+ ; => dir sector buffer (one sector)
+ %endif
+
+ push ax
+ push di ; -> reloc destination of fsiboot.start
+
+ mov es, ax ; => destination
+ mov si, sp ; ds:si = ss:entries_per_sector - 4
+ mov di, si ; es:di -> destination for stack low
+ mov cx, (end_after_fsiboot - (entries_per_sector - 4)) >> 1
+ ; end_after_fsiboot is the top of used memory
+ ; entries_per_sector is the lowest filled stack frame slot
+ ; 4 is for the additional slots taken by the return address
+ rep movsw ; relocate stack, sector, and FSIBOOT
+ mov ss, ax
+ mov ds, ax ; relocate these
+ add ax, (ADR_STACK_LOW) >> 4 ; (rounding down) => behind available
+
+ retf ; jump to relocated FSIBOOT
+ %else
+ sub ax, (20 * 1024) >> 4 ; 20 KiB reserved for iniload
+ jc error_fsiboot
+ mov bx, ADR_FATBUF >> 4 ; => FAT sector buffer (one sector)
+ push es
+ push di ; -> fsiboot.start
+ retf
+ ; Do a far return here to ensure that cs is zero.
+ %endif
+%elif _LOAD_ADR < ADR_STACK_LOW
+ mov ax, (ADR_STACK_LOW >> 4)
+ mov bx, ADR_FATBUF >> 4 ; => FAT sector buffer (one sector)
+ push es
+ push di ; -> fsiboot.start
+ retf
+ ; Do a far return here to ensure that cs is zero.
+%else
+ %error Load address within used memory
+%endif
+
+
+ ; INP: es:bx -> found dir entry in dir sector buffer
+ ; si:di = loaded sector-in-FAT
+ ; CHG: ax, cx, dx
+ ; STT: ss:bp -> boot sector
+ ; ds = ss
+ ; UP
+ ; OUT: directory entry copied if so desired
+ ; (is a no-op if not to copy dir entry)
+writedirentry:
+%if _LOAD_DIR_SEG
+.loaddir:
+ mov ax, _LOAD_DIR_SEG
+%else
+.loaddir: equ read_sector.retn
+%endif
+%if _LOAD_DIR_SEG || (_ADD_DIR_SEG && _ADD_SEARCH)
+ ; INP: ax => where to store directory entry
+.ax:
+ push ds
+ push si
+ push di
+ push es
+ pop ds
+ mov si, bx ; ds:si -> directory entry
+ mov cx, DIRENTRY_size >> 1
+ mov es, ax
+ xor di, di ; es:di -> where to store directory entry
+ rep movsw ; move to here (one directory entry)
+ push ds
+ pop es ; es:bx -> dir entry in dir sector buffer
+ pop di
+ pop si
+ pop ds
+ retn
+%endif
+
+
+%if _MEMORY_CONTINUE
+load_finish_mc:
+ pop bx
+ pop cx
+%endif
+load_finish:
+
+%if _ADD_SEARCH
+ mov word [VAR(fsiboot_table.filename)], add_name
+ call near word [dirsearch_entrypoint]
+ %if _ADD_DIR_SEG
+ mov ax, _ADD_DIR_SEG
+ call writedirentry.ax
+ %endif
+%endif
+
+%if _TURN_OFF_FLOPPY
+; turn off floppy motor
+ mov dx,3F2h
+ mov al,0
+ out dx,al
+%endif
+
+; Set-up registers for and jump to loaded file
+; Already: ss:bp-> boot sector containing BPB
+%if _CHECKVALUE
+CHECKLINEAR equ _LOAD_ADR + _CHECKOFFSET
+ %if CHECKLINEAR <= (64 * 1024 - 2) && ! _RELOCATE
+ cmp word [CHECKLINEAR], _CHECKVALUE
+ %else
+ mov ax, CHECKLINEAR >> 4
+ mov es, ax
+ cmp word [es:CHECKLINEAR & 15], _CHECKVALUE
+ %endif
+ mov al, 'V' ; check 'V'alue mismatch
+ jne error
+%endif
+%if _SET_SIDI_CLUSTER && (_PUSH_DPT || _SET_DSSI_DPT)
+ pop cx
+ pop dx
+%endif
+%if _PUSH_DPT || _SET_DSSI_DPT
+ %ifn _SET_DSSI_DPT ; (implying that only _PUSH_DPT is set)
+ %if _RELOCATE
+ xor ax, ax
+ mov es, ax ; => IVT
+ mov di, 1Eh*4
+ les si, [es:di] ; -> original (also current) DPT
+ push es
+ push si ; original (also current) DPT address
+ push ax
+ push di ; 0000h:0078h (address of 1Eh IVT entry)
+ %else
+ ; If not _RELOCATE, ds = 0000h (=> IVT)
+ mov di, 1Eh*4
+ les si, [di] ; -> original (also current) DPT
+ push es
+ push si ; original (also current) DPT address
+ ; If not _RELOCATE, ss = 0000h (=> IVT)
+ push ss
+ push di ; 0000h:0078h (address of 1Eh IVT entry)
+ %endif
+ %else
+ %if _RELOCATE
+ xor ax, ax
+ mov ds, ax ; => IVT
+ %endif
+ ; If not _RELOCATE, ds = 0000h (=> IVT)
+ mov di, 1Eh*4
+ lds si, [di] ; -> original (also current) DPT
+ %if _PUSH_DPT
+ push ds
+ push si ; original (also current) DPT address
+ %if _RELOCATE
+ push ax
+ push di ; 0000h:0078h (address of 1Eh IVT entry)
+ %else
+ ; If not _RELOCATE, ss = 0000h (=> IVT)
+ push ss
+ push di ; 0000h:0078h (address of 1Eh IVT entry)
+ %endif
+ %endif
+ %endif
+%endif
+%if _ZERO_ES || _ZERO_DS
+ %if _RELOCATE
+ %ifn _PUSH_DPT || _SET_DSSI_DPT
+ xor ax, ax
+ %endif
+ %if _ZERO_ES
+ mov es, ax
+ %endif
+ %if _ZERO_DS
+ mov ds, ax
+ %endif
+ %else
+ %if _ZERO_ES
+ push ss
+ pop es
+ %endif
+ %if _ZERO_DS && _SET_DSSI_DPT
+ push ss
+ pop ds
+ %endif
+ %endif
+%endif
+%if _DATASTART_HIDDEN
+ mov bx, [VAR(hidden_sectors + 0)]
+ mov ax, [VAR(hidden_sectors + 2)]
+ add word [VAR(data_start + 0)], bx
+ adc word [VAR(data_start + 2)], ax
+%endif
+%if _SET_AXBX_DATA
+ mov bx, [VAR(data_start)]
+ mov ax, [VAR(data_start+2)]
+%endif
+%if _SET_SIDI_CLUSTER
+ %if _SET_DSSI_DPT
+ %error Cannot select both of these.
+ %endif
+ %if _PUSH_DPT || _SET_DSSI_DPT
+ mov di, cx
+ mov si, dx
+ %else
+ pop di
+ pop si
+ %endif
+%endif
+%if _SET_DL_UNIT
+ mov dl, [VAR(boot_unit)]; set dl to unit
+ %if _SET_BL_UNIT
+ mov bl, dl ; set bl to unit, too
+ %endif
+%elif _SET_BL_UNIT
+ mov bl, [VAR(boot_unit)]; set bl to unit
+%endif
+ ; ss:bp-> boot sector with BPB
+ jmp (_LOAD_ADR>>4)+_EXEC_SEG_ADJ:_EXEC_OFS
+
+
+error_fsiboot:
+ mov al,'I'
+
+ db __TEST_IMM16 ; (skip mov)
+read_sector.err:
+ mov al, 'R' ; Disk 'R'ead error
+
+error:
+%if _RELOCATE
+ mov bx, 7
+ mov ds, bx
+ mov bh, [462h - 70h]
+%else
+ mov bh, [462h]
+ mov bl, 7
+%endif
+ mov ah, 0Eh
+ int 10h ; display character
+ mov al, 07h
+ int 10h ; beep!
+
+ xor ax, ax ; await key pressed
+ int 16h
+
+ int 19h ; re-start the boot process
+
+
+ ; Read a sector using Int13.02 or Int13.42
+ ;
+ ; INP: dx:ax = sector number within partition
+ ; bx => buffer
+ ; (_LBA) ds = ss
+ ; OUT: If unable to read,
+ ; ! jumps to error instead of returning
+ ; If sector has been read,
+ ; dx:ax = next sector number (has been incremented)
+ ; bx => next buffer (bx = es+word[para_per_sector])
+ ; es = input bx
+ ; CHG: -
+read_sector:
+ push dx
+ push cx
+ push ax
+ push si
+
+ push bx
+
+; DX:AX==LBA sector number
+; add partition start (= number of hidden sectors)
+ add ax,[VAR(hidden_sectors + 0)]
+ adc dx,[VAR(hidden_sectors + 2)]
+ %if (!_LBA || !_LBA_33_BIT) && _LBA_CHECK_NO_33
+ jc .err
+ %endif
+%if _LBA ; +70 bytes (with CHS, +63 bytes without CHS)
+ %if _LBA_33_BIT
+ sbb si, si ; -1 if was CY, 0 else
+ neg si ; 1 if was CY, 0 else
+ %endif
+ xor cx, cx
+ push cx ; highest word = 0
+ %if _LBA_33_BIT
+ push si ; bit 32 = 1 if operating in 33-bit space
+ %else
+ push cx ; second highest word = 0
+ %endif
+ push dx
+ push ax ; = qword sector number
+ push bx
+ push cx ; bx => buffer
+ inc cx
+ push cx ; word number of sectors to read
+ mov cl, 10h
+ push cx ; word size of disk address packet
+ mov si, sp ; ds:si -> disk address packet (on stack)
+
+ %if _LBA_SKIP_CHECK ; -14 bytes
+ mov dl, [VAR(boot_unit)]
+ %else
+ mov ah, 41h
+ mov dl, [VAR(boot_unit)]
+ mov bx, 55AAh
+ stc
+ int 13h ; 13.41.bx=55AA extensions installation check
+ jc .no_lba
+ cmp bx, 0AA55h
+ jne .no_lba
+ test cl, 1 ; support bitmap bit 0
+ jz .no_lba
+ %endif
+
+%if _LBA_RETRY
+ mov ah, 42h
+ int 13h ; 13.42 extensions read
+ jnc .lba_done
+
+ xor ax, ax
+ int 13h
+
+ ; have to reset the LBAPACKET's lpCount, as the handler may
+ ; set it to "the number of blocks successfully transferred".
+ ; (in any case, the high byte is still zero.)
+ mov byte [si + 2], 1
+%endif
+
+ mov ah, 42h
+ int 13h
+ %if _LBA_SKIP_CHECK && _CHS
+ jnc .lba_done
+ cmp ah, 1 ; invalid function?
+ je .no_lba ; try CHS instead -->
+.err_CY:
+.err_2:
+ jmp .lba_error
+ %else
+.err_CY:
+ jc .lba_error
+.err_2: equ .err
+ %endif
+
+.lba_done:
+%if _CHS && _LBA_SET_TYPE
+ mov byte [bp + 2], 0Ch ; LBA-enabled FAT32 FS partition type
+%endif
+ add sp, 10h
+ pop bx
+ mov es, bx
+%if _CHS
+ jmp short .chs_done
+%endif
+
+.lba_error: equ .err
+
+ %if !_CHS
+.no_lba: equ .err
+ %else
+.no_lba:
+ add sp, 8
+ pop cx ; cx = low word of sector
+ pop ax
+ pop dx ; dx:ax = middle two words of sector
+ ; Here dx <= 1 if _LBA_33_BIT, else zero.
+ ; If dx is nonzero then the CHS calculation
+ ; should fail. If CHS sectors is equal to 1
+ ; (very unusual) then the div may fail. Else,
+ ; we will detect a cylinder > 1023 eventually.
+ pop si ; discard highest word of qword
+ %endif
+%else
+.err_2: equ .err
+%endif
+
+%if _CHS ; +70 bytes
+; dx:ax = LBA sector number, (if _LBA) cx = 0
+; divide by number of sectors per track to get sector number
+; Use 32:16 DIV instead of 64:32 DIV for 8088 compatability
+; Use two-step 32:16 divide to avoid overflow
+ %if !_LBA
+ xchg cx, ax ; cx = low word of sector, clobbers ax
+ xchg ax, dx ; ax = high word of sector, clobbers dx
+ xor dx, dx ; dx:ax = high word of sector
+ %else
+ ; from the .no_lba popping we already have:
+ ; cx = low word of sector
+ ; dx:ax = high word of sector
+ %endif
+ div word [VAR(sectors_per_track)]
+ xchg cx,ax
+ div word [VAR(sectors_per_track)]
+ xchg cx,dx
+
+; DX:AX=quotient, CX=remainder=sector (S) - 1
+; divide quotient by number of heads
+ xchg bx, ax ; bx = low word of quotient, clobbers ax
+ xchg ax, dx ; ax = high word of quotient, clobbers dx
+ xor dx, dx ; dx = 0
+ div word [VAR(heads)]
+ ; ax = high / heads, dx = high % heads
+ xchg bx, ax ; bx = high / heads, ax = low quotient
+ div word [VAR(heads)]
+
+; bx:ax=quotient=cylinder (C), dx=remainder=head (H)
+; move variables into registers for INT 13h AH=02h
+ mov dh, dl ; dh = head
+ inc cx ; cl5:0 = sector
+ xchg ch, al ; ch = cylinder 7:0, al = 0
+ shr ax, 1
+ shr ax, 1 ; al7:6 = cylinder 9:8
+ ; bx has bits set iff it's > 0, indicating a cylinder >= 65536.
+ or bl, bh ; collect set bits from bh
+ or cl, al ; cl7:6 = cylinder 9:8
+ ; ah has bits set iff it was >= 4, indicating a cylinder >= 1024.
+ or bl, ah ; collect set bits from ah
+ mov dl,[VAR(boot_unit)] ; dl = drive
+ jnz .err_2 ; error if cylinder >= 1024 -->
+ ; ! bx = 0 (for 13.02 call)
+
+; we call INT 13h AH=02h once for each sector. Multi-sector reads
+; may fail if we cross a track or 64K boundary
+ pop es
+%if _CHS_RETRY_REPEAT
+ mov si, _CHS_RETRY_REPEAT + 1
+.loop_chs_retry_repeat:
+ mov ax, 0201h
+ int 13h ; read one sector
+ jnc .done
+ xor ax, ax
+ int 13h ; reset disk
+ dec si ; another attempt ?
+ jnz .loop_chs_retry_repeat ; yes -->
+ jmp .err_2
+%else
+ %if _CHS_RETRY
+ mov ax, 0201h
+ int 13h ; read one sector
+ jnc .done
+; reset drive
+ xor ax, ax
+ int 13h
+ %endif
+; try read again
+ mov ax, 0201h
+ int 13h
+ jc .err_CY
+%endif
+%if ! _LBA
+.err_CY: equ .err
+%endif
+
+.done:
+; increment segment
+ mov bx, es
+%endif
+
+.chs_done:
+%if _FIX_SECTOR_SIZE
+ add bx, _FIX_SECTOR_SIZE >> 4
+%else
+ add bx, [VAR(para_per_sector)]
+%endif
+
+ pop si
+ pop ax
+ pop cx
+ pop dx
+; increment LBA sector number
+ inc ax
+ jne @F
+ inc dx
+@@:
+
+.retn:
+ retn
+
+
+%if _ADD_SEARCH
+add_name: ; = blank-padded 11-byte filename to search for
+ fill 8,32,db _ADD_NAME
+ fill 3,32,db _ADD_EXT
+%endif
+
+
+%if !_NO_LIMIT
+available:
+ _fill 508,38,start
+
+signatures:
+ dw 0
+; 2-byte magic bootsector signature
+ dw 0AA55h
+
+%assign num signatures-available
+%warning FAT32: num bytes still available.
+%else ; for testing
+ align 4
+signatures:
+ dw 0
+; 2-byte magic bootsector signature
+ dw 0AA55h
+%endif
+
+ align 16, nop
+end:
+
+fsiboot:
+ dd "RRaA"
+.signature:
+ fill 8, 32, db _FSIBOOTNAME
+
+%if ($ - .signature) != 8
+ %error Unexpected name size
+%endif
+.start:
+ ; INP: ax => after last segment to be used for loading
+ ; bx => FAT buffer (8 KiB)
+ ; ss:bp -> boot sector, with EBPB
+ ; dwo [ss:bp - 4] = data_start (uninit)
+ ; wo [ss:bp - 6] = load_seg (uninit)
+ ; wo [ss:bp - 8] = fat_seg (uninit)
+ ; dwo [ss:bp - 12] = fat_sector (uninit)
+ ; dwo [ss:bp - 16] = first_cluster (uninit)
+ ; wo [ss:bp - 18] = para_per_sector
+ ; wo [ss:bp - 20] = entries_per_sector
+ ; ss:sp = ss:bp - 20
+ ; (Note: The following stack frame entries are currently
+ ; only used by FSIBOOT itself, so they may be
+ ; considered implementation detail instead of
+ ; part of the FSIBOOT protocol.)
+ ; wo [ss:bp - 22] = last_available_sector (uninit)
+ ; wo [ss:bp - 24] = adj_sectors_per_cluster (uninit)
+ ; wo [ss:bp - 26] = paras_left (uninit)
+ ; Stack layout has to match!
+ ; ss = ds
+ ; ss:bp + ((11 + ebpbNew + BPBN_size + 1) & ~1)
+ ; = ss:fsiboot_table, refer to its comments
+ ; cs set to address jump table offsets in fsiboot_table
+ ; ds set to address fsiboot_table.load_name
+ ; OUT: Jumps to fsiboot_table.error if error occurs:
+ ; al = error condition letter
+ ; Else:
+ ; data_start (within partition) initialised
+ ; load_seg => behind last loaded data
+ ; fat_seg initialised (from bx)
+ ; fat_sector initialised
+ ; (-1 initially, loaded sector-in-FAT later)
+ ; first_cluster initialised
+ ; last_available_sector initialised
+ ; (from input ax minus word [para_per_sector])
+ ; adj_sectors_per_cluster initialised (from BPB)
+ ; paras_left initialised
+ ; Jumps to fsiboot_table.success if loaded entirely:
+ ; dword [ss:sp] = first cluster
+ ; Jumps to fsiboot_table.memory_full if loaded partially:
+ ; al = error condition letter ('M')
+ ; dword [ss:sp] = current cluster number
+ ; dword [ss:sp + 4] = first cluster
+
+ mov word [VAR(fat_seg)], bx ; initialise => FAT buffer
+
+ sub ax, word [VAR(para_per_sector)]
+ jc .CY_fsiboot_error_badchain
+ push ax ; push into word [VAR(last_available_sector)]
+
+ mov bx, word [VAR(fsiboot_table.loadseg)]
+ mov word [VAR(load_seg)], bx ; initialise => load address
+ cmp ax, bx
+ jb .CY_fsiboot_error_badchain
+
+; adjusted sectors per cluster (store in a word,
+; and decode EDR-DOS's special value 0 meaning 256)
+ xor ax, ax
+ mov al, [VAR(sectors_per_cluster)]
+ dec al
+ inc ax
+ push ax ; push into word [VAR(adj_sectors_per_cluster)]
+ dec ax ; ! ah = 0
+ mov al,[VAR(num_fats)] ; ! ah = 0, hence ax = number of FATs
+ push ax
+ mul word [VAR(sectors_per_fat_large)]
+ ; ax = low word SpF*nF
+ ; dx = high word
+ xchg bx, ax
+ xchg cx, dx
+ ; cx:bx = first mul
+ pop ax
+ mul word [VAR(sectors_per_fat_large + 2)]
+ ; ax = high word adjust
+ ; dx = third word
+ test dx, dx
+ stc
+ jnz .CY_fsiboot_error_badchain
+ ; dx = zero
+ xchg dx, ax
+ ; dx = high word adjust
+ ; ax = zero
+ add dx, cx
+ jc .CY_fsiboot_error_badchain
+ ; dx:bx = result
+ xchg ax, bx
+ ; dx:ax = result
+ ; bx = zero
+
+ add ax,[VAR(num_reserved_sectors)]
+ adc dx, bx ; bx is zero here
+.CY_fsiboot_error_badchain:
+ jc ..@CY_2_fsiboot_error_badchain
+
+; first sector of disk data area:
+ mov [VAR(data_start)], ax
+ mov [VAR(data_start+2)], dx
+
+ mov di, -1
+ mov si, di
+ mov word [VAR(fat_sector + 2)], si
+ mov word [VAR(fat_sector + 0)], di
+ call dirsearch
+
+
+found_load_file:
+ call near word [VAR(fsiboot_table.writedirentry)]
+
+; get starting cluster of file
+ push word [es:bx + deClusterHigh]
+ push word [es:bx + deClusterLow]
+
+ ; check FAT+ size bits
+ test byte [es:bx + dePlusSize], 0E7h
+ ; test whether bits 7-5 and 2-0 NZ
+; https://web.archive.org/web/20150219123449/http://www.fdos.org/kernel/fatplus.txt
+ jnz .large_file ; yes, clamp to maximum paras -->
+ mov ax, [es:bx + deSize + 2]
+ mov bx, [es:bx + deSize]
+ ; ax:bx = file size (non-FAT+)
+
+ mov cx, [VAR(bytes_per_sector)]
+ dec cx ; BpS - 1
+ add bx, cx
+ adc ax, 0 ; large ?
+ jc .large_file ; yes, clamp to maximum paras -->
+
+ not cx ; ~ (BpS - 1)
+ and bx, cx ; mask to limit to rounded-up sector
+ ; (this also rounds up paragraphs)
+ mov cx, 4
+@@:
+ shr ax, 1
+ rcr bx, 1
+ loop @B
+ ; ax:bx = size in paragraphs
+ ; bx = size in paragraphs if < 1_0000h
+ test ax, ax ; > 0FFFFh paras ?
+ jz @F ; no, take actual size -->
+.large_file:
+ mov bx, 0FFFFh ; cx = clamp size to 0FFFFh paras
+@@:
+ call check_enough.in_bx ; (CHG ax)
+
+ pop ax
+ pop dx ; dx:ax = first cluster
+
+ push bx ; push into word [VAR(paras_left)]
+
+ mov word [VAR(first_cluster + 0)], ax
+ mov word [VAR(first_cluster + 2)], dx
+ push dx
+ push ax ; remember cluster for later
+
+ call check_clust
+..@CY_2_fsiboot_error_badchain:
+ jc ..@CY_3_fsiboot_error_badchain
+
+next_load_cluster:
+ call clust_to_first_sector
+ ; dx:ax = first sector of cluster
+ ; cx:bx = cluster value
+ push cx
+ push bx ; preserve cluster number for later
+
+ mov cx, [VAR(adj_sectors_per_cluster)]
+
+; xxx - this will always load an entire cluster (e.g. 64 sectors),
+; even if the file is shorter than this
+@@:
+ mov bx, [VAR(load_seg)] ; => where to read next sector
+ cmp bx, [VAR(last_available_sector)]
+ jbe @F
+ call check_enough
+ mov al, 'M' ; (! _MEMORY_CONTINUE: error code)
+ jmp near word [VAR(fsiboot_table.memory_full)]
+
+@@:
+ call near word [VAR(fsiboot_table.read_sector)]
+ mov [VAR(load_seg)], bx ; => after last read data
+
+ mov bx, [VAR(para_per_sector)]
+ sub word [VAR(paras_left)], bx
+ jbe @F ; read enough -->
+
+ loop @BB
+ pop bx
+ pop cx
+
+ call clust_next
+ jnc next_load_cluster
+ inc ax
+ inc ax
+ test al, 8 ; set in 0FFF_FFF8h--0FFF_FFFFh,
+ ; clear in 0, 1, and 0FFF_FFF7h
+ jz fsiboot_error_badchain
+ db __TEST_IMM16
+@@:
+ pop bx
+ pop cx
+ call check_enough
+ jmp near word [VAR(fsiboot_table.success)]
+
+
+dirsearch:
+ mov ax, [VAR(root_cluster)]
+ mov dx, [VAR(root_cluster + 2)]
+ call check_clust
+..@CY_3_fsiboot_error_badchain:
+ jc fsiboot_error_badchain
+
+next_root_clust:
+ call clust_to_first_sector
+ push cx
+ push bx
+ mov cx, [VAR(adj_sectors_per_cluster)]
+next_root_sect:
+ push cx
+ mov cx, [VAR(entries_per_sector)]
+
+; Scan root directory for file. We don't bother to check for deleted
+; entries (E5h) or entries that mark the end of the directory (00h).
+ mov bx, [VAR(fsiboot_table.dirbuf)]
+ call near word [VAR(fsiboot_table.read_sector)]
+
+ push di
+ xor di, di ; es:di-> first entry in this sector
+next_ent:
+ test byte [es:di + deAttrib], ATTR_DIRECTORY | ATTR_VOLLABEL
+ jnz @F ; directory, label, or LFN entry --> (NZ)
+ push si
+ push di
+ push cx
+ mov si, [VAR(fsiboot_table.filename)]
+ ; ds:si-> name to match
+ mov cx, 11 ; length of padded 8.3 FAT filename
+ repe cmpsb ; check entry
+ pop cx
+ pop di
+ pop si
+@@: ; ZR = match, NZ = mismatch
+ je found_it ; found entry -->
+ lea di, [di + DIRENTRY_size]
+
+ loop next_ent ; count down sector's entries (jumps iff cx >0)
+ pop di
+ pop cx
+ loop next_root_sect
+ pop bx
+ pop cx
+ call clust_next
+ jnc next_root_clust
+file_not_found:
+ mov al, 'F'
+ db __TEST_IMM16
+fsiboot_error_badchain:
+ mov al, 'B'
+
+fsiboot_error:
+ jmp near word [VAR(fsiboot_table.error)]
+
+
+ ; INP: dx:ax = cluster - 2 (0-based cluster)
+ ; OUT: cx:bx = input dx:ax
+ ; dx:ax = first sector of that cluster
+ ; CHG: -
+clust_to_first_sector:
+ push dx
+ push ax
+ push dx
+ mul word [VAR(adj_sectors_per_cluster)]
+ xchg bx, ax
+ xchg cx, dx
+ pop ax
+ mul word [VAR(adj_sectors_per_cluster)]
+ test dx, dx
+ jnz fsiboot_error_badchain
+ xchg dx, ax
+ add dx, cx
+ jc ..@CY_fsiboot_error_badchain
+ xchg ax, bx
+
+ add ax, [VAR(data_start)]
+ adc dx, [VAR(data_start + 2)]
+..@CY_fsiboot_error_badchain:
+ jc fsiboot_error_badchain
+ ; dx:ax = first sector in cluster
+ pop bx
+ pop cx ; cx:bx = cluster
+ retn
+
+
+check_enough:
+ mov bx, [VAR(load_seg)]
+ ; => behind last read sector
+ sub bx, word [VAR(fsiboot_table.loadseg)]
+.in_bx:
+ cmp bx, word [VAR(fsiboot_table.minpara)]
+ mov al, 'E'
+ jb fsiboot_error
+ retn
+
+
+found_it:
+ ; es:di -> dir entry in dir sector buffer
+ mov bx, di
+ pop di ; restore si:di = loaded FAT sector
+ pop cx ; (discard sectors per cluster counter)
+ pop cx
+ pop cx ; (discard current cluster number)
+ ; es:bx -> dir entry
+ retn
+
+
+ ; INP: cx:bx = cluster (0-based)
+ ; si:di = loaded FAT sector, -1 if none
+ ; OUT: CY if no next cluster
+ ; NC if next cluster found,
+ ; dx:ax = next cluster value (0-based)
+ ; si:di = loaded FAT sector
+ ; CHG: cx, bx, es
+clust_next:
+ xchg ax, bx ; ax = low word cluster, clobbers bx
+ mov dx, cx
+ add ax, 2
+ adc dx, 0
+
+ add ax, ax
+ adc dx, dx
+ add ax, ax
+ adc dx, dx ; * 4 = byte offset into FAT (0--4000_0000h)
+ push ax
+ xchg ax, dx
+ xor dx, dx ; dx:ax = high word
+ div word [VAR(bytes_per_sector)]
+ xchg bx, ax ; bx = result high word, clobbers ax
+ pop ax ; dx = remainder, ax = low word
+ div word [VAR(bytes_per_sector)]
+ xchg dx, bx ; dx:ax = result, bx = remainder
+ ; dx:ax = sector offset into FAT (0--200_0000h)
+ ; bx = byte offset into FAT sector (0--8190)
+ cmp dx, si
+ jne @F ; read sector
+ cmp ax, di
+ je @FF ; sector is already buffered
+@@:
+ mov si, dx
+ mov di, ax
+ mov word [VAR(fat_sector + 2)], dx
+ mov word [VAR(fat_sector + 0)], ax
+
+ push bx
+ add ax, [VAR(fat_start)]
+ adc dx, 0
+ mov bx, [VAR(fat_seg)]
+ call near word [VAR(fsiboot_table.read_sector)]
+ pop bx
+@@:
+ mov es, [VAR(fat_seg)]
+ mov ax, [es:bx]
+ mov dx, [es:bx + 2]
+
+ ; INP: dx:ax = cluster value, 2-based
+ ; OUT: dx:ax -= 2 (makes it 0-based)
+ ; CY iff invalid cluster
+check_clust:
+ and dh, 0Fh
+ sub ax, 2
+ sbb dx, 0
+ cmp dx, 0FFFh
+ jb @F ; CY here means valid ...-
+ cmp ax, 0FFF7h - 2
+@@: ; -... or if NC first, CY here also
+ cmc ; NC if valid
+ retn
+
+
+fsinfo_available:
+ _fill 480 + 4 - 2,38,fsiboot
+
+%assign num $-fsinfo_available
+%warning FSINFO: num bytes still available.
+
+
+ ; INP: si:di = loaded sector-in-FAT
+ ; word [fsiboot_table.filename] -> 8.3 filename
+ ; CHG: ax, cx, dx
+ ; OUT: si:di updated, if so
+ ; es:bx -> found directory entry
+ ; jumps to error handler if an error occurs
+ ; STT: stack variables as set up by main FSIBOOT entry
+dirsearch_entrypoint:
+ dw dirsearch
+
+ dd "rrAa"
+ dd -1 ; number of free clusters
+ dd -1 ; first free cluster
+ times 3 dd 0 ; FSINFO.reserved2
+ dd 0_AA55_0000h ; FSINFO.signature3
+
+end_after_fsiboot:
+%if $ - fsiboot != 512
+ %error Wrong FSIBOOT layout
+%endif
diff --git a/test/ldosboot/doc/ldosboot.src b/test/ldosboot/doc/ldosboot.src
new file mode 100644
index 0000000..72cde2a
--- /dev/null
+++ b/test/ldosboot/doc/ldosboot.src
@@ -0,0 +1,325 @@
+\cfg{chapter}{Section}
+
+\cfg{text-filename}{ldosboot.txt}
+\cfg{text-chapter-numeric}{true}
+\cfg{text-indent-preamble}{false}
+\cfg{text-quotes}{"}{"}
+\cfg{text-indent}{4}
+\cfg{text-width}{72}
+
+\cfg{html-chapter-numeric}{true}
+\cfg{html-suppress-address}{true}
+\cfg{html-single-filename}{ldosboot.htm}
+\cfg{html-leaf-level}{0}
+\cfg{html-template-fragment}{%k}{%b}
+\cfg{html-head-end}{}
+
+\cfg{pdf-filename}{ldosboot.pdf}
+
+\cfg{ps-filename}{ldosboot.ps}
+
+\cfg{info-filename}{ldosboot.info}
+
+\cfg{chm-filename}{ldosboot.chm}
+
+\cfg{winhelp-filename}{ldosboot.hlp}
+
+\cfg{man-filename}{ldosboot.7}
+\cfg{man-identity}{ldosboot}{7}{2020}{}{C. Masloch}
+
+\title lDOS boot documentation
+
+\copyright 2020 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.
+
+This document has been compiled on \date{%Y-%m-%d}.
+
+
+\C{protocols} lDOS boot protocols
+
+
+\H{protocol-sector-iniload} Sector to iniload protocol
+
+The iniload kernel is loaded to an arbitrary segment.
+The segment must be at least 60h.
+Common choices are 60h, 70h, and 200h.
+At least 1536 bytes of the file must be loaded.
+Current loaders will load at least 8192 bytes
+if the file is as large or larger than that.
+The entrypoint is found by applying no segment adjustment (0)
+and choosing the offset 400h (1024).
+
+
+\S{protocol-sector-iniload-signatures} Signatures
+
+At offset 1020 (3FCh) there is the signature \cq{lD}.
+Behind that there are two bytes with printable non-blank ASCII codepoints.
+Currently the following signatures are defined:
+
+\dt \cq{lDOS}
+
+\dd lDOS kernel (not yet in use)
+
+\dt \cq{lDRx}
+
+\dd RxDOS kernel
+
+\dt \cq{lDFD}
+
+\dd FreeDOS kernel wrapped in iniload (fdkernpl.asm)
+
+\dt \cq{lDeb}
+
+\dd lDebug
+
+\dt \cq{lDDb}
+
+\dd lDDebug (debuggable lDebug)
+
+\dt \cq{lDTP}
+
+\dd lDOS test payload kernel (testpl.asm)
+
+\dt \cq{lDTW}
+
+\dd lDOS test result writer kernel (testwrit.asm)
+
+
+\S{protocol-sector-iniload-lsv} Load Stack Variables (LSV)
+
+Under this protocol, the pointer \cq{ss:bp} is passed.
+It points to a boot sector with (E)BPB.
+The stack pointer must be at most \cq{bp - 10h}.
+Below the pointed to location there live the Load Stack Variables.
+These follow this structure:
+
+\c struc LOADSTACKVARS, -10h
+\c lsvFirstCluster: resd 1
+\c lsvFATSector: resd 1
+\c lsvFATSeg: resw 1
+\c lsvLoadSeg: resw 1
+\c lsvDataStart: resd 1
+\c endstruc
+
+\dt lsvFirstCluster
+
+\dd (FAT12, FAT16) Low word gives starting cluster of file.
+High word uninitialised.
+
+\dd (FAT32) Dword gives starting cluster of file.
+
+\dd (else) Should be zero.
+
+\dt lsvFATSector
+
+\dd (FAT16) Low word gives loaded sector-in-FAT.
+-1 if none loaded yet.
+High word uninitialised.
+
+\dd (FAT32) Dword gives loaded sector-in-FAT.
+-1 if none loaded yet.
+
+\dd (FAT12, else) Unused.
+
+\dt lsvFATSeg
+
+\dd (FAT16, FAT32) Word gives segment of FAT buffer
+if word/dword [lsvFATSector] != -1.
+
+\dd (FAT12) Word gives segment of FAT buffer.
+Zero if none.
+Otherwise, buffer holds entire FAT data, up to 6 KiB.
+
+\dt lsvLoadSeg
+
+\dd Word points to segment beyond last loaded paragraph.
+Allows iniload to determine how much of it is already loaded.
+
+\dt lsvDataStart
+
+\dd Dword gives sector-in-partition of first cluster's data.
+
+
+An LSV extension allows to pass a command line to the kernel.
+The stack pointer must be at most \cq{bp - 114h} then.
+This follows the structure like this:
+
+\c lsvclSignature equ "CL"
+\c lsvclBufferLength equ 256
+\c
+\c struc LSVCMDLINE, LOADSTACKVARS - lsvclBufferLength - 4
+\c lsvCommandLine:
+\c .start: resb lsvclBufferLength
+\c .signature: resw 1
+\c lsvExtra: resw 1
+\c endstruc
+
+\dt lsvCommandLine.start
+
+\dd Command line buffer. Contains zero-terminated command line string.
+
+\dt lsvCommandLine.signature
+
+\dd Contains the signature value \cq{CL} if command line is given.
+
+\dt lsvExtra
+
+\dd Used internally by iniload.
+Space for this must be reserved when passing a command line.
+
+If no command line is passed then either the stack pointer
+must be \cq{bp - 10h}, or \cq{bp - 12h}, or
+the word in the lsvCommandLine.signature variable
+(\cw{word [ss:bp - 14h]})
+must not equal the string \cq{CL}.
+
+\b dosemu2's RxDOS.3 support sets \cq{sp = bp - 10h}
+
+\b ldosboot boot.asm (FAT12/FAT16) loader
+uses the variable for a \q{paragraphs per sector} value
+which is always a power of two and always below-or-equal 200h.
+
+\b ldosboot boot32.asm (FAT32) loader
+uses the variable for an \q{entries per sector} value
+which is always a power of two and always below-or-equal 100h.
+
+\b lDebug with protocol options \cw{cmdline=0 push_dpt=0}
+sets \cq{sp = bp - 10h}
+
+
+\H{protocol-iniload-payload} Iniload to payload protocol
+
+The payload is loaded to an arbitrary segment.
+The segment must be at least 60h.
+The entire payload must be loaded.
+The size of the payload is determined at iniload build time.
+The entrypoint is found by applying a segment adjustment
+and choosing an offset.
+The segment adjustment is specified at iniload build time
+by the numeric define \cw{_EXEC_SEGMENT} (default 0),
+and the offset by the define \cw{_EXEC_OFFSET} (default 0).
+
+
+\S{protocol-iniload-payload-ebpb} Extended BIO Parameter Block (EBPB)
+
+Above the LSV, \cw{ss:bp} points to an EBPB and surrrounding boot sector.
+Note that this is always a FAT32-style EBPB.
+If the filesystem that is loaded from is not FAT32,
+and is therefore FAT16 or FAT12,
+then the FAT16/FAT12 BPBN structure is moved up.
+It is placed where the FAT32 BPBN is usually expected.
+In this case, the entire boot sector contents behind the BPBN
+are also moved up by the size of the FAT32-specific fields.
+The FAT32-specific fields are filled with zeros,
+except for the FAT32 \q{sectors per FAT} field.
+It is filled with the contents of the FAT16/FAT12
+\q{sectors per FAT} field.
+
+
+\S{protocol-iniload-payload-lsv} Load Stack Variables (LSV)
+
+Refer to \k{protocol-sector-iniload-lsv}.
+
+
+\S{protocol-iniload-payload-ld} Load Data 1 (LD)
+
+Below the LSV, iniload passes the LOADDATA (1) structure.
+
+\c struc LOADDATA, LOADSTACKVARS - 10h
+\c ldMemoryTop: resw 1
+\c ldLoadTop: resw 1
+\c ldSectorSeg: resw 1
+\c ldFATType: resb 1
+\c ldHasLBA: resb 1
+\c ldClusterSize: resw 1
+\c ldParaPerSector:resw 1
+\c ldLoadingSeg: resw 1
+\c ldLoadUntilSeg: resw 1
+\c endstruc
+
+\dt ldMemoryTop
+
+\dd Word. Segment pointer to behind usable memory.
+Points at the first of the EBDA, RPL-reserved memory, or
+video memory or otherwise UMA.
+Indicates how much memory may be used by a typical kernel.
+(lDebug detects the EBDA to move that below where it installs.)
+
+\dt ldLoadTop
+
+\dd Word. Segment pointer to lowest lDOS boot memory in use.
+All memory between linear 600h and the segment indicated here
+is usable by the payload.
+Only the payload itself is stored in this area.
+The other buffers, stack, and structures passed by iniload
+must live above this segment.
+
+\dt ldSectorSeg
+
+\dd Word. Segment pointer to an 8 KiB transfer buffer.
+It is insured that this buffer does not cross a 64 KiB boundary.
+This may be needed by some disk units.
+The buffer is not initialised to anything generally.
+
+\dt ldFATType
+
+\dd Byte. Indicates length of FAT entry in bits.
+12 indicates FAT12, 16 FAT16, 32 FAT32.
+It is planned to allow zero for non-FAT filesystems.
+
+\dt ldHasLBA
+
+\dd Byte. Only least significant bit used.
+Bit on indicates LBA extensions available for the load disk unit.
+Bit off indicates LBA extensions not available.
+
+\dt ldClusterSize
+
+\dd Word. Contains amount of sectors per cluster.
+Unlike the byte field for the same purpose in the BPB,
+this field can encode 256 (EDR-DOS compatible) without any masking.
+May be given as zero for non-FAT filesystems.
+
+\dt ldParaPerSector
+
+\dd Word. Contains amount of paragraphs per sector.
+Must be a power of two between 2 (32 B/s) and 200h (8192 B/s).
+May be given as zero for non-FAT filesystems.
+
+\dt ldLoadingSeg
+
+\dd Word. Internally used by iniload.
+Available for re-use by payload.
+
+\dt ldLoadUntilSeg
+
+\dd Word. Internally used by iniload.
+Available for re-use by payload.
+
+
+\S{protocol-iniload-payload-lcl} Load Command Line (LCL)
+
+Below the LOADDATA structure, iniload passes the LOADCMDLINE structure.
+
+\c lsvclBufferLength equ 256
+\c
+\c struc LOADCMDLINE, LOADDATA - lsvclBufferLength
+\c ldCommandLine:
+\c .start: resb lsvclBufferLength
+\c endstruc
+
+This buffer is always initialised to an ASCIZ string.
+At most 255 bytes may be initialised to string data.
+At most the 256th byte is a zero.
+
+If the first word of the buffer is equal to 0FF00h,
+that is there is an empty command line
+the terminator of which is followed by a byte with the value 0FFh,
+then no command line was passed to iniload.
+Currently lDebug can pass a command line to iniload when
+loading with its lDOS, RxDOS.2, or RxDOS.3 protocols.
+When iniload is loaded as a Multiboot1 or Multiboot2 specification kernel,
+it is also assumed that a command line can be passed.
diff --git a/test/ldosboot/doc/mak.sh b/test/ldosboot/doc/mak.sh
new file mode 100755
index 0000000..f65e02d
--- /dev/null
+++ b/test/ldosboot/doc/mak.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+
+# 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.
+
+echo -ne "\\U Source Control Revision ID\n\nhg $(hg id -i), from commit on at $(hg log -r . --template="{date|isodatesec}\n")\n\nIf this is in ecm's repository, you can find it at \\W{https://hg.pushbx.org/ecm/ldosboot/rev/$(hg log -r . --template "{node|short}")}{https://hg.pushbx.org/ecm/ldosboot/rev/$(hg log -r . --template "{node|short}")}\n" > screvid.src
+halibut --precise ldosboot.src screvid.src --html --text --pdf
diff --git a/test/ldosboot/fdkernpl.asm b/test/ldosboot/fdkernpl.asm
new file mode 100644
index 0000000..f75cf68
--- /dev/null
+++ b/test/ldosboot/fdkernpl.asm
@@ -0,0 +1,246 @@
+
+%if 0
+
+Loader adjustment to load FreeDOS kernel
+ by C. Masloch, 2017
+
+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
+
+
+%include "lmacros3.mac"
+
+ 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
+
+
+%ifndef _MAP
+%elifempty _MAP
+%else ; defined non-empty, str or non-str
+ [map all _MAP]
+%endif
+
+ strdef PAYLOAD_FILE, "KERNEL.SYS"
+
+
+ struc LOADSTACKVARS, -10h
+lsvFirstCluster: resd 1
+lsvFATSector: resd 1
+lsvFATSeg: resw 1
+lsvLoadSeg: resw 1
+lsvDataStart: resd 1
+ endstruc
+
+
+lsvclSignature equ "CL"
+lsvclBufferLength equ 256
+
+ struc LOADDATA, LOADSTACKVARS - 10h
+ldMemoryTop: resw 1
+ldLoadTop: resw 1
+ldSectorSeg: resw 1
+ldFATType: resb 1
+ldHasLBA: resb 1
+ldClusterSize: resw 1
+ldParaPerSector:resw 1
+ldLoadingSeg: ; word
+lsvCommandLine: ; word
+.start: equ $ - lsvclBufferLength
+.signature: resw 1
+ldLoadUntilSeg: ; word
+lsvExtra: ; word
+.partition: resb 1 ; byte
+.flags: resb 1 ; byte
+ endstruc
+
+lsvefNoDataStart equ 1
+lsvefPartitionNumber equ 2
+
+ struc LOADCMDLINE, LOADDATA - lsvclBufferLength
+ldCommandLine:
+.start: resb lsvclBufferLength
+ endstruc
+
+
+ cpu 8086
+ org 0
+ addsection ENTRY, start=0 vstart=0
+entry:
+
+ mov ax, cs
+ add ax, entry_size_p + payload_size_p
+ xor bx, bx
+ push ax
+ push bx
+ retf
+
+ align 16
+ endarea entry
+
+
+ addsection PAYLOAD, follows=ENTRY
+payload:
+realpayload:
+ incbin _PAYLOAD_FILE
+ align 16, db 38
+ endarea realpayload
+
+
+ addsection STACKRELOCATE, follows=PAYLOAD vstart=0
+stackrelocate:
+
+ mov ax, 60h + payload_size_p
+ mov es, ax
+ xor di, di
+ xor si, si
+ lea cx, [ bp + 512 ]
+ rep movsb
+ mov ds, ax
+ mov si, bp
+ xor di, di
+ cmp word [ds:bp + ldCommandLine], 0FF00h
+ je @F
+ lea si, [bp + ldCommandLine + lsvclBufferLength - 2]
+ lea di, [bp + lsvCommandLine.start + lsvclBufferLength - 2]
+ mov cx, words(lsvclBufferLength)
+%if words(lsvclBufferLength) <= 20
+ %error AMD erratum 109 workaround needed
+%endif
+ std
+ rep movsw
+ cld
+ lea si, [bp + lsvCommandLine.start]
+ mov di, lsvclSignature
+@@:
+ cli
+ mov ss, ax
+ mov sp, si
+ mov word [bp + lsvCommandLine.signature], di
+ ; Note that this access uses the new ss.
+ ; Also note: If no command line is passed,
+ ; si will equal bp. That means the word
+ ; written here is technically below sp,
+ ; that is it belongs to the unused stack.
+ ; This does not cause any problems however.
+ ; It hardens the next load stage against
+ ; accidentally expecting a command line if
+ ; it does not check the offsets properly.
+ sti
+
+ jmp 60h:0
+
+ align 16
+ endarea stackrelocate
+
+payload_size equ realpayload_size + stackrelocate_size
+ endarea payload, 1
+
+
+ addsection RELOCATE, follows=STACKRELOCATE vstart=0
+relocate:
+ mov bx, 1000h
+ mov ax, 60h
+ mov es, ax
+ mov cx, payload_size_p
+ mov ax, cs
+ sub ax, cx
+ mov ds, ax
+ xor si, si
+ xor di, di
+
+ mov ax, cx
+ cmp ax, bx
+ jbe @F
+ mov cx, bx
+@@:
+ sub ax, cx
+ shl cx, 1
+ shl cx, 1
+ shl cx, 1
+ rep movsw
+
+@@:
+ mov dx, es
+ add dx, bx
+ mov es, dx ; next segment
+
+ mov dx, ds
+ add dx, bx
+ mov ds, dx ; next segment
+
+ sub ax, bx ; = how much to relocate after this round
+ mov cx, 1000h << 3 ; in case another full 64 KiB to relocate
+ jae @F ; another full 64 KiB to relocate -->
+ add ax, bx ; restore
+ shl ax, 1
+ shl ax, 1
+ shl ax, 1 ; convert paragraphs to words
+ mov cx, ax ; that many words
+ xor ax, ax ; no more to relocate after this round
+
+@@:
+ xor si, si
+ xor di, di
+ rep movsw ; relocate next chunk
+ test ax, ax ; another round needed?
+ jnz @BB ; yes -->
+ ; ax = 0
+
+ mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
+ mov bl, dl
+
+ push ss
+ pop ds
+ cmp word [bp + bsBPB + bpbSectorsPerFAT], ax
+ je @F
+ push ss
+ pop es
+ lea si, [bp + bsBPB + ebpbNew]
+ lea di, [bp + bsBPB + bpbNew]
+ mov cx, (512 - bsBPB - bpbNew + 1) >> 1
+ rep movsw
+@@:
+
+ jmp 60h + realpayload_size_p:0
diff --git a/test/ldosboot/iniload.asm b/test/ldosboot/iniload.asm
new file mode 100644
index 0000000..7fabc39
--- /dev/null
+++ b/test/ldosboot/iniload.asm
@@ -0,0 +1,2477 @@
+
+%if 0
+
+Loader for finishing file system booting
+ by C. Masloch, 2017
+
+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
+
+
+%assign __lMACROS1_MAC__DEBUG_DEFAULTS 1
+%include "lmacros3.mac"
+ numdef DEBUG5
+%idefine d5 _d 5,
+
+ 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 LOADSTACKVARS, -10h
+lsvFirstCluster: resd 1
+lsvFATSector: resd 1
+lsvFATSeg: resw 1
+lsvLoadSeg: resw 1
+lsvDataStart: resd 1
+ endstruc
+
+lsvclSignature equ "CL"
+lsvclBufferLength equ 256
+
+ struc LOADDATA, LOADSTACKVARS - 10h
+ldMemoryTop: resw 1
+ldLoadTop: resw 1
+ldSectorSeg: resw 1
+ldFATType: resb 1
+ldHasLBA: resb 1
+ldClusterSize: resw 1
+ldParaPerSector:resw 1
+ldLoadingSeg: ; word
+lsvCommandLine: ; word
+.start: equ $ - lsvclBufferLength
+.signature: resw 1
+ldLoadUntilSeg: ; word
+lsvExtra: ; word
+.partition: resb 1 ; byte
+.flags: resb 1 ; byte
+ endstruc
+
+lsvefNoDataStart equ 1
+lsvefPartitionNumber equ 2
+
+ struc LOADCMDLINE, LOADDATA - lsvclBufferLength
+ldCommandLine:
+.start: resb lsvclBufferLength
+ endstruc
+
+ struc LBAPACKET
+lpSize: resw 1
+lpCount: resw 1
+lpBuffer: resd 1
+lpSector: resq 1
+ endstruc
+
+ struc PARTINFO
+piBoot: resb 1
+piStartCHS: resb 3
+piType: resb 1
+piEndCHS: resb 3
+piStart: resd 1
+piLength: resd 1
+ endstruc
+
+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
+
+
+%ifndef _MAP
+%elifempty _MAP
+%else ; defined non-empty, str or non-str
+ [map all _MAP]
+%endif
+
+ defaulting
+
+ numdef QUERY_GEOMETRY, 1 ; query geometry via 13.08 (for CHS access)
+ numdef RPL, 1 ; support RPL and do not overwrite it
+ numdef CHS, 1 ; support CHS (if it fits)
+ numdef LBA, 1 ; support LBA (if available)
+ numdef LBA_33_BIT, 1 ; support 33-bit LBA
+ numdef LBA_CHECK_NO_33, 1 ; else: check that LBA doesn't carry
+ numdef MULTIBOOT1, 1 ; use Multiboot specification loader
+ numdef MULTIBOOT2, 1 ; use Multiboot2 specification loader
+ numdef LSVEXTRA, 1 ; use lsvExtra field
+ ; (needed if to use partition scanner)
+
+ numdef LBA_SKIP_CHECK, 0 ; don't use proper LBA extensions check
+ numdef LBA_RETRY, 1 ; retry LBA reads
+ numdef CHS_RETRY, 1 ; retry CHS reads
+ numdef STACKSIZE, 2048
+%if _STACKSIZE < 256
+ %error Too small stack size
+%elif _STACKSIZE > 3 * 1024
+ ; Note that we use 8 KiB for SectorSeg, 8 KiB for FATSeg,
+ ; 512 bytes + (ebpbNew - bpbNew) for the boot sector,
+ ; and a few paragraphs left for MCBs and headers. As the
+ ; protocol is implemented with a 20 KiB reserved area (below
+ ; EBDA / RPL / end of low memory), this results in a maximum
+ ; stack size around 3 KiB (substantially below 4 KiB).
+ %error Too large stack size
+%endif
+ numdef CHECKSUM, 0 ; include checksumming of kernel image
+%if _CHECKSUM
+ %include "inicheck.mac"
+%endif
+
+ strdef PAYLOAD_FILE, "lDOSLOAD.BIN"
+ numdef EXEC_OFFSET, 0
+ numdef EXEC_SEGMENT, 0
+ strdef INILOAD_SIGNATURE, "XX"
+
+ numdef IMAGE_EXE, 0
+ numdef IMAGE_EXE_CS, -16 ; relative-segment for CS
+ numdef IMAGE_EXE_IP, 256 +64 ; value for IP
+ ; The next two are only used if _IMAGE_EXE_AUTO_STACK is 0.
+ numdef IMAGE_EXE_SS, -16 ; relative-segment for SS
+ numdef IMAGE_EXE_SP, 0FFFEh ; value for SP (0 underflows)
+ numdef IMAGE_EXE_AUTO_STACK, 0, 2048 ; allocate stack behind image
+ numdef IMAGE_EXE_MIN, 65536 ; how much to allocate for the process
+%ifndef _IMAGE_EXE_MIN_CALC
+ %define _IMAGE_EXE_MIN_CALC \
+ (((_IMAGE_EXE_MIN \
+ - (payload.actual_end - payload) \
+ - 256 \
+ + _IMAGE_EXE_AUTO_STACK) + 15) & ~15)
+%endif
+ numdef IMAGE_EXE_MAX, -1
+
+ numdef SECOND_PAYLOAD_EXE, 0
+ numdef SECOND_PAYLOAD_EXE_CS, -16
+ numdef SECOND_PAYLOAD_EXE_IP, 256 +64
+ numdef SECOND_PAYLOAD_EXE_SS, -16
+ numdef SECOND_PAYLOAD_EXE_SP, 0FFFEh
+ numdef SECOND_PAYLOAD_EXE_AUTO_STACK, 0, 2048
+ numdef SECOND_PAYLOAD_EXE_MIN, 65536
+%ifndef _SECOND_PAYLOAD_EXE_MIN_CALC
+ %define _SECOND_PAYLOAD_EXE_MIN_CALC \
+ (((_SECOND_PAYLOAD_EXE_MIN \
+ - (second_payload.actual_end - second_payload) \
+ - 256 \
+ + _SECOND_PAYLOAD_EXE_AUTO_STACK) + 15) & ~15)
+%endif
+ numdef SECOND_PAYLOAD_EXE_MAX, -1
+ strdef SECOND_PAYLOAD_FILE, "lDOSEXEC.COM"
+
+
+ strdef INILOAD_CFG, ""
+%ifnidn _INILOAD_CFG, ""
+ %include _INILOAD_CFG
+%endif
+
+
+%if _IMAGE_EXE && _SECOND_PAYLOAD_EXE
+ %error Cannot use both of these.
+%endif
+
+%push
+%define %$string _INILOAD_SIGNATURE
+%strlen %$length %$string
+%if %$length != 2
+ %error Invalid signature
+%endif
+%substr %$letter %$string 1
+%if %$letter <= 32 || %$letter >= 127
+ %error Invalid signature
+%endif
+%substr %$letter %$string 2
+%if %$letter <= 32 || %$letter >= 127
+ %error Invalid signature
+%endif
+%pop
+
+
+ cpu 8086
+ org 0
+start:
+ db "MZ" ; exeSignature
+ ; dec bp, pop dx
+ jmp strict short ms6_entry ; exeExtraBytes
+ ; db 0EBh, 16h ; dw 16EBh
+%if _IMAGE_EXE
+ ; For now hardcoded to carry a .COM-like executable.
+ ; Note: With _IMAGE_EXE_AUTO_STACK, the
+ ; stack segment will be behind the image.
+ dw (payload.end - $$ + 511) / 512 ; exePages
+ dw 0 ; exeRelocItems
+ dw (payload -$$+0) >> 4 ; exeHeaderSize
+ dw (_IMAGE_EXE_MIN_CALC + 15) >> 4 ; exeMinAlloc
+%if _IMAGE_EXE_MAX
+ dw _IMAGE_EXE_MAX ; exeMaxAlloc
+%else
+ dw (_IMAGE_EXE_MIN_CALC + 15) >> 4 ; exeMaxAlloc
+%endif
+%if _IMAGE_EXE_AUTO_STACK
+ dw ((payload.actual_end - payload) \
+ + _IMAGE_EXE_MIN_CALC \
+ - _IMAGE_EXE_AUTO_STACK + 15) >> 4 ; exeInitSS
+ ; ss: payload size minus 512 (conservative, assume DOS
+ ; treats bogus exeExtraBytes as below 512 bytes.)
+ ; + exeMinAlloc
+ ; - auto stack size
+ dw _IMAGE_EXE_AUTO_STACK ; exeInitSP
+ ; sp = auto stack size (eg 800h)
+%else
+ dw _IMAGE_EXE_SS ; exeInitSS
+ dw _IMAGE_EXE_SP ; exeInitSP
+%endif
+ dw 0 ; exeChecksum
+ dw _IMAGE_EXE_IP, _IMAGE_EXE_CS ; exeInitCSIP
+ dw 0 ; exeRelocTable
+%elif _SECOND_PAYLOAD_EXE
+ ; For now hardcoded to carry a .COM-like executable.
+ ; Note: With _SECOND_PAYLOAD_EXE_AUTO_STACK, the
+ ; stack segment will be behind the image.
+ dw (second_payload.end - $$ + 511) / 512 ; exePages
+ dw 0 ; exeRelocItems
+ dw (second_payload -$$+0) >> 4 ; exeHeaderSize
+ dw (_SECOND_PAYLOAD_EXE_MIN_CALC + 15) >> 4 ; exeMinAlloc
+%if _SECOND_PAYLOAD_EXE_MAX
+ dw _SECOND_PAYLOAD_EXE_MAX ; exeMaxAlloc
+%else
+ dw (_SECOND_PAYLOAD_EXE_MIN_CALC + 15) >> 4 ; exeMaxAlloc
+%endif
+%if _SECOND_PAYLOAD_EXE_AUTO_STACK
+ dw ((second_payload.actual_end - second_payload) \
+ + _SECOND_PAYLOAD_EXE_MIN_CALC \
+ - _SECOND_PAYLOAD_EXE_AUTO_STACK + 15) >> 4 ; exeInitSS
+ dw _SECOND_PAYLOAD_EXE_AUTO_STACK ; exeInitSP
+%else
+ dw _SECOND_PAYLOAD_EXE_SS ; exeInitSS
+ dw _SECOND_PAYLOAD_EXE_SP ; exeInitSP
+%endif
+ dw 0 ; exeChecksum
+ dw _SECOND_PAYLOAD_EXE_IP, _SECOND_PAYLOAD_EXE_CS ; exeInitCSIP
+ dw 0 ; exeRelocTable
+%else
+ dw -1 ; exePages
+ dw 0 ; exeRelocItems
+ dw 0 ; exeHeaderSize
+ dw -1 ; exeMinAlloc
+ dw -1 ; exeMaxAlloc
+ dw -16, 0 ; exeInitSS, exeInitSP
+ dw 0 ; exeChecksum
+ dw 100h, -16 ; exeInitCSIP
+ dw 0 ; exeRelocTable
+%endif
+
+ms6_entry:
+ ; This is the MS-DOS 6 / IBMDOS compatible entry point.
+ ; Note that this supports FAT32 for PC-DOS 7.10!
+ ; cs:ip = 70h:0
+ ; ax:bx = first data sector of first cluster,
+ ; including hidden sectors
+ ; 0:7C00h-> boot sector with BPB,
+ ; load unit field set, hidden sectors set
+ ; (actually boot unit in dl; because the "MZ" signature
+ ; destroys dl we assume it's in the BPB too)
+ ; Either:
+ ; dword [ss:sp] = 0:78h = 1Eh * 4 (IVT entry of int 1Eh)
+ ; dword [ss:sp + 4] = old int 1Eh address
+ ; Or:
+ ; ds:si = old int 1Eh address
+ ; 0:500h-> directory entry for BIO file
+ cli
+ cld
+ push dx
+ inc bp ; undo signature instructions
+
+d3 call d3_display_two_characters
+d3 test ax, "00"
+
+; test dx, dx
+; jnz @FF
+ ; Actual DOS will always put a zero word on top of
+ ; the stack. But when the debugger loads us as
+ ; a flat format binary it may set up another
+ ; stack segment or not initialise the stack slot.
+ ; (So as to avoid corrupting the binary.)
+ ; The offset check should suffice anyway.
+ call @F
+@@:
+ pop cx
+ cmp cx, @B + 100h
+ je msdos1_com_entry
+@@:
+
+ mov cx, cs
+ cmp cx, 60h
+ je freedos_entry
+
+ xor cx, cx
+
+ ; Note: It has been observed that some IBMBIO.COM / IO.SYS
+ ; boot sector loaders pass the int 1Eh address on the
+ ; stack (like MS-DOS 7 loading does). So we detect
+ ; whether the first dword (far pointer to IVT entry)
+ ; matches and then assume that the second dword has
+ ; the original int 1Eh address. Else, ds:si is used.
+ mov di, 1Eh * 4 ; -> IVT entry of int 1Eh
+ cmp dx, di ; int 1Eh address on stack ?
+ jne .dssi ; no -->
+ mov bp, sp
+ cmp word [bp + 2], cx ; segment 0 in next word ?
+ jne .dssi ; no -->
+ pop si
+ pop ds ; discard
+ pop si
+ pop ds ; get old int 1Eh address from stack
+.dssi:
+ jmp ms6_continue1
+
+
+error:
+ push cs
+ pop ds
+ mov si, msg.error
+ call disp_error
+ pop si
+ call disp_error
+ xor ax, ax
+ int 16h
+ int 19h
+
+disp_error:
+.:
+ lodsb
+ test al, al
+ jz .ret
+ mov ah, 0Eh
+ mov bx, 7
+ ; push bp
+ ; (call may change bp, but it is not used here any longer.)
+ int 10h
+ ; pop bp
+ jmp short .
+
+msg:
+.error: db "Load error: ", 0
+
+
+query_geometry:
+%if _QUERY_GEOMETRY ; +30 bytes
+ mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
+ %if !_LBA_SKIP_CHECK
+ push dx
+ %endif
+; test dl, dl ; floppy?
+; jns @F ; don't attempt query, might fail -->
+ ; Note that while the original PC BIOS doesn't support this function
+ ; (for its diskettes), it does properly return the error code 01h.
+ ; https://sites.google.com/site/pcdosretro/ibmpcbios (IBM PC version 1)
+ mov ah, 08h
+ xor cx, cx ; initialise cl to 0
+ stc ; initialise to CY
+ int 13h ; query drive geometry
+ jc @F ; apparently failed -->
+ and cx, 3Fh ; get sectors
+ jz @F ; invalid (S is 1-based), don't use -->
+ mov [bp + bsBPB + bpbCHSSectors], cx
+ mov cl, dh ; cx = maximum head number
+ inc cx ; cx = number of heads (H is 0-based)
+ mov [bp + bsBPB + bpbCHSHeads], cx
+@@:
+%endif
+
+%if !_LBA_SKIP_CHECK
+ mov ah, 41h
+ %if _QUERY_GEOMETRY
+ pop dx
+ %else
+ mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
+ %endif
+ mov bx, 55AAh
+ stc
+ int 13h ; 13.41.bx=55AA extensions installation check
+ mov al, 0 ; zero in case of no LBA support
+ jc .no_lba
+ cmp bx, 0AA55h
+ jne .no_lba
+ test cl, 1 ; support bitmap bit 0
+ jz .no_lba
+ inc ax ; al = 1 to indicate LBA support
+.no_lba:
+ mov byte [bp + ldHasLBA], al
+%else
+ mov byte [bp + ldHasLBA], 0
+%endif
+
+%if 1 || _QUERY_GEOMETRY || !_LBA_SKIP_CHECK
+disp_error.ret:
+ retn
+%endif
+
+
+ ; Read a sector using Int13.02 or Int13.42
+ ;
+ ; INP: dx:ax = sector number within partition
+ ; bx:0-> buffer
+ ; (_LBA) ds = ss
+ ; OUT: If unable to read,
+ ; ! jumps to error instead of returning
+ ; If sector has been read,
+ ; dx:ax = next sector number (has been incremented)
+ ; bx:0-> next buffer (bx = es+word[para_per_sector])
+ ; es = input bx
+ ; CHG: -
+ ; STT: ds = ss
+ ;
+ ; Note: If error 09h (data boundary error) is returned,
+ ; the read is done into the ldSectorSeg buffer,
+ ; then copied into the user buffer.
+read_sector:
+ push dx
+ push cx
+ push ax
+ push si
+
+ push bx
+
+; DX:AX==LBA sector number
+; add partition start (= number of hidden sectors)
+ add ax,[bp + bsBPB + bpbHiddenSectors + 0]
+ adc dx,[bp + bsBPB + bpbHiddenSectors + 2]
+
+ %if (!_LBA || !_LBA_33_BIT) && _LBA_CHECK_NO_33
+ jc .err_CY_2
+ %if !_LBA
+.err_CY_2: equ .err_CY_1
+ %endif
+ %endif
+%if _LBA ; +70 bytes (with CHS, +63 bytes without CHS)
+ %if _LBA_33_BIT
+ sbb si, si ; -1 if was CY, 0 else
+ neg si ; 1 if was CY, 0 else
+ %endif
+ xor cx, cx ; cx = 0 (needed if jumping to .no_lba_checked)
+ %if !_LBA_SKIP_CHECK
+ test byte [bp + ldHasLBA], 1
+ jz .no_lba_checked
+ %endif
+ push cx
+ %if _LBA_33_BIT
+ push si ; bit 32 = 1 if operating in 33-bit space
+ %else
+ push cx ; second highest word = 0
+ %endif
+ push dx
+ push ax ; qword sector number (lpSector)
+ push bx
+ push cx ; bx:0 -> buffer (lpBuffer)
+ inc cx
+ push cx ; word number of sectors to read (lpCount)
+ mov cl, 10h
+ push cx ; word size of disk address packet (lpSize)
+ mov si, sp ; ds:si -> disk address packet (on stack)
+
+ mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
+ mov ah, 42h ; 13.42 extensions read
+%if _LBA_RETRY
+ call .int13_retry
+%else
+ call .int13_preserve_lpcount
+%endif
+ jnc .lba_done
+
+%if _LBA_SKIP_CHECK
+ cmp ah, 1 ; invalid function?
+ je .no_lba_skip ; try CHS instead -->
+%endif
+ cmp ah, 9 ; data boundary error?
+ jne .lba_error
+
+ ; push word [si + 4 + 0]
+ push word [si + 4 + 2] ; user buffer
+ push word [bp + ldSectorSeg]
+ pop word [si + 4 + 2]
+ ; and word [si + 4 + 0], byte 0
+
+ mov ah, 42h
+%if _LBA_RETRY
+ call .int13_retry
+%else
+ int 13h
+ ; (don't need .int13_preserve_lpcount as no further call)
+%endif
+.err_CY_2:
+ jc .err_CY_1
+%ifn _CHS
+.err_CY_1: equ .err
+%endif
+
+ pop es
+ ; pop cx
+ call .sectorseg_helper
+
+.lba_done:
+ add sp, 10h
+ pop bx
+ jmp short .chs_done
+
+.lba_error: equ .err
+
+ %if !_CHS
+.no_lba_skip: equ .err
+.no_lba_checked: equ .err
+ %elif _LBA_SKIP_CHECK
+.no_lba_skip:
+ add sp, 8
+ pop ax
+ pop dx
+ %if _LBA_33_BIT
+ pop si
+ pop cx ; cx = 0 (needed as input for next cwd instruction)
+ test si, si
+ mov si, sp ; si == sp
+ %else
+ pop cx
+ pop cx
+ ; si == sp - 16
+ %endif
+ %else
+.no_lba_checked:
+ %if _LBA_33_BIT
+ test si, si
+ %endif
+ mov si, sp ; si == sp
+ %endif
+%endif
+
+%if _CHS ; +70 bytes
+ %if _LBA && _LBA_33_BIT
+ jnz .err_NZ_2
+ %endif
+; dx:ax = LBA sector number, (if _LBA) cx = 0
+; divide by number of sectors per track to get sector number
+; Use 32:16 DIV instead of 64:32 DIV for 8088 compatability
+; Use two-step 32:16 divide to avoid overflow
+ %if !_LBA
+ xchg cx, ax ; cx = low word of sector, clobbers ax
+ xchg ax, dx ; ax = high word of sector, clobbers dx
+ xor dx, dx ; dx:ax = high word of sector
+ %else
+ xchg cx, ax ; cx = low word of sector, ax = 0
+ push dx ; stack = high word of sector
+ cwd ; dx = 0 (because ax was 0)
+ pop ax ; ax = high word of sector
+ ; dx:ax = high word of sector
+ %endif
+ div word [bp + bsBPB + bpbCHSSectors]
+ xchg cx,ax
+ div word [bp + bsBPB + bpbCHSSectors]
+ xchg cx,dx
+
+; DX:AX=quotient, CX=remainder=sector (S) - 1
+; divide quotient by number of heads
+ xchg bx, ax ; bx = low word of quotient, clobbers ax
+ xchg ax, dx ; ax = high word of quotient, clobbers dx
+ xor dx, dx ; dx = 0
+ div word [bp + bsBPB + bpbCHSHeads]
+ ; ax = high / heads, dx = high % heads
+ xchg bx, ax ; bx = high / heads, ax = low quotient
+ div word [bp + bsBPB + bpbCHSHeads]
+
+; bx:ax=quotient=cylinder (C), dx=remainder=head (H)
+; move variables into registers for INT 13h AH=02h
+ mov dh, dl ; dh = head
+ inc cx ; cl5:0 = sector
+ xchg ch, al ; ch = cylinder 7:0, al = 0
+ shr ax, 1
+ shr ax, 1 ; al7:6 = cylinder 9:8
+ ; bx has bits set iff it's > 0, indicating a cylinder >= 65536.
+ or bl, bh ; collect set bits from bh
+ or cl, al ; cl7:6 = cylinder 9:8
+ ; ah has bits set iff it was >= 4, indicating a cylinder >= 1024.
+ or bl, ah ; collect set bits from ah
+ mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
+ ; dl = drive
+.err_NZ_2:
+ jnz .err_NZ_1 ; error if cylinder >= 1024 -->
+ ; ! bx = 0 (for 13.02 call)
+
+; we call INT 13h AH=02h once for each sector. Multi-sector reads
+; may fail if we cross a track or 64K boundary
+ pop es
+
+ mov ax, 0201h ; read one sector
+%if _CHS_RETRY
+ call .int13_retry
+%else
+ int 13h
+%endif
+ jnc .done
+
+ cmp ah, 9 ; data boundary error?
+.err_NZ_1:
+ jne .err
+
+ push es ; user buffer
+ mov es, word [bp + ldSectorSeg]
+
+ mov ax, 0201h
+%if _CHS_RETRY
+ call .int13_retry
+%else
+ int 13h
+%endif
+.err_CY_1:
+ jc .err
+
+ pop es
+ call .sectorseg_helper
+
+.done:
+; increment segment
+ mov bx, es
+%endif
+
+.chs_done:
+ mov es, bx
+ add bx, word [bp + ldParaPerSector]
+
+ pop si
+ pop ax
+ pop cx
+ pop dx
+; increment LBA sector number
+ inc ax
+ jne @F
+ inc dx
+@@:
+ retn
+
+
+%if (_LBA && _LBA_RETRY) || (_CHS && _CHS_RETRY)
+.int13_retry:
+ push ax
+%if _LBA
+ call .int13_preserve_lpcount
+%else
+ int 13h ; first try
+%endif
+ jnc @F ; NC, success on first attempt -->
+
+; reset drive
+ xor ax, ax
+ int 13h
+ jc @F ; CY, reset failed, error in ah -->
+
+; try read again
+ pop ax ; restore function number
+%if _LBA
+ call .int13_preserve_lpcount
+%else
+ int 13h ; retry, CF error status, ah error number
+%endif
+ retn
+
+@@: ; NC or CY, stack has function number
+ inc sp
+ inc sp ; discard word on stack, preserve CF
+ retn
+%endif
+
+%if _LBA
+ ; have to reset the LBAPACKET's lpCount, as the handler may
+ ; set it to "the number of blocks successfully transferred".
+
+ ; hack: si points into unclaimed stack space
+ ; when this is called from the CHS handler.
+ ; this should not cause any issues however.
+ ; actually, if !_LBA_SKIP_CHECK, then si is set
+ ; to point to claimed stack space. also legal.
+.int13_preserve_lpcount:
+ push word [si + lpCount]
+ int 13h
+ pop word [si + lpCount]
+ retn
+%endif
+
+.sectorseg_helper:
+ xor si, si
+ mov ds, word [bp + ldSectorSeg]
+ push di
+ ; mov di, cx
+ xor di, di
+ mov cx, word [bp + bsBPB + bpbBytesPerSector]
+ rep movsb
+ pop di
+
+ push ss
+ pop ds
+ retn
+
+.err:
+error_diskaccess:
+ call error
+ db "Disk read error.", 0
+
+error_shortfile:
+ call error
+ db "File is too short.", 0
+
+error_badchain:
+ call error
+ db "Bad cluster chain.", 0
+
+error_badclusters:
+ call error
+ db "Bad amount of clusters.", 0
+
+error_outofmemory:
+ call error
+ db "Out of memory.", 0
+
+%assign num 512-($-$$)
+%if num >= 3
+%assign num num - 3
+ %warning num bytes in front of ms7_entry
+ _fill 512 - 3,38,start
+error_outofmemory_j1:
+ jmp error_outofmemory
+%else
+error_outofmemory_j1: equ error_outofmemory
+ %warning num bytes in front of ms7_entry
+%endif
+ _fill 512,38,start
+ms7_entry:
+ ; This is the MS-DOS 7 compatible entry point.
+ ; Supports FAT32 too.
+ ; cs:ip = 70h:200h
+ ; (si:)di = first cluster of load file
+ ; dwo [ss:bp - 4] = first data sector (with hidden sectors)
+ ; dwo [ss:sp] = 0:78h (IVT entry of int 1Eh)
+ ; dwo [ss:sp + 4] = old int 1Eh address
+ ; ss:bp -> boot sector with (E)BPB,
+ ; load unit field set, hidden sectors set
+ inc dx
+ dec dx ; "BJ" signature (apparently not about FAT32 support)
+ cli
+ cld
+
+ jmp .continue ; jump to handler above 600h (sector loads 800h bytes)
+
+.ms6_common:
+ mov ax, cs
+ add ax, (3 * 512) >> 4
+
+.continue2_set_extra_and_empty_cmdline:
+%if _LSVEXTRA
+ and word [bp + lsvExtra], 0
+%endif
+ and word [bp + lsvCommandLine], 0
+.continue2:
+ mov word [bp + lsvLoadSeg], ax
+
+ xor ax, ax
+ mov word [bp + lsvFATSeg], ax ; initialise to zero (for FAT12)
+ dec ax
+ mov word [bp + lsvFATSector + 0], ax
+ mov word [bp + lsvFATSector + 2], ax ; initialise to -1
+
+ ; Actually it seems that the MS-DOS 7 loaders load 4 sectors
+ ; instead of only three (as the MS-DOS 6 loaders do).
+ ; We use this to store specific handling in that last sector.
+
+ jmp ldos_entry.ms7_common
+
+
+finish_continue:
+ add ax, bx ; = cs + rounded up length
+ sub ax, word [bp + ldLoadTop] ; = paras to move down
+ jbe short finish_load
+
+ push ax
+ neg ax
+ add ax, bx ; ax = cs - paras to move down
+ jnc short error_outofmemory_j1
+ mov di, relocate_to
+ push ax
+ push di ; dword on stack: relocate_to
+ cmp ax, 60h + 1
+ jb short error_outofmemory_j1
+ dec ax ; one less to allow relocator
+ mov es, ax
+
+
+finish_relocation:
+ xor di, di ; es:di -> where to put relocator
+
+ push es
+ push di ; dword on stack: relocator destination
+
+ mov ds, bx ; ds => unrelocated cs
+ inc ax ; ax => where to relocate to
+ mov si, relocator ; ds:si -> relocator
+relocator_size equ relocator.end - relocator
+%rep (relocator_size + 1) / 2
+ movsw ; place relocator
+%endrep
+ mov es, ax
+ xor di, di ; -> where to relocate to
+ xor si, si ; ds:si = cs:0
+
+ mov cx, word [bp + lsvLoadSeg]
+ sub cx, bx ; length of currently loaded fragment
+ mov bx, 1000h
+ mov ax, cx
+ cmp ax, bx ; > 64 KiB ?
+ jbe @F
+ mov cx, bx ; first relocate the first 64 KiB
+@@:
+ sub ax, cx ; how much to relocate later
+ shl cx, 1
+ shl cx, 1
+ shl cx, 1 ; how much to relocate first,
+ ; << 3 == convert paragraphs to words
+ retf ; jump to relocator
+
+
+ ; ds => first chunk of to be relocated data
+ ; es => first chunk of relocated data
+ ; bx = 1000h (64 KiB >> 4)
+ ; ax = number of paragraphs after first chunk (in next chunk)
+relocate_to:
+@@:
+ mov dx, es
+ add dx, bx
+ mov es, dx ; next segment
+
+ mov dx, ds
+ add dx, bx
+ mov ds, dx ; next segment
+
+ sub ax, bx ; = how much to relocate after this round
+ mov cx, 1000h << 3 ; in case another full 64 KiB to relocate
+ jae @F ; another full 64 KiB to relocate -->
+ add ax, bx ; restore
+ shl ax, 1
+ shl ax, 1
+ shl ax, 1 ; convert paragraphs to words
+ xchg cx, ax ; cx = that many words
+ xor ax, ax ; no more to relocate after this round
+
+@@:
+ xor si, si
+ xor di, di
+ rep movsw ; relocate next chunk
+ test ax, ax ; another round needed?
+ jnz @BB ; yes -->
+
+ pop ax
+ sub word [bp + lsvLoadSeg], ax
+ push ss
+ pop ds
+
+ ; ds = ss
+ ; cs = low enough to complete load
+ ; lsvLoadSeg => after last loaded fragment
+ ; ldLoadTop => after last available memory
+ ; ldParaPerSector = initialised
+ ; word [ss:sp] = payload.actual_end in paras
+finish_load:
+ pop ax
+ mov bx, cs
+ add ax, bx
+ mov word [bp + ldLoadUntilSeg], ax
+ ; ldLoadUntilSeg => after last to-be-loaded paragraph
+
+ mov bx, word [bp + lsvLoadSeg]
+ mov word [bp + ldLoadingSeg], bx
+ cmp bx, ax
+ jae short loaded_all_if_ae ; (for FreeDOS entrypoint) already loaded -->
+
+ mov word [bp + ldLoadingSeg], cs
+
+ mov ax, [bp + lsvFirstCluster]
+ mov dx, [bp + lsvFirstCluster + 2]
+ mov di, [bp + lsvFATSector]
+ mov si, [bp + lsvFATSector + 2]
+ call check_clust
+ jc short error_badchain_j
+
+skip_next_clust:
+ call clust_to_first_sector
+ push cx
+ push bx
+ mov cx, [bp + ldClusterSize]
+skip_next_sect:
+ push cx
+
+ mov bx, [bp + ldLoadingSeg]
+ cmp bx, [bp + ldLoadUntilSeg]
+ jae loaded_all.3stack
+
+ mov cx, bx
+ add cx, [bp + ldParaPerSector]
+ cmp cx, [bp + lsvLoadSeg]
+ ja skipped_all
+ inc ax ; emulate read_sector:
+ jnz @F
+ inc dx ; dx:ax += 1
+@@:
+ mov bx, cx ; bx += paras per sector
+ mov [bp + ldLoadingSeg], bx
+
+ pop cx
+ loop skip_next_sect
+ pop bx
+ pop cx
+ call clust_next
+ jnc skip_next_clust
+end_of_chain:
+ inc ax
+ inc ax
+ test al, 8 ; set in 0FFF_FFF8h--0FFF_FFFFh,
+ ; clear in 0, 1, and 0FFF_FFF7h
+ jz short error_badchain_j
+ mov bx, [bp + ldLoadingSeg]
+ cmp bx, [bp + ldLoadUntilSeg]
+loaded_all_if_ae:
+ jae loaded_all
+ jmp error_shortfile
+
+
+skipped_all:
+ call read_sector
+ ; we can depend on the fact that at least
+ ; up to end was already loaded, so this
+ ; (successful) read_sector call loaded
+ ; at least 32 bytes starting at end.
+ ; therefore, we can put part of the
+ ; remaining handler into these 32 bytes.
+ jmp skipped_all_continue
+
+
+error_badchain_j:
+ jmp error_badchain
+
+
+ ; ds => first chunk of to be relocated data
+ ; es => first chunk of relocation destination
+ ; cx = number of words in first chunk
+relocator:
+ rep movsw
+ retf ; jump to relocated relocate_to
+.end:
+
+
+ ; INP: dx:ax = cluster - 2 (0-based cluster)
+ ; OUT: cx:bx = input dx:ax
+ ; dx:ax = first sector of that cluster
+ ; CHG: -
+clust_to_first_sector:
+ push dx
+ push ax
+ push dx
+ mul word [bp + ldClusterSize]
+ xchg bx, ax
+ xchg cx, dx
+ pop ax
+ mul word [bp + ldClusterSize]
+ test dx, dx
+ jnz short error_badchain_j
+ xchg dx, ax
+ add dx, cx
+.cy_error_badchain:
+ jc short error_badchain_j
+ xchg ax, bx
+
+ add ax, [bp + lsvDataStart]
+ adc dx, [bp + lsvDataStart + 2]
+ jc short .cy_error_badchain
+ ; dx:ax = first sector in cluster
+ pop bx
+ pop cx ; cx:bx = cluster
+ retn
+
+
+ ; INP: cx:bx = cluster (0-based)
+ ; si:di = loaded FAT sector, -1 if none
+ ; OUT: CY if no next cluster
+ ; NC if next cluster found,
+ ; dx:ax = next cluster value (0-based)
+ ; si:di = loaded FAT sector
+ ; CHG: cx, bx
+clust_next:
+ mov ax, bx
+ mov dx, cx
+ add ax, 2
+ adc dx, 0
+
+ push es
+ cmp byte [bp + ldFATType], 16
+ je .fat16
+ ja .fat32
+
+.fat12:
+; FAT12 entries are 12 bits, bytes are 8 bits. Ratio is 3 / 2,
+; so multiply cluster number by 3 first, then divide by 2.
+ ; ax = cluster number (up to 12 bits set)
+ mov dx, ax
+ shl ax, 1 ; = 2n (up to 13 bits set)
+ add ax, dx ; = 2n+n = 3n (up to 14 bits set)
+ shr ax, 1 ; ax = byte offset into FAT (0..6129)
+ ; CF = whether to use high 12 bits
+ sbb cx, cx ; = -1 iff CY, else 0
+
+; Use the calculated byte offset as an offset into the FAT
+; buffer, which holds all of the FAT's relevant data.
+ mov es, [bp + lsvFATSeg]
+ xchg bx, ax ; bx -> 16-bit word in FAT to load
+
+; get 16 bits from FAT
+ mov ax, [es:bx]
+
+ and cl, 4 ; = 4 iff CY after shift, else 0
+ shr ax, cl ; shift down iff odd entry, else unchanged
+ and ax, 0FFFh ; insure it's only 12 bits
+ jmp short .gotvalue_zero_dx
+
+.fat32:
+ ; * 4 = byte offset into FAT (0--4000_0000h)
+ add ax, ax
+ adc dx, dx
+.fat16:
+ ; * 2 = byte offset into FAT (0--2_0000h)
+ add ax, ax
+ adc dx, dx
+
+ push ax
+ xchg ax, dx
+ xor dx, dx ; dx:ax = high word
+ div word [bp + bsBPB + bpbBytesPerSector]
+ xchg bx, ax ; bx = high word / divisor
+ pop ax ; dx = remainder, ax = low word
+ div word [bp + bsBPB + bpbBytesPerSector]
+ xchg dx, bx ; dx:ax = result, bx = remainder
+ ; dx:ax = sector offset into FAT (0--200_0000h)
+ ; bx = byte offset into FAT sector (0--8190)
+ cmp dx, si
+ jne @F ; read sector
+ cmp ax, di
+ je @FF ; sector is already buffered
+@@:
+ mov si, dx
+ mov di, ax
+ mov word [bp + lsvFATSector + 2], dx
+ mov word [bp + lsvFATSector + 0], ax
+
+ push bx
+ add ax, [bp + bsBPB + bpbReservedSectors]
+ adc dx, 0
+ mov bx, [bp + lsvFATSeg]
+ call read_sector
+ pop bx
+@@:
+ mov es, [bp + lsvFATSeg]
+ mov dx, [es:bx + 2]
+ mov ax, [es:bx] ; dx:ax = FAT32 entry
+
+ cmp byte [bp + ldFATType], 16 ; is it FAT32 ?
+ jne @F ; yes -->
+.gotvalue_zero_dx:
+ xor dx, dx ; no, clear high word
+@@:
+ pop es
+
+ ; INP: dx:ax = cluster value, 2-based
+ ; OUT: dx:ax -= 2 (makes it 0-based)
+ ; CY iff invalid cluster
+check_clust:
+ and dh, 0Fh
+ sub ax, 2
+ sbb dx, 0
+
+ cmp byte [bp + ldFATType], 16
+ ja .fat32
+ je .fat16
+
+.fat12:
+ cmp ax, 0FF7h - 2
+ jmp short .common
+
+.fat32:
+ cmp dx, 0FFFh
+ jb @F ; CY here means valid ...-
+
+.fat16:
+ cmp ax, 0FFF7h - 2
+@@: ; -... or if NC first, CY here also
+.common:
+ cmc ; NC if valid
+ retn
+
+
+ms6_continue1:
+ mov es, cx
+ mov bp, 7C00h
+
+ mov word [es:di], si
+ mov word [es:di + 2], ds ; restore old int 1Eh address
+
+ mov ss, cx
+ mov sp, 7C00h + lsvCommandLine
+
+ mov dx, word [es:500h + 26]
+ mov cx, word [es:500h + 20]
+ mov word [bp + lsvFirstCluster + 0], dx
+ mov word [bp + lsvFirstCluster + 2], cx
+
+ sub bx, word [bp + bsBPB + bpbHiddenSectors + 0]
+ sbb ax, word [bp + bsBPB + bpbHiddenSectors + 2]
+ mov word [bp + lsvDataStart + 0], bx
+ mov word [bp + lsvDataStart + 2], ax
+ jmp ms7_entry.ms6_common
+
+
+%assign num 1020-($-$$)
+%warning num bytes in front of ldos_entry
+ _fill 1020,38,start
+ dw "lD" ; always this signature (word [1020] == 446Ch)
+ dw _INILOAD_SIGNATURE
+ ; two printable non-blank ASCII characters
+ ; (ie both bytes in the range 21h..7Eh)
+ ; Rx = RxDOS kernel
+ ; FD = FreeDOS kernel
+ ; TP = TestPL
+ ; (lD)eb = lDebug
+ ; (lD)Db = lDDebug
+%if ($ - $$) != 1024
+ %error Invalid signature
+%endif
+ldos_entry:
+ cli
+ cld
+
+ ; cs:ip = 70h:400h
+ ; dwo [ss:bp - 4] = first data sector (without hidden sectors)
+ ; wo [ss:bp - 6] = load_seg, => after last loaded data
+ ; wo [ss:bp - 8] = fat_seg, 0 if invalid
+ ; initialised to 0 by MS-DOS 6, 7, FreeDOS entrypoints
+ ; fat_sector is not used for FAT12 !
+ ; wo [ss:bp - 12] = fat_sector, -1 if none (FAT16)
+ ; dwo [ss:bp - 12] = fat_sector, -1 if none (FAT32)
+ ; initialised to -1 by MS-DOS 6, 7, FreeDOS entrypoints
+ ; wo [ss:bp - 16] = first_cluster (FAT16, FAT12)
+ ; dwo [ss:bp - 16] = first_cluster (FAT32)
+ ; initialised to 0 by FreeDOS entrypoint
+ ;
+ ; Extension 1:
+ ; lsvExtra (word [ss:bp - 18]) may be set,
+ ; not sure about interface yet. allows
+ ; to not initialise data start, or to specify
+ ; a partition number instead of offset
+ ;
+ ; Extension 2:
+ ; word [ss:bp - 20] = signature "CL" if valid
+ ; 256bytes [ss:bp - 20 - 256] = ASCIZ command line string
+
+ xor ax, ax
+ push ax ; push into lsvExtra if sp -> LSV
+%if _LSVEXTRA
+ mov word [bp + lsvExtra], ax
+ ; byte [ss:bp - 18] = partition number
+ ; byte [ss:bp - 17] = flags for initialisation
+%endif
+ push ax ; push into lsvCommandLine if sp -> LSV
+
+.ms7_common:
+ mov ax, cs
+ mov cx, word [bp + lsvLoadSeg]
+ sub cx, ax
+ cmp cx, (end -$$+0) >> 4
+ jae @F
+error_notfullyloaded:
+ call error
+ db "Initial loader not fully loaded.", 0
+@@:
+
+ mov bx, (payload.actual_end -$$+0 +15) >> 4
+ cmp cx, bx
+ jbe @F
+ add bx, ax
+ mov word [bp + lsvLoadSeg], bx
+@@:
+
+init_memory:
+; Get conventional memory size and store it
+ int 12h
+ mov cl, 6
+ shl ax, cl
+%if _RPL
+ xor si, si
+ xchg dx, ax
+ mov ds, si
+ lds si, [4 * 2Fh]
+ add si, 3
+ lodsb
+ cmp al, 'R'
+ jne .no_rpl
+ lodsb
+ cmp al, 'P'
+ jne .no_rpl
+ lodsb
+ cmp al, 'L'
+ jne .no_rpl
+ mov ax, 4A06h
+ int 2Fh
+.no_rpl:
+ xchg ax, dx
+%endif
+ push ax
+ ; sub ax, 32 >> 4 ; make space for two MCBs: top MCB, RPL MCB
+ dec ax
+ dec ax
+ mov cx, ax
+ sub ax, (8192 + 16) >> 4
+ dec cx ; => last paragraph of higher buffer (16-byte trailer)
+ mov dx, ax ; => first paragraph of higher buffer
+ mov bx, cx
+ and dx, 0F000h ; 64 KiB chunk of first paragraph of higher buffer
+ and bx, 0F000h ; 64 KiB chunk of last paragraph of higher buffer
+ cmp bx, dx ; in same chunk?
+ mov bx, ax
+ je .gotsectorseg ; yes, use higher buffer as sector buffer ->
+ ; bx = use higher buffer as FAT buffer
+ inc bx ; => 8 KiB buffer (no 16-byte trailer)
+ sub ax, (8192 + 32) >> 4
+ ; 32 = leave space for higher buffer MCB + header
+ ; +16 from the above calcs for 16-byte trailer
+ mov cx, ax ; use lower buffer as sector buffer
+ jmp short .gotsegs
+
+.gotsectorseg:
+ ; ax = use higher buffer as sector buffer
+ sub bx, (8192 + 32) >> 4 ; use lower buffer as FAT buffer
+ ; 32 = leave space for higher buffer MCB + header
+ mov cx, bx
+ ; ax = sector seg
+ ; bx = FAT seg
+ ; cx = the lower of the two
+.gotsegs:
+ sub cx, (+_STACKSIZE -LOADCMDLINE + 512 + (ebpbNew - bpbNew) + 32 + 15) >> 4
+ ; +_STACKSIZE = stack space
+ ; -LOADCMDLINE = load cmd line + data + lsv space
+ ; 512 = boot sector (allows finding filename)
+ ; (ebpbNew - bpbNew) = additional space for BPBN moving
+ ; 32 = leave space for lower buffer MCB + header
+ ; cx = stack seg
+
+ dec cx ; leave space for stack + BPB buffer MCB
+ cmp cx, word [bp + lsvLoadSeg]
+ jnb @F
+.error_outofmemory:
+ jmp error_outofmemory
+@@:
+
+ push ax
+ mov dx, ss
+ mov ax, bp
+ add ax, 512 + 15
+ jnc @F
+ mov ax, 1_0000h >> 1
+ db __TEST_IMM16 ; (skip one shr)
+@@:
+ shr ax, 1
+ shr ax, 1
+ shr ax, 1
+ shr ax, 1
+ add dx, ax
+ cmp dx, cx
+ ja .error_outofmemory
+
+ ; note that the next conditional doesn't jump for lsvFATSeg = 0
+ mov dx, word [bp + lsvFATSeg]
+ add dx, (8192) >> 4
+ cmp dx, cx
+ ja .error_outofmemory
+ pop ax
+
+ pop dx ; top of memory (=> start of RPL, EBDA, video memory)
+ inc cx ; => stack + BPB buffer
+ push ss
+ pop ds
+ lea si, [bp + lsvCommandLine.start]
+ mov es, cx
+ mov di, _STACKSIZE - LOADCMDLINE + ldCommandLine.start
+ ; -> cmd line target
+ push cx ; top of memory below buffers
+ mov cx, (LOADCMDLINE_size + 1) >> 1
+ rep movsw ; copy cmd line
+%if lsvCommandLine.start + fromwords(words(LOADCMDLINE_size)) != lsvCommandLine.signature
+ %error Unexpected structure layout
+%endif
+ cmp word [si], lsvclSignature
+ je @F ; if command line given -->
+ mov byte [es: _STACKSIZE - LOADCMDLINE + ldCommandLine.start ], cl
+ ; truncate as if empty line given
+ dec cx ; cl = 0FFh
+@@:
+ mov byte [es:di - 1], cl
+ ; remember whether command line given
+ ; = 0 if given (also truncates if too long)
+ ; = 0FFh if not given
+
+ push ax
+%if lsvCommandLine.signature + 2 != lsvExtra
+ %error Unexpected structure layout
+%endif
+ lodsw
+ ; lea si, [bp + lsvExtra]
+ ; ds:si -> lsv + BPB
+ mov di, _STACKSIZE - LOADCMDLINE + lsvExtra
+ ; es:di -> where to place lsv
+ mov cx, (- lsvExtra + 512 + 1) >> 1
+ rep movsw ; copy lsv (including lsvExtra) and BPB
+ xor ax, ax
+ mov cx, ((ebpbNew - bpbNew + 15) & ~15) >> 1
+ rep stosw ; initialise area behind sector (left so for FAT32)
+ pop ax
+ pop cx
+ mov ss, cx
+ mov sp, _STACKSIZE
+ ; -> above end of stack space
+ mov bp, _STACKSIZE - LOADCMDLINE
+ ; -> BPB, above end of lsv
+ dec cx ; => space for stack + BPB buffer MCB
+ sti
+
+ ; ax => sector buffer
+ ; bx => FAT buffer
+ ; cx => above end of memory available for load
+ ; dx => above end of memory used by us
+ mov word [bp + ldMemoryTop], dx
+ mov word [bp + ldLoadTop], cx
+ mov word [bp + ldSectorSeg], ax
+
+ mov ds, word [bp + lsvFATSeg]
+ xor si, si ; ds:si -> FAT buffer
+ mov es, bx
+ xor di, di ; es:di -> where to move
+ mov cx, 8192 >> 1
+ rep movsw
+ mov word [bp + lsvFATSeg], bx
+
+ push ds ; to check for word [lsvFATSeg] == zero later on
+
+ push ss
+ pop es
+ push ss
+ pop ds
+
+ mov bx, [bp + bsBPB + bpbSectorsPerFAT]
+ test bx, bx
+ jz .is_fat32
+
+ lea si, [bp + 510] ; -> last source word
+ lea di, [si + (ebpbNew - bpbNew)] ; -> last dest word
+ mov cx, (512 - bsBPB - bpbNew + 1) >> 1
+ ; move sector up, except common BPB start part
+%if ((512 - bsBPB - bpbNew + 1) >> 1) <= 20
+ %fatal Need AMD erratum 109 workaround
+%endif
+ std ; AMD erratum 109 handling not needed
+ rep movsw
+ cld
+
+ mov word [bp + lsvFirstCluster + 2], cx
+ mov word [bp + lsvFATSector + 2], cx
+
+ mov word [bp + bsBPB + ebpbSectorsPerFATLarge], bx
+ mov word [bp + bsBPB + ebpbSectorsPerFATLarge + 2], cx
+ mov word [bp + bsBPB + ebpbFSFlags], cx
+ ; FSVersion, RootCluster, FSINFOSector, BackupSector, Reserved:
+ ; uninitialised here (initialised by loaded_all later)
+.is_fat32:
+%if 1 || _QUERY_GEOMETRY || !_LBA_SKIP_CHECK
+ call query_geometry
+ ; The ebpbNew BPBN needs to be initialised
+ ; to use this function. It must be called
+ ; before using read_sector (used by the FAT12
+ ; FAT loader, or by finish_load later).
+%endif
+
+%if _LSVEXTRA
+ test byte [bp + lsvExtra.flags], -1
+ jz @F
+
+ mov cx, cs
+ mov ax, word [bp + lsvLoadSeg]
+ sub ax, cx
+ cmp ax, (end_of_handle_lsv_extra_flags + 15 -$$+0) >> 4
+ jb error_notfullyloaded
+
+ call handle_lsv_extra_flags
+@@:
+%endif
+
+; adjusted sectors per cluster (store in a word,
+; and decode EDR-DOS's special value 0 meaning 256)
+ xor ax, ax
+ mov al, [bp + bsBPB + bpbSectorsPerCluster]
+ dec al
+ inc ax
+ mov [bp + ldClusterSize], ax
+
+; 16-byte paragraphs per sector
+ mov ax, [bp + bsBPB + bpbBytesPerSector]
+ mov cl, 4
+ shr ax, cl
+ mov [bp + ldParaPerSector], ax
+
+; total sectors
+ ; After the prior shr instruction, ax is < 8000h,
+ ; so the following cwd always zeros dx.
+ cwd
+ mov ax, [bp + bsBPB + bpbTotalSectors]
+ test ax, ax
+ jnz @F
+ mov dx, [bp + bsBPB + bpbTotalSectorsLarge + 2]
+ mov ax, [bp + bsBPB + bpbTotalSectorsLarge]
+
+ ; fall through and let it overwrite the field with the
+ ; already current contents. saves a jump.
+@@:
+ mov [bp + bsBPB + bpbTotalSectorsLarge + 2], dx
+ mov [bp + bsBPB + bpbTotalSectorsLarge], ax
+
+ ; dx:ax = total sectors
+
+ cmp word [bp + bsBPB + bpbSectorsPerFAT], 0
+ mov byte [bp + ldFATType], 32
+ je .got_fat_type
+
+ ; dx:ax = total amount of sectors
+ sub ax, word [bp + lsvDataStart]
+ sbb dx, word [bp + lsvDataStart + 2]
+
+ ; dx:ax = total amount of data sectors
+ mov bx, ax
+ xchg ax, dx
+ xor dx, dx
+ div word [bp + ldClusterSize]
+ xchg bx, ax
+ div word [bp + ldClusterSize]
+ ; bx:ax = quotient, dx = remainder
+ ; bx:ax = number of clusters
+ test bx, bx
+ jz @F
+.badclusters:
+ jmp error_badclusters
+
+@@:
+ cmp ax, 0FFF7h - 2
+ ja .badclusters
+ mov byte [bp + ldFATType], 16
+ cmp ax, 0FF7h - 2
+ ja .got_fat_type
+
+ mov byte [bp + ldFATType], 12
+ pop ax
+ test ax, ax
+ jnz .got_fat12
+
+; lsvFATSeg was zero! This means the FAT isn't loaded yet.
+
+; Load the entire FAT into memory. This is easily feasible for FAT12,
+; as the FAT can only contain at most 4096 entries.
+; (The exact condition should be "at most 4087 entries", or with a
+; specific FF7h semantic, "at most 4088 entries"; the more reliable
+; and portable alternative would be "at most 4080 entries".)
+; Thus, no more than 6 KiB need to be read, even though the FAT size
+; as indicated by word[sectors_per_fat] could be much higher. The
+; first loop condition below is to correctly handle the latter case.
+; (Sector size is assumed to be a power of two between 32 and 8192
+; bytes, inclusive. An 8 KiB buffer is necessary if the sector size
+; is 4 or 8 KiB, because reading the FAT can or will write to 8 KiB
+; of memory instead of only the relevant 6 KiB. This is always true
+; if the sector size is 8 KiB, and with 4 KiB sector size it is true
+; iff word[sectors_per_fat] is higher than one.)
+ mov di, 6 << 10 ; maximum size of FAT12 to load
+ mov cx, [bp + bsBPB + bpbSectorsPerFAT]
+ ; maximum size of this FS's FAT
+ ; If we're here, then ax = 0 (jnz jumped if not),
+ ; so this cwd always zeros dx.
+ cwd
+ mov ax, [bp + bsBPB + bpbReservedSectors]; = first FAT sector
+ mov bx, [bp + lsvFATSeg]
+@@:
+ call read_sector ; read next FAT sector
+ sub di, [bp + bsBPB + bpbBytesPerSector]
+ ; di = bytes still left to read
+ jbe @F ; if none -->
+ ; (jbe means jump if CF || ZF)
+ loop @B ; if any FAT sector still remains -->
+@@: ; one of the limits reached; FAT read
+
+.got_fat12:
+ db __TEST_IMM8 ; skip pop ax
+.got_fat_type:
+ pop ax
+
+ mov ax, (payload.actual_end -$$+0 +15) >> 4
+ push ax
+ ; on stack: payload.actual_end in paragraphs
+ mov bx, [bp + ldParaPerSector]
+ dec bx ; para per sector - 1
+ add ax, bx ; round up
+ not bx ; ~ (para per sector - 1)
+ and ax, bx ; rounded up,
+ ; ((payload.actual_end -$$+0 +15) >> 4 + pps - 1) & ~ (pps - 1)
+
+ mov bx, cs
+ jmp finish_continue
+
+
+%assign num 1024+512-4-($-$$)
+%warning num bytes in front of end
+ _fill 1024+512-4,38,start
+ ; -4 is for the following two instructions.
+ ; they want execution to fall through to
+ ; load_next_clust_continue. placing them
+ ; at the very end of the 3 sectors allows
+ ; not to use a jump here.
+
+load_next_clust:
+ call clust_to_first_sector
+ push cx
+ align 16, nop
+ _fill 1024+512,90h,start ; check that we are at 3 sectors end
+end:
+
+load_next_clust_continue:
+ push bx
+ mov cx, [bp + ldClusterSize]
+load_next_sect:
+ push cx
+ mov bx, [bp + ldLoadingSeg]
+ cmp bx, [bp + ldLoadUntilSeg]
+ jae loaded_all.3stack_j
+
+ call read_sector
+skipped_all_continue:
+ mov [bp + ldLoadingSeg], bx
+ pop cx
+ loop load_next_sect
+ pop bx
+ pop cx
+ call clust_next
+ jnc load_next_clust
+ jmp end_of_chain
+
+%if ($ - end) > 32
+ %error load_next part exceeds end+32
+%endif
+
+ ; if we jump to here, then the whole file has
+ ; been loaded, so this jump doesn't have to
+ ; stay in the 32 bytes after the end label.
+loaded_all.3stack_j:
+ jmp loaded_all.3stack
+
+
+ms7_entry.continue:
+ pop bx
+ pop es
+ pop word [es:bx]
+ pop word [es:bx + 2]
+
+ lea bx, [bp + lsvCommandLine]
+ cmp sp, bx
+ jbe @F
+ mov sp, bx
+@@:
+ mov word [bp + lsvFirstCluster + 0], di
+ mov word [bp + lsvFirstCluster + 2], si
+
+ mov ax, word [bp + bsBPB + bpbHiddenSectors + 0]
+ mov dx, word [bp + bsBPB + bpbHiddenSectors + 2]
+ sub word [bp + lsvDataStart + 0], ax
+ sbb word [bp + lsvDataStart + 2], dx
+
+ mov ax, cs
+ add ax, (4 * 512) >> 4
+
+ jmp ms7_entry.continue2_set_extra_and_empty_cmdline
+
+
+%assign num 2046-($-$$)
+%warning num bytes in front of end2
+ _fill 2046,38,start
+ dw "MS" ; signature of MS-DOS 7 load
+ align 16, db 38
+end2:
+
+
+ ; This handling is in the second header part,
+ ; behind the needed part to finish loading.
+ ; It is only used when the file is completely loaded.
+loaded_all.3stack:
+ pop ax
+ pop ax
+ pop ax
+loaded_all:
+ mov ax, word [bp + bsBPB + bpbSectorsPerFAT]
+ test ax, ax
+ jz .fat32
+
+ xor ax, ax
+ push ss
+ pop es
+ lea di, [bp + bsBPB + ebpbFSFlags]
+ mov cx, (EBPB_size - ebpbFSFlags) / 2
+ rep stosw
+ ; initialise ebpbFSFlags (reinit), ebpbFSVersion,
+ ; ebpbRootCluster, ebpbFSINFOSector, ebpbBackupSector,
+ ; ebpbReserved
+
+.fat32:
+
+%if _CHECKSUM
+ push cs
+ pop ds
+
+ mov si, checksumheader
+ mov cx, CHECKSUMHEADER_size / 2
+ xor bx, bx
+@@:
+ cmp si, ..@checksumfield
+ lodsw
+ jne @F
+ xor ax, ax
+@@:
+ add bx, ax
+ loop @BB
+
+ test bx, bx
+ jnz error_header_checksum_failed
+
+ testopt [..@checksumtype], 8000h
+ jnz @F
+
+ call checksum_crc16_6_paragraphs_start_cs
+ int3
+
+ push cs
+ pop ds
+
+ cmp ax, word [..@checksumfield]
+ jne error_data_checksum_failed
+..@data_checksum_ignore_failure_debugger:
+@@:
+%endif
+
+ push ss
+ pop es
+ lea di, [bp + ldCommandLine.start]
+ mov cx, lsvclBufferLength
+ xor ax, ax
+ push word [bp + ldCommandLine.start + lsvclBufferLength - 1]
+ ; get sentinel (whether command line given)
+ repne scasb ; scan for terminator
+ pop ax ; al = 0FFh if no command line given
+ ; al = 0 else
+ rep stosb ; clear remainder of buffer
+
+ mov ax, cs
+ add ax, ((payload -$$+0) >> 4) + _EXEC_SEGMENT
+ push ax
+%if _EXEC_OFFSET
+ mov ax, _EXEC_OFFSET
+%else
+ xor ax, ax
+%endif
+ push ax
+ ; cs:ip = xxxxh:_EXEC_OFFSET
+ ; entire payload loaded (payload -- payload.actual_end)
+ ; LOADSTACKVARS and LOADDATA and EBPB and ebpbNew BPBN set
+ ; LOADCMDLINE set (ASCIZ, up to 255 bytes + 1 byte terminator)
+ ; word [ldCommandLine.start] = 0FF00h if had invalid signature
+ retf
+
+
+%if _CHECKSUM
+error_header_checksum_failed:
+ call error
+ db "Header checksum failed.", 0
+
+error_data_checksum_failed:
+ stc
+ int3
+ jnc ..@data_checksum_ignore_failure_debugger
+ call error
+ db "Data checksum failed.", 0
+%endif
+
+
+freedos_entry:
+ ; This is the FreeDOS compatible entry point.
+ ; Supports FAT32 too.
+ ; cs:ip = 60h:0
+ ; whole load file loaded
+ ; first cluster of load file: not given!
+ ; first data sector: not given!
+ ; int 1Eh not modified, original address: not given!
+ ; bl = load unit (not used by us)
+ ; ss:bp -> boot sector with (E)BPB,
+ ; load unit field set, hidden sectors set
+ ; (usually at 1FE0h:7C00h)
+ ; NEW: word [ss:bp - 14h] = "CL" to indicate command line
+ ; then ss:bp - 114h -> 256 byte ASCIZ string
+
+ lea bx, [bp + lsvCommandLine.start]
+ ; ss:bx -> command line buffer, if any
+ cmp bp, - lsvCommandLine.start
+ ; enough data below bp to hold buffer ?
+ jb @F ; no -->
+ cmp sp, bx ; sp below-or-equal would-be buffer ?
+ jbe .canbevalid ; yes, can be valid --> (and word access valid)
+@@:
+ cmp bp, - lsvCommandLine.signature
+ ; enough data below bp to hold our lsv ?
+ jae @F ; yes -->
+ test bp, 1 ; valid to access even-aligned words ?
+ jnz .error ; maybe not -->
+@@:
+ and word [bp + lsvCommandLine.signature], 0
+ ; invalidate signature
+.canbevalid:
+ cmp word [bp + lsvCommandLine.signature], "CL"
+ ; valid signature ?
+ je @F ; yes, keep bx pointing at buffer
+
+ lea bx, [bp + lsvCommandLine.signature]
+ ; no, ss:bx -> lsv with signature
+@@:
+ cmp sp, bx ; sp below-or-equal needed stack frame ?
+ jbe @F ; yes -->
+ and bl, ~1 ; make even-aligned stack (rounding down)
+ mov sp, bx ; change sp
+@@:
+
+
+d3 call d3_display_two_characters
+d3 test ax, "F0"
+
+ xor cx, cx
+ mov word [bp + lsvFirstCluster + 0], cx
+ mov word [bp + lsvFirstCluster + 2], cx
+
+%if _LSVEXTRA
+ mov word [bp + lsvExtra], lsvefNoDataStart << 8
+%else
+ call calculate_data_start
+%endif
+.multiboot_entry:
+ mov ax, cs
+ add ax, (payload.actual_end -$$+0 +15) >> 4
+
+ jmp ms7_entry.continue2
+
+
+.error:
+ call error
+ asciz "Invalid base pointer in FreeDOS entrypoint."
+
+
+%if _LSVEXTRA
+handle_lsv_extra_flags:
+ test byte [bp + lsvExtra.flags], lsvefPartitionNumber
+ jz @F
+ call parse_partition_number
+@@:
+ test byte [bp + lsvExtra.flags], lsvefNoDataStart
+ jz @F
+ call calculate_data_start
+@@:
+ retn
+
+
+parse_partition_number:
+ xor ax, ax
+ mov word [bp + bsBPB + bpbHiddenSectors], ax
+ mov word [bp + bsBPB + bpbHiddenSectors + 2], ax
+ cmp byte [bp + bsBPB + ebpbNew + bpbnBootUnit], -1
+ jne @F
+ mov byte [bp + bsBPB + ebpbNew + bpbnBootUnit], 80h
+ mov word [bp + bsBPB + bpbCHSSectors], ax
+ mov word [bp + bsBPB + bpbCHSHeads], ax
+ call query_geometry
+@@:
+
+ %if !_LBA_SKIP_CHECK
+ test byte [bp + ldHasLBA], 1
+ jnz @F
+ %endif
+
+ mov ax, word [bp + bsBPB + bpbCHSSectors]
+ mov dx, word [bp + bsBPB + bpbCHSHeads]
+
+ ; following is from lDebug 0c0930773929 boot.asm
+ overridedef DEBUG5, 0
+%define load_unit (bp + bsBPB + ebpbNew + bpbnBootUnit)
+%define load_sectorsize (bp + bsBPB + bpbBytesPerSector)
+%define load_sectorsizepara (bp + ldParaPerSector)
+
+ test ax, ax
+ jz .invalid_sectors
+ cmp ax, 63
+ ja .invalid_sectors
+ test dx, dx
+ jz .invalid_heads
+ cmp dx, 100h
+ ja .invalid_heads
+@@:
+
+ mov ax, word [bp + ldSectorSeg] ; ax => sector seg
+ dec ax ; ax => sector seg - 16
+ mov es, ax
+ xor ax, ax
+ mov bx, 16
+
+d5 call d5dumpregs
+d5 call d5message
+d5 asciz 13,10,"In query_geometry 0",13,10
+
+ mov di, bx
+ mov cx, (8192 + 2) >> 1
+ ; es:bx -> auxbuff, es:di = same
+ rep stosw ; fill buffer, di -> behind (auxbuff+8192+2)
+ mov ax, 0201h ; read sector, 1 sector
+ inc cx ; sector 1 (1-based!), cylinder 0 (0-based)
+ mov dh, 0 ; head 0 (0-based)
+ mov dl, [load_unit]
+ stc
+ call .int13_retry
+ jc .access_error
+
+ std ; _AMD_ERRATUM_109_WORKAROUND does not apply
+ mov word [es:bx - 2], 5E5Eh ; may overwrite last 2 bytes at line_out_end
+ scasw ; -> auxbuff+8192 (at last word to sca)
+d5 call d5dumpregs
+d5 call d5message
+d5 asciz 13,10,"In query_geometry 1",13,10
+ mov cx, (8192 + 2) >> 1
+ xor ax, ax
+ repe scasw
+ add di, 4 ; di -> first differing byte (from top)
+ cld
+ push di
+
+ mov di, bx
+ mov cx, (8192 + 2) >> 1
+ dec ax ; = FFFFh
+ rep stosw
+
+ mov ax, 0201h
+ inc cx
+ mov dh, 0
+ mov dl, [load_unit]
+ stc
+ call .int13_retry
+ jc .access_error
+
+ std ; _AMD_ERRATUM_109_WORKAROUND does not apply
+ scasw ; di -> auxbuff+8192 (last word to sca)
+d5 call d5dumpregs
+d5 call d5message
+d5 asciz 13,10,"In query_geometry 2",13,10
+ pop dx
+ mov ax, -1
+ mov cx, (8192 + 2) >> 1
+ repe scasw
+%if 0
+AAAB
+ ^
+ sca B, match
+ ^
+ sca B, mismatch
+ ^
+ stop
+%endif
+ add di, 4 ; di -> first differing byte (from top)
+ cld
+
+%if 0
+0000000000000
+AAAAAAAA00000
+ ^
+FFFFFFFFFFFFF
+AAAAAAAA00FFF
+ ^
+%endif
+ cmp dx, di ; choose the higher one
+ jae @F
+ mov dx, di
+@@:
+ sub dx, bx ; dx = sector size
+
+d5 call d5dumpregs
+d5 call d5message
+d5 asciz 13,10,"In query_geometry 3",13,10
+
+ cmp dx, 8192 + 2
+ jae .sector_too_large
+ mov ax, 32
+ cmp dx, ax
+ jb .sector_too_small
+@@:
+ cmp dx, ax
+ je .got_match
+ cmp ax, 8192
+ jae .sector_not_power
+ shl ax, 1
+ jmp @B
+
+.got_match:
+ mov word [load_sectorsize], ax
+ mov cl, 4
+ shr ax, cl
+ mov word [load_sectorsizepara], ax
+
+ resetdef
+
+
+ push cs
+ pop ds
+ push cs
+ pop es
+
+ mov cx, .per_partition
+ call scan_partitions
+
+ mov di, partition_offset
+ mov dx, word [di + 2]
+ mov ax, word [di]
+
+ cmp ax, -1
+ jne @F
+ cmp dx, -1
+ jne @F
+
+ push ss
+ pop es
+ mov di, bp
+ xor ax, ax
+ mov cx, 512 / 2 + (((ebpbNew - bpbNew + 15) & ~15) >> 1)
+ push word [bp + bsBPB + ebpbNew + bpbnBootUnit]
+ push word [bp + bsBPB + bpbCHSSectors]
+ push word [bp + bsBPB + bpbCHSHeads]
+ rep stosw
+ pop word [bp + bsBPB + bpbCHSHeads]
+ pop word [bp + bsBPB + bpbCHSSectors]
+ pop bx
+ mov byte [bp + bsBPB + ebpbNew + bpbnBootUnit], bl
+ mov word [bp + bsBPB + bpbHiddenSectors], dx
+ mov word [bp + bsBPB + bpbHiddenSectors + 2], dx
+
+ jmp .invalid_return
+
+
+@@:
+ push dx
+ push ax
+ mov bx, [bp + ldSectorSeg]
+ call read_ae_512_bytes
+ push es
+ pop ds
+ xor si, si
+ push ss
+ pop es
+ mov di, bp
+ mov cx, 512 / 2
+
+ pop ax
+ pop dx
+
+ push word [bp + bsBPB + ebpbNew + bpbnBootUnit]
+ push word [bp + bsBPB + bpbCHSSectors]
+ push word [bp + bsBPB + bpbCHSHeads]
+
+
+ rep movsw
+
+ push ax
+ xor ax, ax
+ mov cx, ((ebpbNew - bpbNew + 15) & ~15) >> 1
+ rep stosw ; initialise area behind sector (left so for FAT32)
+ pop ax
+
+
+ pop word [bp + bsBPB + bpbCHSHeads]
+ pop word [bp + bsBPB + bpbCHSSectors]
+ mov word [bp + bsBPB + bpbHiddenSectors], ax
+ mov word [bp + bsBPB + bpbHiddenSectors + 2], dx
+
+
+ push ss
+ pop ds
+ push ss
+ pop es
+
+ mov bx, [bp + bsBPB + bpbSectorsPerFAT]
+ test bx, bx
+ jz .not_fat32
+
+ lea si, [bp + 510] ; -> last source word
+ lea di, [si + (ebpbNew - bpbNew)] ; -> last dest word
+ mov cx, (512 - bsBPB - bpbNew + 1) >> 1
+ ; move sector up, except common BPB start part
+%if ((512 - bsBPB - bpbNew + 1) >> 1) <= 20
+ %fatal Need AMD erratum 109 workaround
+%endif
+ std ; AMD erratum 109 handling not needed
+ rep movsw
+ cld
+
+ mov word [bp + lsvFirstCluster + 2], cx
+ mov word [bp + lsvFATSector + 2], cx
+
+ mov word [bp + bsBPB + ebpbSectorsPerFATLarge], bx
+ mov word [bp + bsBPB + ebpbSectorsPerFATLarge + 2], cx
+ mov word [bp + bsBPB + ebpbFSFlags], cx
+ ; FSVersion, RootCluster, FSINFOSector, BackupSector, Reserved:
+ ; uninitialised here (initialised by loaded_all later)
+.not_fat32:
+
+ pop bx
+ mov byte [bp + bsBPB + ebpbNew + bpbnBootUnit], bl
+
+ mov ah, lsvefNoDataStart
+ mov al, byte [cs:partition_type]
+ cmp al, ptFAT12
+ je @F
+ cmp al, ptFAT16_16BIT_CHS
+ je @F
+ cmp al, ptFAT16_CHS
+ je @F
+ cmp al, ptFAT32_CHS
+ je @F
+ cmp al, ptFAT32
+ je @F
+ cmp al, ptFAT16
+ je @F
+
+.invalid_return:
+ xor ax, ax
+ mov word [bp + lsvDataStart], ax
+ mov word [bp + lsvDataStart + 2], ax
+@@:
+ mov byte [bp + lsvExtra.flags], ah
+
+%if _CHECKSUM
+ push cs
+ pop es
+ mov di, scanparttab_variables_start
+ mov cx, scanparttab_variables_length_w
+ xor ax, ax
+ rep stosw ; clear variables for eventual checksum
+ dec ax
+ mov di, partition_offset
+ stosw
+ stosw ; reset this variable too
+%endif
+
+ push ss
+ pop es
+ push ss
+ pop ds
+ retn
+
+
+.per_partition:
+ push cx
+ push si
+ push di
+ push bx
+
+ mov ax, [es:si + piStart]
+ mov dx, [es:si + piStart + 2]
+ add ax, [ss:bx + di - 8]
+ adc dx, [ss:bx + di - 8 + 2] ; = partition start
+
+ mov cx, -1
+ mov di, partition_offset
+ cmp word [di], cx ; first one encountered ?
+ jne @F
+ cmp word [di + 2], cx
+ jne @F ; no -->
+ mov cl, byte [es:si + piType]
+ mov byte [di - partition_offset + partition_type], cl
+ ; save type
+ mov word [di], ax
+ mov word [di + 2], dx ; yes, save offset
+@@:
+
+ mov cl, byte [load_current_partition]
+ ; which one ?
+ cmp cl, byte [bp + lsvExtra.partition]
+ jne @F ; not the sought one
+
+ mov cl, byte [es:si + piType]
+ mov byte [di - partition_offset + partition_type], cl
+ ; save type
+ mov word [di], ax
+ mov word [di + 2], dx ; save offset
+
+ pop bx ; bx = base
+ mov sp, bx ; reset sp
+ pop ax ; pop dummy bp
+ retn ; return to caller
+
+@@: ; not yet found, continue
+ pop bx
+ pop di
+ pop si
+ pop cx
+ retn
+
+
+.int13_retry:
+ pushf
+ push ax
+ int 13h ; first try
+ jnc @F ; NC, success on first attempt -->
+
+; reset drive
+ xor ax, ax
+ int 13h
+ jc @F ; CY, reset failed, error in ah -->
+
+; try read again
+ pop ax ; restore function number
+ popf
+ int 13h ; retry, CF error status, ah error number
+ retn
+
+@@: ; NC or CY, stack has function number
+ inc sp
+ inc sp
+ inc sp
+ inc sp ; discard two words on stack, preserve CF
+ retn
+
+
+.access_error:
+ jmp error_diskaccess
+
+.sector_too_large:
+.sector_too_small:
+.sector_not_power:
+ call error
+ asciz "Invalid sector size."
+
+.invalid_sectors:
+.invalid_heads:
+ call error
+ asciz "Invalid geometry."
+
+scan_logical.got_partition_cycle:
+ call error
+ asciz "Partition cycle detected."
+
+scan_logical.error_too_many_partitions:
+ call error
+ asciz "Too many partitions detected."
+
+read_partition_table.signature_fail:
+ call error
+ asciz "Invalid partition table detected."
+
+
+ ; INP: dx:ax = first sector
+ ; bx:0 -> buffer
+ ; OUT: dx:ax = sector number after last read
+ ; es = input bx
+ ; bx:0 -> buffer after last written
+ ; CHG: -
+read_ae_512_bytes:
+ push ds
+ push cx
+ push bx
+ push ss
+ pop ds
+ mov cx, 512
+.loop:
+ call read_sector
+ sub cx, word [bp + bsBPB + bpbBytesPerSector]
+ ja .loop
+ pop es
+ pop cx
+ pop ds
+ retn
+
+
+%assign _PARTITION_TABLE_IN_CS 1
+%assign _BOOTCMD_FAIL_ERROR 0
+%define _SCANPTAB_PREFIX
+%define _SCANPTAB_DEBUG4_PREFIX
+ overridedef DEBUG4, 0
+%include "scanptab.asm"
+ resetdef
+
+
+ align 16
+scanparttab_variables_start:
+partition_table:
+ times 16 * 4 db 0
+.end:
+
+partition_offset:
+ dd -1
+
+load_partition_cycle:
+ dw 0
+load_current_partition:
+ db 0
+partition_type:
+ db 0
+
+ align 2
+scanparttab_variables_length_w: equ ($ - scanparttab_variables_start) / 2
+%endif
+
+
+ ; INP: ss:bp -> BPB
+ ; ss:bp - LOADSTACKVARS -> lsv
+ ; OUT: lsvDataStart set
+ ; CHG: ax, bx, cx, dx, si, di
+calculate_data_start:
+ xor cx, cx ; ! ch = 0
+
+ ; Although this currently is unused, we calculate the
+ ; first data sector (including root directory size)
+ ; here to complete the LOADSTACKVARS.
+
+; 32-byte FAT directory entries per sector
+ mov ax, [bp + bsBPB + bpbBytesPerSector]
+ mov cl, 5 ; ! ch = 0
+ shr ax, cl
+
+; number of sectors used for root directory (store in CX)
+ ; After the prior shr instruction, ax is always < 8000h,
+ ; so this cwd instruction always zeros dx.
+ cwd
+ mov si, [bp + bsBPB + bpbNumRootDirEnts] ; (0 iff FAT32)
+ mov bx, ax
+ dec ax ; rounding up
+ js .error_badchain ; if >= 8000h (ie, 0FFFFh while bx = 0)
+ add ax, si ; from BPB
+ adc dx, dx ; account for overflow (dx was zero)
+ div bx ; get number of root sectors
+ xchg ax, cx ; cx = number of root secs, ! ah = 0
+
+ push cx ; number of root secs
+; first sector of root directory
+ mov al, [bp + bsBPB + bpbNumFATs]
+ ; ! ah = 0, hence ax = number of FATs
+ mov cx, word [bp + bsBPB + bpbSectorsPerFAT]
+ xor di, di ; di:cx = sectors per FAT
+ ; iff FAT12, FAT16
+ test cx, cx ; is FAT32 ?
+ jnz @F ; no -->
+ mov cx, word [bp + bsBPB + ebpbSectorsPerFATLarge]
+ mov di, word [bp + bsBPB + ebpbSectorsPerFATLarge + 2] ; for FAT32
+@@:
+ push ax
+ mul cx
+ ; ax = low word SpF*nF
+ ; dx = high word
+ xchg bx, ax
+ xchg cx, dx
+ ; cx:bx = first mul
+ pop ax
+ mul di
+ ; ax = high word adjust
+ ; dx = third word
+ test dx, dx
+ jnz .error_badchain
+ xchg dx, ax
+ ; dx = high word adjust
+ add dx, cx
+ ; dx:bx = result
+ xchg ax, bx
+ ; dx:ax = result
+ jc .error_badchain
+
+ add ax, [bp + bsBPB + bpbReservedSectors]
+ adc dx, byte 0
+ jc .error_badchain
+
+ pop cx ; number of root sectors
+ xor di, di
+
+; first sector of disk data area:
+ add cx, ax
+ adc di, dx
+ mov [bp + lsvDataStart], cx
+ mov [bp + lsvDataStart + 2], di
+
+ retn
+
+.error_badchain:
+ jmp error_badchain
+
+
+end_of_handle_lsv_extra_flags:
+
+
+%if _CHECKSUM
+CHECKSUM_SIZE_P equ (payload.actual_end - $$ + 0) / 16
+
+ overridedef STANDALONE, 0
+ %include "inicheck.asm"
+ resetdef
+
+ align 16
+checksumheader:
+ istruc CHECKSUMHEADER
+at cshSignature, dw "CS"
+at cshLengthBytesStructure, dw CHECKSUMHEADER_size
+at cshOffsetStructure, dw paras(checksumheader - $$ + 0)
+at cshChecksumStructure, dw \
+ 10000h-(("CS" + CHECKSUMHEADER_size \
+ + paras(checksumheader - $$ + 0) \
+ + 8106h + CHECKSUM_SIZE_P \
+ + 0 + 0) & 0FFFFh)
+at cshTypeChecksum
+..@checksumtype: dw 8106h
+at cshAmountParagraphsData, dw CHECKSUM_SIZE_P
+at cshChecksumData
+..@checksumfield: dw 0
+at cshReserved, dw 0
+ iend
+%endif
+
+%if _MULTIBOOT1 || _MULTIBOOT2
+ %include "multboot.asm"
+%endif
+
+
+%if _DEBUG3
+ ; INP: word [cs:ip + 1] = two characters to display
+ ; (second one may be NUL to skip)
+ ; OUT: -
+ ; CHG: -
+d3_display_two_characters:
+ lframe near
+ lenter
+ push ax
+ push bx
+ mov bx, word [bp + ?frame_ip]
+ mov ax, [cs:bx + 1]
+
+ push ax
+ call d3_disp_al
+ pop ax
+
+ xchg al, ah
+ test al, al
+ jz @F
+
+ call d3_disp_al
+@@:
+
+ pop bx
+ pop ax
+ lleave
+ lret
+
+ ; INP: al = to display
+ ; CHG: ax, bx
+d3_disp_al:
+ push bp
+ mov ah, 0Eh
+ mov bx, 7
+ int 10h
+ pop bp
+ retn
+%endif
+
+
+msdos1_com_entry:
+ mov dx, .msg + 100h
+ mov ah, 09h
+ int 21h
+ int 20h
+
+.msg:
+ ascic "86-DOS version 1 not supported, aborting.",13,10
+
+
+ align 16, db 38
+payload:
+ incbin _PAYLOAD_FILE
+ align 16, db 38
+.actual_end:
+%if _IMAGE_EXE
+ align 512, db 38 ; until end of page
+ times 512 db 38 ; a full additional page,
+ ; this is for the bogus exeExtraBytes
+ ; Note that the pages start counting within the EXE header!
+ ; Thus alignment to the file-level page boundary is correct.
+%endif
+.end:
+
+
+%if _SECOND_PAYLOAD_EXE
+ align 16, db 38
+second_payload:
+ incbin _SECOND_PAYLOAD_FILE
+ align 16, db 38
+.actual_end:
+ align 512, db 38
+ times 512 db 38
+.end:
+%endif
+
diff --git a/test/ldosboot/kernshim.asm b/test/ldosboot/kernshim.asm
new file mode 100644
index 0000000..cd995b0
--- /dev/null
+++ b/test/ldosboot/kernshim.asm
@@ -0,0 +1,58 @@
+
+%if 0
+
+FreeDOS kernel executable MZ header shim
+ by C. Masloch, 2022
+
+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
+
+%include "lmacros2.mac"
+
+ defaulting
+
+ strdef FILE, ""
+%ifidn _FILE,""
+ %fatal Has to specify a file!
+%endif
+
+
+ org 0
+header:
+ db "MZ" ; exeSignature
+ dw (payload.end - $$) % 512 ; exeExtraBytes
+ dw (payload.end - $$ + 511) / 512 ; exePages
+ dw 0 ; exeRelocItems
+ dw (payload -$$+0) >> 4 ; exeHeaderSize
+ dw 0 ; exeMinAlloc
+ dw -1 ; exeMaxAlloc
+ dw 0 ; exeInitSS
+ dw -2 ; exeInitSP
+ dw 0 ; exeChecksum
+ dw 0, 0 ; exeInitCSIP
+ dw 0 ; exeRelocTable
+ endarea header
+
+
+ align 16, db 38
+payload:
+ jmp strict short entry
+ db "CONFIG"
+ dw 1
+ db -1
+
+ times 32 - ($ - payload) db 0
+entry: equ $
+ jmp entry_common
+
+ times 0xC0 - ($ - payload) db 0
+entry_common: equ $
+
+ incbin _FILE
+.actual_end:
+.end:
diff --git a/test/ldosboot/multboot.asm b/test/ldosboot/multboot.asm
new file mode 100644
index 0000000..e0e4134
--- /dev/null
+++ b/test/ldosboot/multboot.asm
@@ -0,0 +1,566 @@
+
+%if 0
+
+Multiboot header and loader
+ 2008--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
+
+%macro mbootchecksummed 1-2.nolist 1BADB002h
+ dd %2 ; signature
+ dd %1 ; flags
+ dd -((%2+%1) & 0FFFF_FFFFh)
+ ; checksum: dword sum of these three = 0
+%endmacro
+
+%macro mboot2checksummed 0-1.nolist 0E852_50D6h
+ dd %1 ; signature
+ dd 0 ; platform (i386)
+ dd mboot2_header_end - mboot2_header
+ ; size of header, including header tags
+ dd -((%1 + (mboot2_header_end - mboot2_header))) & 0FFFF_FFFFh
+ ; checksum
+%endmacro
+
+
+MULTIBOOT_CMDLINE_LENGTH equ lsvclBufferLength
+
+MULTIBOOT_BASE equ (1024+64)*1024 ; one paragraph above the HMA
+MULTIBOOT_END equ MULTIBOOT_BASE + (payload.actual_end - $$ + 0)
+MULTIBOOT_BSS_END equ MULTIBOOT_END \
+ + fromdwords(dwords(MULTIBOOT_CMDLINE_LENGTH))
+MULTIBOOT_TARGET_SEGMENT equ 200h
+MULTIBOOT_TARGET equ MULTIBOOT_TARGET_SEGMENT << 4
+MULTIBOOT_BPB equ MULTIBOOT_TARGET - 512 - 16
+MULTIBOOT_CMDLINE_START equ MULTIBOOT_BPB + lsvCommandLine.start
+MULTIBOOT_STACK_TOP equ MULTIBOOT_CMDLINE_START
+
+%if _MULTIBOOT1
+ ; Multiboot header
+ ; must be dword aligned and in first 8 KiB!
+ align 4, db 0
+mbootheader:
+ mbootchecksummed 00010000h
+ ; flags: provides info where to load image
+ dd MULTIBOOT_BASE + mbootheader ; load address of header
+ dd MULTIBOOT_BASE ; load address
+ dd MULTIBOOT_END ; load area end (0 = load whole file)
+ dd MULTIBOOT_BSS_END ; uninitialised area end (0 = none)
+ dd MULTIBOOT_BASE + mbootentry ; entry point
+%endif
+
+%if _MULTIBOOT2
+ ; Multiboot2 header
+ ; qword (64-bit) aligned and in first 32 KiB
+ align 8, db 0
+mboot2_header:
+ mboot2checksummed
+mboot2_tag_information_request:
+.:
+.type: dw 1
+.flags: dw 0
+.size: dd .end - .
+ dd 1 ; command line
+ dd 5 ; ROM-BIOS boot device
+.end:
+ align 8, db 0
+mboot2_tag_addresses:
+.:
+.type: dw 2
+.flags: dw 0
+.size: dd .end - .
+ dd MULTIBOOT_BASE + mboot2_header
+ ; load address of header
+ dd MULTIBOOT_BASE ; load address
+ dd MULTIBOOT_END ; load area end (0 = load whole file)
+ dd MULTIBOOT_BSS_END ; uninitialised area end (0 = none)
+.end:
+ align 8, db 0
+mboot2_tag_entrypoint:
+.:
+.type: dw 3
+.flags: dw 0
+.size: dd .end - .
+ dd MULTIBOOT_BASE + mboot2entry ; entry point
+.end:
+ align 8, db 0
+mboot2_tag_end:
+.:
+.type: dw 0
+.flags: dw 0
+.size: dd .end - .
+.end:
+mboot2_header_end:
+%endif
+
+
+%unmacro mbootchecksummed 1-2.nolist 1BADB002h
+%unmacro mboot2checksummed 0-1.nolist 0E85250D6h
+
+%if ($-$$) > 8192
+ %fatal Multiboot header must be in the first 8 KiB
+%endif
+
+ struc MBI
+mbiFlags: resd 1
+ resb 8
+mbiBootDevice: resd 1
+mbiCmdLine: resd 1
+mbiModuleCount: resd 1
+mbiModuleTable: resd 1
+ ; (More data follows but none which seems useful.)
+ endstruc
+
+[cpu 386]
+[bits 32]
+
+ numdef MULTIBOOT_CHECKS, 1
+ numdef MULTIBOOT_DEBUG, 0
+ numdef MULTIBOOT_HALT, 0
+ numdef MULTIBOOT_PROGRESS, 0
+
+
+ %if _MULTIBOOT_CHECKS || _MULTIBOOT_HALT
+mbootentry.halt:
+mboot2entry.halt:
+ ; Unfortunately, as the environment is unknown, we can only
+ ; literally halt here, as we don't know how to do I/O.
+ cli
+@@:
+ hlt
+ jmp short @B
+ %endif
+
+%if _MULTIBOOT1
+ ; INP: eax = 2BADB002h
+ ; ebx-> multiboot info structure
+ ; STT: loaded and executed according to Multiboot header, ie
+ ; loaded at MULTIBOOT_BASE
+ ; eip = MULTIBOOT_BASE+mbootentry
+ ; in PM32 with paging disabled
+ ; CPL 0
+ ; cs,ds,es,fs,gs,ss = flat 32-bit code/data selectors
+ ; DI
+ ; ! stack not set up
+ ; ! GDT might not be set up
+ ; ! IDT might not be set up
+ ; A20 on
+ ;
+ ; Note: Although A20 is on, HMA access without an XMM
+ ; to manage it might be undesirable. Multiboot
+ ; also doesn't tell us whether and if so then
+ ; how we can switch A20.
+mbootentry:
+ %if _MULTIBOOT_HALT
+ jmp .halt
+ %endif
+ %if _MULTIBOOT_CHECKS
+[bits 16]
+ test ax, 0 ; (test eax if in 32-bit segment)
+ jmp short .halt ; (skipped if in 32-bit segment)
+[bits 32]
+ cmp eax, 2BADB002h ; signature ?
+ jne short .halt ; no -->
+ smsw eax
+ rol eax, 1 ; 2 = protection, 1 = paging
+ and al, 3 ; mask off others
+ cmp al, 2 ; PE but not PG ?
+ jne short .halt ; no -->
+ mov eax, cs
+ and al, 3 ; CPL 0 ?
+ jnz short .halt ; no -->
+ %endif
+
+ %if _MULTIBOOT_PROGRESS
+ mov ebp, 0B8000h + 2 * 80 * 20
+ mov word [ebp], 5000h | '1'
+ inc ebp
+ inc ebp
+ %endif
+
+ ; prepare this and that
+ or edx, -1
+ ; locate boot drive in Multiboot info structure
+ test byte [ ebx + mbiFlags ], 2 ; boot device info valid ?
+ jz @F
+ mov eax, [ ebx + mbiBootDevice ]; get the info
+ rol eax, 16
+ xchg al, ah
+ mov edx, eax ; dl = boot load unit (or 0FFh),
+ ; dh = partition (or 0FFh)
+ ; (edxh = subpartition numbers)
+@@:
+
+ %if _MULTIBOOT_PROGRESS
+ mov word [ebp], 5000h | '2'
+ inc ebp
+ inc ebp
+ %endif
+
+ xor eax, eax
+ mov edi, MULTIBOOT_END
+ mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4
+ test byte [ ebx + mbiFlags ], 4 ; command line valid ?
+ jz @F
+ mov esi, [ ebx + mbiCmdLine ]
+ rep movsd
+ mov byte [ edi - 1 ], 0 ; insure it is terminated
+ ; (this may truncate the line)
+@@:
+ rep stosd
+
+ %if _MULTIBOOT_PROGRESS
+ mov word [ebp], 5000h | '3'
+ inc ebp
+ inc ebp
+ %endif
+
+ %if _MULTIBOOT2
+ jmp multiboot_1_2_common
+ %endif
+%endif
+
+%if _MULTIBOOT2
+ ; INP: eax = 36D7_6289h
+ ; ebx-> multiboot2 info structure
+ ; STT: loaded and executed according to Multiboot2 header, ie
+ ; loaded at MULTIBOOT_BASE
+ ; eip = MULTIBOOT_BASE+mboot2entry
+ ; in PM32 with paging disabled
+ ; CPL 0
+ ; cs,ds,es,fs,gs,ss = flat 32-bit code/data selectors
+ ; DI
+ ; ! stack not set up
+ ; ! GDT might not be set up
+ ; ! IDT might not be set up
+ ; A20 on
+ ;
+ ; Note: Although A20 is on, HMA access without an XMM
+ ; to manage it might be undesirable. Multiboot
+ ; also doesn't tell us whether and if so then
+ ; how we can switch A20.
+mboot2entry:
+ %if _MULTIBOOT_HALT
+ jmp .halt
+ %endif
+ %if _MULTIBOOT_CHECKS
+[bits 16]
+ test ax, 0 ; (test eax if in 32-bit segment)
+ jmp short .halt ; (skipped if in 32-bit segment)
+[bits 32]
+ cmp eax, 36D7_6289h ; signature ?
+ jne .halt ; no -->
+ smsw eax
+ rol eax, 1 ; 2 = protection, 1 = paging
+ and al, 3 ; mask off others
+ cmp al, 2 ; PE but not PG ?
+ jne .halt ; no -->
+ mov eax, cs
+ and al, 3 ; CPL 0 ?
+ jnz .halt ; no -->
+ %endif
+
+ %if _MULTIBOOT_PROGRESS
+ mov ebp, 0B8000h + 2 * 80 * 20
+ mov word [ebp], 5000h | 'A'
+ inc ebp
+ inc ebp
+ %endif
+
+ or edx, -1 ; initialise boot partition info
+
+ xor eax, eax
+ mov edi, MULTIBOOT_END
+ mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4
+ rep stosd ; initialise command line buffer
+
+ add ebx, 8 ; skip fixed header (size, reserved)
+.tags_loop:
+
+ %if _MULTIBOOT_PROGRESS
+ mov word [ebp], 5000h | 'B'
+ inc ebp
+ inc ebp
+ %endif
+
+ mov eax, dword [ebx]
+ test eax, eax ; end of tags ?
+ jz .tags_end ; yes -->
+
+ cmp eax, 1
+ je .cmdline
+
+ cmp eax, 5
+ je .partition
+
+.tags_next:
+ %if _MULTIBOOT_PROGRESS
+ mov word [ebp], 5000h | 'C'
+ inc ebp
+ inc ebp
+ %endif
+
+ add ebx, dword [ebx + 4]
+ ; -> after end of tag
+ ; (at padding or next tag)
+ add ebx, 7
+ and ebx, ~7 ; skip any padding
+ jmp .tags_loop
+
+.cmdline:
+
+ %if _MULTIBOOT_PROGRESS
+ mov word [ebp], 5000h | 'D'
+ inc ebp
+ inc ebp
+ %endif
+
+ lea esi, [ebx + 8]
+ mov edi, MULTIBOOT_END
+ mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4
+ rep movsd ; copy command line
+ mov byte [ edi - 1 ], 0
+ ; insure it is terminated
+ ; (this may truncate the line)
+ jmp .tags_next
+
+.partition:
+
+ %if _MULTIBOOT_PROGRESS
+ mov word [ebp], 5000h | 'E'
+ inc ebp
+ inc ebp
+ %endif
+
+ mov eax, dword [ebx + 8]
+ cmp eax, 100h
+ jae @F
+ mov dl, al ; get ROM-BIOS unit
+@@:
+
+ mov eax, dword [ebx + 12]
+ cmp eax, 100h
+ jae @F
+ mov dh, al ; get partition number (0 = first primary)
+@@:
+ jmp .tags_next
+
+.tags_end:
+ xor eax, eax
+
+ %if _MULTIBOOT_PROGRESS
+ mov word [ebp], 5000h | 'F'
+ inc ebp
+ inc ebp
+ %endif
+
+%endif
+
+multiboot_1_2_common:
+ mov esp, MULTIBOOT_STACK_TOP ; set a valid stack, at 8 KiB
+ ; (also required by 86M entry later)
+
+ %if _MULTIBOOT_PROGRESS
+ mov word [ebp], 5000h | 's'
+ inc ebp
+ inc ebp
+ %endif
+
+ ; A20 is on. set up some things
+ xor ecx, ecx
+ mov edi, 1024*1024
+ mov cl, 10h ; (ecx = 10h)
+ rep stosd ; clear this area so A20 tests succeed
+; The above writes to 10_0000h..10_003Fh, leaving edi = 10_0040h
+ mov al, 0C0h ; (eax = C0h)
+ mov byte [ edi-40h+eax ], 0EAh
+ mov [ edi-40h+eax+1 ], eax ; write jump for CP/M entry hack here
+; The above writes to 10_00C0h..10_00C4h
+
+ %if _MULTIBOOT_PROGRESS
+ mov word [ebp], 5000h | 't'
+ inc ebp
+ inc ebp
+ %endif
+
+ ; relocate image to a good location, 2000h
+ mov esi, MULTIBOOT_BASE ; -> iniload image
+ mov edi, MULTIBOOT_TARGET
+ mov ecx, (payload.actual_end - $$ + 0 + 3) / 4
+ rep movsd
+
+ %if _MULTIBOOT_PROGRESS
+ mov word [ebp], 5000h | 'u'
+ inc ebp
+ inc ebp
+ %endif
+
+MULTIBOOT_ESI_AFTER equ MULTIBOOT_BASE + (payload.actual_end - $$ + 0 + 3) / 4 * 4
+%if MULTIBOOT_ESI_AFTER != MULTIBOOT_END
+ mov esi, MULTIBOOT_END
+%endif
+ mov edi, MULTIBOOT_CMDLINE_START
+ mov ecx, MULTIBOOT_CMDLINE_LENGTH / 4
+ rep movsd
+
+ %if _MULTIBOOT_PROGRESS
+ mov word [ebp], 5000h | 'v'
+ inc ebp
+ inc ebp
+ %endif
+
+ ; now back to real mode
+ o32 lgdt [ MULTIBOOT_BASE+mbootgdtdesc ]
+ ; set our GDT
+ mov ax, 10h
+ mov ds, ax
+ mov es, ax
+ mov ss, ax
+ mov fs, ax
+ mov gs, ax ; set 64 KiB segment limits
+ o32 push byte 0
+ o32 popf ; reset all flags
+ jmp 08h:.pm16 ; use 16-bit selector
+
+[bits 16]
+ ; now really switch to real mode
+ ; (already executing relocated code)
+.pm16:
+ mov eax, cr0
+ dec ax
+ mov cr0, eax ; clear PE bit
+
+ jmp dword MULTIBOOT_TARGET_SEGMENT:.rm ; reload cs
+
+ ; Set up registers and the environment for
+ ; the kernel entry point. It doesn't need initialised
+ ; segment registers but we'll initialise them in case
+ ; they still contain selector content.
+.rm:
+ ; some sources indicate we should set the IDT
+ o32 lidt [cs:mbootidtdesc] ; set IDT to RM IVT
+ xor ax, ax
+ mov ds, ax
+ mov es, ax
+ mov fs, ax
+ mov gs, ax
+ mov ss, ax
+ ; (cs should be = 200h now, need no far jump)
+ ; cs = 200h, ip = mboot_86m_entry
+ ; ds = es = fs = gs = ss = 0
+ ; sp = 2000h - 512 - 16 + lsvCommandLine.start
+ ; dl = boot unit (or -1)
+ ; dh = boot partition (0..3 = primary, 4+ = logical, or -1)
+
+ mov bp, MULTIBOOT_BPB ; -> pseudo BPB
+ mov di, MULTIBOOT_BPB + lsvCommandLine.signature
+ mov ax, lsvclSignature
+ stosw ; store signature
+ mov cx, (- (lsvCommandLine.signature + 2) + 512 + 3) / 4
+ xor eax, eax
+ rep stosd ; clear
+ ; lsvFirstCluster = 0 (same as for FreeDOS entrypoint)
+ ; bsBPB + bpbHiddenSectors = 0 (invalid or unpartitioned)
+ ; bsBPB + bpbCHSSectors = 0
+ ; bsBPB + bpbCHSHeads = 0
+
+ inc ax
+ mov dword [bp + lsvDataStart], eax
+ ; lsvDataStart = 1 (placeholder)
+__CPU__
+ mov word [bp + bsBPB + bpbNumRootDirEnts], ax
+ mov word [bp + bsBPB + bpbSectorsPerFAT], ax
+ ; do not detect as FAT32
+ mov byte [bp + bsBPB + bpbSectorsPerCluster], al
+ mov word [bp + bsBPB + bpbBytesPerSector], 512
+ mov word [bp + bsBPB + bpbTotalSectors], 8192
+ ; 1 C/s * 8 Ki sectors = 8 Ki clusters, detect as FAT16
+ ; that is, do not load FAT at all (would load a FAT12)
+
+ mov byte [bp + bsBPB + bpbNew + bpbnBootUnit], dl
+ ; set boot load unit
+
+%if _LSVEXTRA
+ %if _MULTIBOOT_DEBUG
+ mov ax, dx
+ call mb_disp_ax_hex
+ %endif
+ xchg dl, dh ; dl = Multiboot spec partition number
+ ; (0..3 primary, 4+ logical, -1 invalid)
+ inc dx ; 0 invalid, 1..4 primary, 5+ logical
+ mov dh, lsvefPartitionNumber
+ mov word [bp + lsvExtra], dx
+%endif
+ jmp freedos_entry.multiboot_entry
+
+
+%if _MULTIBOOT_DEBUG
+mb_disp_ax_hex: ; ax
+ xchg al,ah
+ call mb_disp_al_hex ; display former ah
+ xchg al,ah ; and fall trough for al
+mb_disp_al_hex: ; al
+ push cx
+ mov cl,4
+ ror al,cl
+ call mb_disp_al_lownibble_hex ; display former high-nibble
+ rol al,cl
+ pop cx
+ ; and fall trough for low-nibble
+mb_disp_al_lownibble_hex:
+ push ax ; save ax for call return
+ and al,00001111b ; high nibble must be zero
+ add al,'0' ; if number is 0-9, now it's the correct character
+ cmp al,'9'
+ jna .decimalnum ; if we get decimal number with this, ok -->
+ add al,7 ; otherwise, add 7 and we are inside our alphabet
+ .decimalnum:
+ call mb_disp_al
+ pop ax
+ retn
+
+mb_disp_al:
+ push ax
+ push bx
+ push bp
+ mov ah, 0Eh
+ mov bx, 7
+ int 10h
+ pop bp
+ pop bx
+ pop ax
+ retn
+%endif
+
+
+ align 16, db 0
+mbootgdt:
+ dw 0,0
+ db 0,0,0,0
+
+ ; selector 8: 16-bit real mode CS
+ ; base = 00002000h, limit 0FFFFh (1 B Granularity), present
+ ; type = 16-bit code execute/read only/conforming, DPL = 0
+ dw 0FFFFh,MULTIBOOT_TARGET
+ db 0,9Eh,0,0
+
+ ; selector 10h: 16-bit real mode DS
+ ; base = 00000000h, limit 0FFFFh (1 B Granularity), present
+ ; type = 16-bit data read/write, DPL = 0
+ dw 0FFFFh,0
+ db 0,92h,0,0
+ endarea mbootgdt
+
+mbootgdtdesc:
+ dw mbootgdt_size-1 ; limit
+ dd MULTIBOOT_BASE+mbootgdt ; address
+
+mbootidtdesc:
+ dw 400h-1 ; limit
+ dd 0 ; address (86M IVT)
diff --git a/test/ldosboot/patch.sld b/test/ldosboot/patch.sld
new file mode 100644
index 0000000..14b118f
--- /dev/null
+++ b/test/ldosboot/patch.sld
@@ -0,0 +1,89 @@
+@goto :help
+
+Patch iniload script
+ by C. Masloch, 2021
+
+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.
+
+
+@:help
+; Available patches:
+;
+; :patch_iniload_no_query_geometry
+; :patch_iniload_no_lba
+;
+; Inputs: vef = nonzero to debug
+; cs:0 -> iniload
+; Output: vee = nonzero if error
+; iniload patched
+; Change: ve0 to vef, src, sro, aao, stack
+;
+; Requires lDebug release 3 or later
+@goto :eof
+
+:patch_iniload_no_query_geometry
+@if not (vef) then r ysf |= C000
+r ve0 word (sp - 100)
+a ss:ve0
+ mov ah, 08
+ xor cx, cx
+ stc
+ int 13
+ jc (ve0)
+ .
+r ve1 := aao - 1
+r vec := ve1 - ve0
+s cs:0 l #8192 range ss:ve0 l vec
+if not (src) then goto :error
+r byte [srs:sro + vec - 1] := EB
+goto :success
+
+:patch_iniload_no_lba
+@if not (vef) then r ysf |= C000
+r ve0 word (sp - 100)
+a ss:ve0
+ mov ah, 41
+ mov dl, byte [bp + 40]
+ mov bx, 55AA
+ stc
+ int 13
+ mov al, 0
+ jc (ve0)
+ .
+r ve1 := aao - 1
+r vec := ve1 - ve0
+s cs:0 l #8192 range ss:ve0 l vec
+if (src) then goto :patch_iniload_no_lba.success
+a ss:ve0
+ mov ah, 41
+ pop dx
+ mov bx, 55AA
+ stc
+ int 13
+ mov al, 0
+ jc (ve0)
+ .
+r ve1 := aao - 1
+r vec := ve1 - ve0
+s cs:0 l #8192 range ss:ve0 l vec
+if not (src) then goto :error
+
+:patch_iniload_no_lba.success
+r byte [srs:sro + vec - 1] := EB
+goto :success
+
+:success
+r vee := 0
+r ysf &= ~C000
+; Patched successfully
+@goto :eof
+
+:error
+r vee := 1
+r ysf &= ~C000
+; Patch failed
+@goto :eof
diff --git a/test/ldosboot/test/cfg.sh b/test/ldosboot/test/cfg.sh
new file mode 100644
index 0000000..9c5cab0
--- /dev/null
+++ b/test/ldosboot/test/cfg.sh
@@ -0,0 +1,38 @@
+#! /bin/bash
+
+# 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.
+
+# If you want to override configuration without the
+# hassle of having to exclude differences in this file
+# (cfg.sh) from the SCM, you may provide ovr.sh.
+# The meaning of this line allows you to copy cfg.sh
+# to serve as a template for ovr.sh without changes.
+[ -f ovr.sh ] && [[ "${BASH_SOURCE[0]##*/}" != ovr.sh ]] && . ovr.sh
+
+# As the below only are set if no value is provided
+# yet, any value can be overridden from the shell.
+[ -z "$LMACROS_DIR" ] && LMACROS_DIR=../../lmacros/
+[ -z "$LDOSBOOT_DIR" ] && LDOSBOOT_DIR=../
+[ -z "$SCANPTAB_DIR" ] && SCANPTAB_DIR=../../scanptab/
+[ -z "$INICHECK_DIR" ] && INICHECK_DIR=../../crc16-t/
+[ -z "$BOOTIMG_DIR" ] && BOOTIMG_DIR=../../bootimg/
+[ -z "$LDEBUG_DIR" ] && LDEBUG_DIR=../../ldebug/bin/
+[ -z "$LDOSMBR_DIR" ] && LDOSMBR_DIR=../../ldosmbr/
+[ -z "$INSTSECT_DIR" ] && INSTSECT_DIR=../../instsect/
+
+[ -z "$DOSEMU" ] && DOSEMU=dosemu
+[ -z "$QEMU" ] && QEMU=qemu-system-i386
+[ -z "$DEFAULT_MACHINE" ] && DEFAULT_MACHINE=dosemu
+[ -z "$SENDKEYS" ] && SENDKEYS=sendkeys
+[ -z "$BOOT_KERNEL" ] && BOOT_KERNEL=~/.dosemu/drive_c/kernel.sys
+[ -z "$BOOT_COMMAND" ] && BOOT_COMMAND=~/.dosemu/drive_c/command.com
+[ -z "$BOOT_PROTOCOL" ] && BOOT_PROTOCOL=FREEDOS
+[ -z "$BOOT_OPTIONS" ] && BOOT_OPTIONS=" "
+[ -z "$NASM" ] && NASM=nasm
+[ -z "$CHECKSUM" ] && CHECKSUM="${INICHECK_DIR%/}"/iniload/checksum
+
+[ -z "$use_build_inicheck" ] && use_build_inicheck=0
diff --git a/test/ldosboot/test/quit.asm b/test/ldosboot/test/quit.asm
new file mode 100644
index 0000000..b12b156
--- /dev/null
+++ b/test/ldosboot/test/quit.asm
@@ -0,0 +1,60 @@
+
+%if 0
+
+Shut down machine
+ by C. Masloch, 2020
+
+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
+
+
+ cpu 8086
+ org 256
+quit:
+ int3
+
+ mov ax, 0F000h
+ mov es, ax
+ mov di, 0FFF5h
+ mov si, msg.dosemudate
+ mov cx, 4
+ repe cmpsw ; running in DosEmu?
+ jne .quit_not_dosemu
+
+ xor bx, bx
+ mov ax, -1
+ int 0E6h ; dosemu quit
+
+.quit_not_dosemu:
+
+; from https://stackoverflow.com/a/5240330/738287
+ mov ax, 5301h
+ xor bx, bx
+ int 15h ; connect to APM API
+
+ mov ax, 530Eh
+ xor bx, bx
+ mov cx, 0102h
+ int 15h ; set APM version to 1.02
+
+ mov ax, 5307h
+ mov bx, 1
+ mov cx, 3
+ int 15h ; shut down system
+
+ mov dx, msg.failed
+ mov ah, 09h
+ int 21h
+ mov ax, 4C00h
+ int 21h
+
+
+ align 4
+msg:
+.dosemudate: db "02/25/93"
+.failed: db "Quit failed.",13,10,36
diff --git a/test/ldosboot/test/run.sh b/test/ldosboot/test/run.sh
new file mode 100755
index 0000000..d958e63
--- /dev/null
+++ b/test/ldosboot/test/run.sh
@@ -0,0 +1,20 @@
+#! /bin/bash
+
+# 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.
+
+testrunname="fat12 default: " \
+ ./test.sh diskette
+testrunname="fat12 direct: " \
+ ./test.sh diskette direct
+testrunname="fat12 hdimage: " \
+ ./test.sh hdimage
+testrunname="fat12 16spc 20MiB: " \
+ ./test.sh hdimage aligndata bpe 12 spc 16 mib 20 nr 512
+testrunname="fat16 4spc 32MiB: " \
+ ./test.sh hdimage aligndata bpe 16 spc 4 mib 32 nr 512
+testrunname="fat32 1spc 34MiB: " \
+ ./test.sh hdimage aligndata bpe 32 spc 1 mib 34
diff --git a/test/ldosboot/test/test.sh b/test/ldosboot/test/test.sh
new file mode 100755
index 0000000..5775ae8
--- /dev/null
+++ b/test/ldosboot/test/test.sh
@@ -0,0 +1,491 @@
+#! /bin/bash
+
+# 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.
+
+if [[ "$1" != selfcall ]]
+then
+ setsid -w "$0" selfcall "$@"
+ exit $?
+fi
+shift
+
+. cfg.sh
+
+if [ -n "$LMACROS_DIR" ]; then {
+ options_i_lmacros=-I"${LMACROS_DIR%/}"/
+} fi
+
+if [ -n "$LDOSBOOT_DIR" ]; then {
+ options_i_ldosboot=-I"${LDOSBOOT_DIR%/}"/
+} fi
+
+if [ -n "$SCANPTAB_DIR" ]; then {
+ options_i_scanptab=-I"${SCANPTAB_DIR%/}"/
+} fi
+
+if [ -n "$BOOTIMG_DIR" ]; then {
+ options_i_bootimg=-I"${BOOTIMG_DIR%/}"/
+} fi
+
+if [ -n "$LDEBUG_DIR" ]; then {
+ options_i_ldebug=-I"${LDEBUG_DIR%/}"/
+} fi
+
+if [ -n "$LDOSMBR_DIR" ]; then {
+ options_i_ldosmbr=-I"${LDOSMBR_DIR%/}"/
+} fi
+
+if [ -n "$INSTSECT_DIR" ]; then {
+ options_i_instsect=-I"${INSTSECT_DIR%/}"/
+} fi
+
+direct=0
+options_dosemu_direct="-A"
+bpe=12
+spc=1
+spi=2880
+nr=224
+pitype=""
+chsheads=8
+chssectors=8
+
+((debug)) && echo DEFAULT_MACHINE="$DEFAULT_MACHINE" parameters="$@"
+machine="$DEFAULT_MACHINE"
+if [[ "$1" == dosemu ]]
+then
+ machine="$1"
+ shift
+elif [[ "$1" == qemu ]]
+then
+ machine="$1"
+ shift
+fi
+
+driveletters=( {C..Z} )
+unitletters=( {a..z} )
+dosemu=0
+qemu=0
+if [[ "$machine" == dosemu ]]
+then
+ dosemu=1
+ if [ -z "$dosemu_drives" ] || [ "$dosemu_drives" == test ]
+ then
+ # Depending on the dosemu2 configuration, dosemu may
+ # set up 2, 3, or 4 system drives. To support all
+ # these cases, the variable dosemu_drives may now
+ # be set to indicate the amount of drives.
+ # If it is not set or set to "test" we do a bit of
+ # magic detection: Run dosemu -E lredir and count
+ # the amount of redirections displayed. Also check
+ # that all of the redirections are on the expected
+ # drives, starting with C:.
+ # We assume that each of the system drives corresponds
+ # to its own ROM-BIOS unit.
+ # Discussed here: https://github.com/dosemu2/dosemu2/discussions/1456
+ driveslist="$("$DOSEMU" -dumb -quiet -E lredir -te 2> /dev/null < /dev/null |
+ sed -re '/^about to execute|^current drive redirections/Id' )"
+ dosemu_drives="$(echo "$driveslist" | wc -l)"
+ for checkdrivenumber in $(seq 0 "$((dosemu_drives - 1))")
+ do
+ driveletter="${driveletters[checkdrivenumber]}"
+ if ! echo "$driveslist" | grep -Eiq "^$driveletter:"
+ then
+ echo "Error: Drive $driveletter: not found in lredir list" >&2
+ exit 1
+ fi
+ done
+ fi
+ ((debug)) && echo "Drive ${driveletters[dosemu_drives]}:"
+elif [[ "$machine" == qemu ]]
+then
+ qemu=1
+ dosemu_drives=0
+else
+ echo "Error: invalid machine \"$machine\" selected" >&2
+ exit 1
+fi
+
+if [[ "$1" == hdimage ]]
+then
+ shift
+ options_hdimage="-D_MBR"
+ if ((dosemu))
+ then
+ options_hdimage="$options_hdimage -D_MBR_DOSEMU_IMAGE_HEADER"
+ fi
+ dosemu_category="disk"
+ dosemu_device="hdimage"
+ dosemu_drive="${driveletters[dosemu_drives]}:"
+ qemu_switch="-hda"
+ name="hdimage"
+ bootfile=""
+ unit="$(printf "%03Xh" "$((0x80 + $dosemu_drives))")"
+ options_ldebug_unit="hd${unitletters[dosemu_drives]}"
+ diskette=0
+ if [[ "$1" == direct ]]
+ then
+ shift
+ # output of the following command is either:
+ # 1. "CPU set to 486", followed by the sign ons for dosemu, FreeDOS,
+ # and config.sys and autoexec.bat output, then executing "exitemu"
+ # (if -C4 switch is not supported, parsed as -C -4).
+ # 2. Only the dosemu sign on with the error message as follows:
+ # "ERROR: Drive G not defined, can't boot!" (execution aborted).
+ if ((dosemu)) && "$DOSEMU" -dumb -E exitemu -C4 < /dev/null 2>&1 | grep "CPU set to 486" > /dev/null
+ then
+ echo "Error: dosemu lacks support for -C4 switch" >&2
+ exit 1
+ fi
+ options_dosemu_direct="-C$dosemu_drives"
+ unit=80h
+ direct=1
+ fi
+ if ((qemu))
+ then
+ unit=80h
+ options_ldebug_unit="hda"
+ fi
+ ((debug)) && echo "unit=\"$unit\""
+ while true
+ do
+ if [[ "$1" == bpe && -n "$2" ]]
+ then
+ ((bpe="$2"))
+ if (($?)) || [[ "$bpe" != 32 && "$bpe" != 16 && "$bpe" != 12 ]]
+ then
+ echo "Error: Invalid bpe \"$2\" given, expected 12, 16, 32" >&2
+ exit 1
+ fi
+ shift
+ shift
+ elif [[ "$1" == spc && -n "$2" ]]
+ then
+ ((spc="($2)"))
+ (($?)) && exit $?
+ shift
+ shift
+ elif [[ "$1" == mib && -n "$2" ]]
+ then
+ ((spi="(($2) * 1024 * 2)"))
+ (($?)) && exit $?
+ shift
+ shift
+ elif [[ "$1" == spi && -n "$2" ]]
+ then
+ ((spi="($2)"))
+ (($?)) && exit $?
+ shift
+ shift
+ elif [[ "$1" == nr && -n "$2" ]]
+ then
+ ((nr="($2)"))
+ (($?)) && exit $?
+ shift
+ shift
+ elif [[ "$1" == pitype && -n "$2" ]]
+ then
+ pitype="$2"
+ shift
+ shift
+ elif [[ "$1" == aligndata ]]
+ then
+ options_hdimage="$options_hdimage -D_ALIGNDATA"
+ shift
+ elif [[ "$1" == chsheads && -n "$2" ]]
+ then
+ ((chsheads="($2)"))
+ (($?)) && exit $?
+ shift
+ shift
+ elif [[ "$1" == chssectors && -n "$2" ]]
+ then
+ ((chssectors="($2)"))
+ (($?)) && exit $?
+ shift
+ shift
+ else
+ break
+ fi
+ done
+ if ((qemu && chsheads > 16))
+ then
+ echo "Warning: qemu autodetection requires CHS heads <= 16" >&2
+ fi
+ if ((dosemu))
+ then
+ options_mcopy_offset="@@$((16 + chsheads * chssectors))s"
+ else
+ options_mcopy_offset="@@$((chsheads * chssectors))s"
+ fi
+ options_hdimage="$options_hdimage -D_CHS_HEADS=$chsheads -D_CHS_SECTORS=$chssectors"
+elif [[ "$1" == diskette ]]
+then
+ shift
+ options_hdimage=""
+ dosemu_category="floppy"
+ dosemu_device="device"
+ dosemu_drive="A:"
+ qemu_switch="-fdb"
+ name="diskette"
+ bootfile="boot${bpe}tw.bin"
+ unit=01h
+ options_ldebug_unit="fdb"
+ options_mcopy_offset=""
+ diskette=1
+ if [[ "$1" == direct ]]
+ then
+ shift
+ qemu_switch="-fda"
+ unit=00h
+ direct=1
+ fi
+else
+ echo "Error: Invalid disk type \"$1\" specified , must be hdimage or diskette" >&2
+ exit 1
+fi
+
+if [[ "$bpe" == 32 ]]
+then
+ if [[ -z "$pitype" ]]
+ then
+ pitype=ptFAT32
+ fi
+ bootname=boot32
+else
+ if [[ -z "$pitype" ]]
+ then
+ if [[ "$bpe" == 16 ]]
+ then
+ pitype=ptFAT16
+ else
+ pitype=ptFAT12
+ fi
+ fi
+ bootname=boot
+fi
+
+echo -ne 'failure\r\n' > result.txt
+
+ "$NASM" "${LDOSBOOT_DIR%/}"/$bootname.asm -w-user \
+ -D_LOAD_NAME="'TESTWRIT'" -D_LOAD_EXT="'SYS'" -D_FAT$bpe \
+ -D_UNIT=$unit \
+ "$@" \
+ "$options_i_lmacros" \
+ -D_MAP=boot${bpe}tw.map -l boot${bpe}tw.lst -o boot${bpe}tw.bin &&
+ "$NASM" "${LDOSBOOT_DIR%/}"/testwrit.asm \
+ "$options_i_lmacros" \
+ -o testwrit.bin -l testwrit.lst &&
+ "$NASM" "${LDOSBOOT_DIR%/}"/iniload.asm -w-user \
+ "$options_i_ldosboot" \
+ "$options_i_lmacros" \
+ "$options_i_scanptab" \
+ -D_PAYLOAD_FILE="'testwrit.bin'" -o testwrit.sys -l testwrin.lst \
+ -D_INILOAD_SIGNATURE='"TW"' &&
+ "$NASM" "${BOOTIMG_DIR%/}"/bootimg.asm \
+ -D_WARN_DEFAULT_OFF=1 \
+ -D_WARN_TOOMANYFAT=0 -D_WARN_ALIGNDATA=0 \
+ $options_hdimage -D_MBR_PART_TYPE="$pitype" \
+ -D_BPE="$bpe" -D_SPC="$spc" -D_SPI="$spi" \
+ -D_SPF="$(( (spi / spc * bpe / 8 + 511) / 512 ))" \
+ -D_NUMROOT="$nr" \
+ -o $name.img -l $name.lst \
+ -D_PAYLOADFILE="testwrit.sys,result.txt" \
+ -D_BOOTFILE="'$bootfile'" \
+ -D_UNIT=$unit \
+ "$@" \
+ -I ./ \
+ "$options_i_lmacros" \
+ "$options_i_bootimg"
+(($?)) && exit $?
+
+pgid="$(ps -o pgid= $$)"
+function handle_timeout_process() {
+ stty sane
+ ((debug)) && ps -e -o pgid=,comm=,pid= | grep -E "^\s*$pgid "
+ pidlist="$(ps -e -o pgid=,comm=,pid= |
+ grep -E "^\s*$pgid " |
+ grep -Ev " (bash|test.sh|ps|grep) ")"
+ pidlist="$(echo "$pidlist" |
+ sed -re 's/^\s+//g' |
+ tr -s " " | cut -d" " -f 3)"
+ if [[ -n "$pidlist" ]]
+ then
+ ((debug)) && ps $pidlist
+ kill $pidlist
+ fi
+}
+
+if ((! diskette))
+then
+ if ((dosemu))
+ then
+ seekmbr=8192
+ else
+ seekmbr=0
+ fi
+ "$NASM" "${LDOSMBR_DIR%/}"/oldmbr.asm -w-user \
+ "$options_i_ldosmbr" \
+ "$options_i_lmacros" \
+ -o oldmbr.bin &&
+ dd if=oldmbr.bin of=hdimage.img seek="$seekmbr" bs=1 count=440 conv=notrunc status=none
+ (($?)) && exit $?
+
+ "$NASM" "${INSTSECT_DIR%/}"/instsect.asm \
+ -w-macro-params-legacy \
+ -I ./ \
+ "$options_i_instsect" \
+ "$options_i_lmacros" \
+ -D_FAT12=0 -D_FAT16=0 -D_FAT32=0 -D_FAT$bpe=1 \
+ -D_PAYLOAD_FAT$bpe="'boot${bpe}tw.bin'" \
+ -o inst${bpe}tw.com -l inst${bpe}tw.lst -D_MAP=inst${bpe}tw.map
+ (($?)) && exit $?
+ if ((dosemu))
+ then
+ timeout --foreground 10 "$DOSEMU" \
+ -I "$dosemu_category { $dosemu_device $name.img }" \
+ -dumb -quiet > result.log 2>&1 -K "$PWD" \
+ -E "inst${bpe}tw.com $dosemu_drive" < /dev/null
+ rc=$?
+ handle_timeout_process
+ if ((rc))
+ then
+ if ((rc == 124))
+ then
+ echo "Error: instsect run timed out" >&2
+ fi
+ cat result.log
+ exit 1
+ fi
+ # && "$DOSEMU" -I "..." -dumb -K "$PWD" -E dirg.bat;
+ elif ((qemu))
+ then
+ cp -aL "$BOOT_KERNEL" "${BOOT_KERNEL##*/}"
+ cp -aL "$BOOT_COMMAND" "${BOOT_COMMAND##*/}"
+ echo -ne "@echo off\r\ninst${bpe}tw.com C:\r\nquit.com\r\n" > autoexec.bat
+ "$NASM" quit.asm \
+ "$options_i_lmacros" \
+ -o quit.com &&
+ "$NASM" "${LDOSBOOT_DIR%/}"/boot.asm -w-user \
+ "$options_i_lmacros" \
+ -D_COMPAT_"$BOOT_PROTOCOL"=1 \
+ -D_LBA=0 -D_USE_PART_INFO=0 -D_QUERY_GEOMETRY=0 \
+ $BOOT_OPTIONS \
+ -D_MAP=bootinst.map -l bootinst.lst -o bootinst.bin &&
+ "$NASM" "${BOOTIMG_DIR%/}"/bootimg.asm \
+ -I ./ \
+ "$options_i_ldebug" \
+ "$options_i_bootimg" \
+ "$options_i_lmacros" \
+ -o diskinst.img -l diskinst.lst \
+ -D_PAYLOADFILE="${BOOT_KERNEL##*/},${BOOT_COMMAND##*/},autoexec.bat,inst${bpe}tw.com,quit.com" \
+ -D_BOOTFILE="'bootinst.bin'"
+ (($?)) && exit $?
+ timeout --foreground 10 "$QEMU" -fda diskinst.img "$qemu_switch" "$name".img -boot order=a -display none 2> /dev/null
+ rc=$?
+ handle_timeout_process
+ if ((rc == 124))
+ then
+ echo "Error: instsect run timed out" >&2
+ exit 1
+ fi
+ fi
+fi
+
+if ((! direct))
+then
+ "$NASM" "${LDOSBOOT_DIR%/}"/boot.asm -w-user \
+ "$options_i_lmacros" \
+ -D_LOAD_NAME="'LDEBUG'" -D_LOAD_EXT="'COM'" \
+ -D_MAP=boot12db.map -l boot12db.lst -o boot12db.bin &&
+ "$NASM" "${BOOTIMG_DIR%/}"/bootimg.asm \
+ -I ./ \
+ "$options_i_ldebug" \
+ "$options_i_bootimg" \
+ "$options_i_lmacros" \
+ -o diskldbg.img -l diskldbg.lst \
+ -D_PAYLOADFILE="ldebug.com" -D_BOOTFILE="'boot12db.bin'"
+ (($?)) && exit $?
+
+ if ((dosemu))
+ then
+ timeout --foreground 10 "$DOSEMU" -input \
+ "$(echo -ne "boot $options_ldebug_unit"'\rif (rc) then boot quit\rq\r')" \
+ -I "floppy { device diskldbg.img }" \
+ -I "$dosemu_category { $dosemu_device $name.img }" \
+ -A -dumb 2> result.err > result.log < /dev/null
+ elif ((qemu))
+ then
+ ( (sleep 2; "$SENDKEYS" "boot $options_ldebug_unitif (rc) then boot quitq" | socat - UNIX-CONNECT:qemu-monitor > /dev/null ) & > /dev/null)
+ timeout --foreground 10 "$QEMU" -fda diskldbg.img "$qemu_switch" "$name".img -boot order=a -monitor unix:qemu-monitor,server,nowait -display none 2> /dev/null
+ fi
+else
+ if ((dosemu))
+ then
+ timeout --foreground 10 "$DOSEMU" \
+ -I "$dosemu_category { $dosemu_device $name.img }" \
+ "$options_dosemu_direct" -dumb 2> result.err > result.log < /dev/null
+ elif ((qemu))
+ then
+ timeout --foreground 10 "$QEMU" "$qemu_switch" "$name".img -display none 2> /dev/null
+ fi
+fi
+
+rc=$?
+handle_timeout_process
+if ((rc == 124))
+then
+ echo "${testrunname}timeout"
+fi
+if [[ "$(mtype -t -i $name.img$options_mcopy_offset ::RESULT.TXT 2> /dev/null)" == success ]]
+then
+ echo "${testrunname}success"
+elif ((dosemu))
+then
+ echo "${testrunname}failure, log contains:"
+ if [ ! -s result.log ]
+ then
+ echo "Result log file empty, dosemu error?"
+ fi
+ cat result.log | perl -e '
+ my %errorlookup = (
+ V => "Check value mismatch",
+ F => "File not found",
+ E => "Not enough file data",
+ R => "Disk read error",
+ B => "Bad chain / bad FS",
+ M => "Out of memory",
+ I => "FSIBOOT error",
+ S => "Fix sector size mismatch",
+ C => "Fix cluster size mismatch",
+ N => "Non-FAT load needs larger cluster size",
+ );
+ my $empty = 1;
+ while (<>) {
+ next if $empty and /^\s*$/;
+ next if /^(dosemu2 2|Configured: |Please test against)/;
+ next if /^(Get the latest code|Submit Bugs via|Ask for help in)/;
+ next if /^(This program comes with|This is free software,)/;
+ $empty = 0;
+ if (/^([A-Z])\x7/ and defined $errorlookup{$1}) {
+ s/^(.).//;
+ print "lDOS boot error condition letter \"$1\" = $errorlookup{$1}\n";
+ s/[\x7\n]//g;
+ s/^\s*$//;
+ next if /^(dosemu2 2|Configured: |Please test against)/;
+ next if /^(Get the latest code|Submit Bugs via|Ask for help in)/;
+ next if /^(This program comes with|This is free software,)/;
+ print;
+ print "\n" unless /^$/;
+ } else {
+ s/[\x7\n]//g;
+ print;
+ print "\n";
+ };
+ };'
+else
+ echo "${testrunname}failure"
+fi
diff --git a/test/ldosboot/testpl.asm b/test/ldosboot/testpl.asm
new file mode 100644
index 0000000..1071edf
--- /dev/null
+++ b/test/ldosboot/testpl.asm
@@ -0,0 +1,867 @@
+
+%if 0
+
+Loader test payload
+ by C. Masloch, 2017
+
+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
+
+
+%include "lmacros2.mac"
+
+ numdef LARGE, 1
+ numdef PADDING, 0
+
+ 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 LOADSTACKVARS, -10h
+lsvFirstCluster: resd 1
+lsvFATSector: resd 1
+lsvFATSeg: resw 1
+lsvLoadSeg: resw 1
+lsvDataStart: resd 1
+ endstruc
+
+lsvclSignature equ "CL"
+lsvclBufferLength equ 256
+
+ struc LOADDATA, LOADSTACKVARS - 10h
+ldMemoryTop: resw 1
+ldLoadTop: resw 1
+ldSectorSeg: resw 1
+ldFATType: resb 1
+ldHasLBA: resb 1
+ldClusterSize: resw 1
+ldParaPerSector:resw 1
+ldLoadingSeg: ; word
+lsvCommandLine: ; word
+.start: equ $ - lsvclBufferLength
+.signature: resw 1
+ldLoadUntilSeg: ; word
+lsvExtra: ; word
+.partition: resb 1 ; byte
+.flags: resb 1 ; byte
+ endstruc
+
+lsvefNoDataStart equ 1
+lsvefPartitionNumber equ 2
+
+ struc LOADCMDLINE, LOADDATA - lsvclBufferLength
+ldCommandLine:
+.start: resb lsvclBufferLength
+ endstruc
+
+
+%ifndef _MAP
+%elifempty _MAP
+%else ; defined non-empty, str or non-str
+ [map all _MAP]
+%endif
+
+ cpu 8086
+ org 0
+payload:
+ ; The device header is of a fixed format.
+ ; For our purposes, the 4-byte code for
+ ; each the strategy entry and the
+ ; interrupt entry is part of this format.
+ ; (DOS may read the attributes or entrypoint
+ ; offsets before calling either, so the
+ ; inicomp stage needs to recreate in its
+ ; entrypoints part exactly what we have here.)
+device_header:
+.next:
+ fill 2, -1, jmp strict short j_zero_entrypoint
+ dw -1
+.attributes:
+ dw 8000h ; character device
+.strategy:
+ dw .strategy_entry ; -> strategy entry
+.interrupt:
+ dw .interrupt_entry ; -> interrupt entry
+.name:
+ fill 8, 32, db "TESTPL$$" ; character device name
+.strategy_entry:
+ fill 4, 90h, jmp device_entrypoint
+.interrupt_entry:
+ fill 4, 90h, retf
+
+
+j_zero_entrypoint:
+ jmp zero_entrypoint
+
+
+ nop
+ align 32, nop
+kernel_entrypoint:
+ ; cs:ip = load seg : 32 here
+%if ($ - $$) != 32
+ %error Wrong kernel mode entrypoint
+%endif
+
+ ; S0 +28
+ pushf ; +26
+ push ax ; +24
+ push cs ; +22
+ call .push_ip
+.push_ip:
+ pop ax
+ sub ax, .push_ip - kernel_entrypoint
+ push ax ; +20 IP
+
+common_entrypoint:
+ push es ; +18 ES
+ push bx ; +16 BX
+ cmp word [cs:signature], 2638h
+ je sig1_valid
+ jmp sig_invalid
+
+ align 64, nop
+dos_exe_entrypoint:
+ ; cs:ip = PSP : 256 + 64 here
+ ;
+ ; Code must be position independent enough.
+%if ($ - $$) != 64
+ %error Wrong EXE mode entrypoint
+%endif
+
+ ; S0 +28
+ pushf ; +26
+ push ax ; +24
+ push cs ; +22
+ call .push_ip
+.push_ip:
+ pop ax
+ sub ax, .push_ip - dos_exe_entrypoint
+ push ax ; +20 IP
+
+dos_com_entrypoint:
+ mov byte [cs:100h + loadmode], 1
+ mov ax, cs
+ add ax, 10h ; simulate kernel loading
+ push ax
+jump_common_entrypoint:
+ mov ax, common_entrypoint
+ push ax
+ retf ; jump to cs + 10h : common_entrypoint
+
+
+zero_entrypoint:
+ ; S0 +28
+ pushf ; +26
+ push ax ; +24
+ push cs ; +22
+ call .push_ip
+.push_ip:
+ pop ax
+ sub ax, .push_ip
+ push ax ; +20 IP
+
+ cmp word [cs:0], 20CDh
+ jne @F
+ cmp ax, 100h
+ je dos_com_entrypoint
+@@:
+
+ push cx
+ mov cl, 4
+ shr ax, cl
+ mov cx, cs
+ add ax, cx
+ pop cx
+ push ax
+ jmp jump_common_entrypoint
+
+
+device_entrypoint:
+ pushf
+ push ax
+ push cs
+ call .push_ip
+.push_ip:
+ pop ax
+ sub ax, .push_ip - device_header.strategy_entry
+ push ax
+ mov byte [cs:loadmode], 2
+ mov word [cs:dev_sp], sp
+ mov word [cs:dev_exit], dev_exit.1
+ jmp common_entrypoint
+
+
+sig_invalid:
+ call error
+ db "Signature invalid.", 0
+
+sig1_valid:
+ push ds
+ push bx
+ mov bx, cs
+ add bx, (signature2 -$$+0) >> 4
+ mov ds, bx
+ cmp word [(signature2 -$$+0) & 0Fh], 2638h
+ pop bx
+ pop ds
+ jne sig_invalid
+ jmp sig_valid
+
+msg:
+.error: db "Test payload error: ", 0
+.test: db "Test payload loaded.", 13, 10, 0
+
+.psp_and_size_before: asciz "PSP at "
+.psp_and_size_between: asciz "h, size of memory block is "
+.psp_and_size_after: asciz "h paragraphs.",13,10
+.cmdline.kern.none: asciz "No kernel command line given!",13,10
+.cmdline_before.kern: asciz "Kernel command line = ",'"'
+.cmdline_before.app: asciz "Application command line = ",'"'
+.cmdline_before.device: asciz "Device command line = ",'"'
+.cmdline_after: asciz '"',13,10
+
+ align 4
+.foundname:
+ times 8+1+3+1 db 0
+ ; buffer for base name (8) + dot (1) + ext (3) + NUL (1)
+ align 2
+.foundname_none:
+ asciz "(None)"
+.foundname_none_size: equ $ - .foundname_none
+ align 2
+.names:
+ dw .name_first, 0
+ dw .name_second, 0
+ dw .name_third, 0
+ dw .name_fourth, 0
+ dw 0
+.name_first: asciz "1st name"
+.name_second: asciz "2nd name"
+.name_third: asciz "3rd name"
+.name_fourth: asciz "4th name"
+.name_before: asciz ": "
+.name_quote: asciz '"'
+.name_after: asciz 13,10
+
+ align 4
+dev_request_header:
+ dd 0
+loadmode: dw 0 ; 0 = loaded as boot payload,
+ ; 1 = loaded as DOS application,
+ ; 2 = loaded as DOS device driver
+dev_sp: dw 0
+dev_exit: dw 0
+.1:
+ mov sp, word [cs:dev_sp]
+ jmp .common_1
+
+.2: mov sp, word [cs:dev_sp]
+ jmp .common_2
+
+.common_2:
+ pop ax ; ss
+ pop ds
+ pop ax ; sp
+ pop bp
+ pop di
+ pop si
+ pop dx
+ pop cx
+
+.common_1:
+ pop bx ; bx
+ pop es ; es
+ pop ax ; (IP)
+ pop ax ; (CS)
+ pop ax ; ax
+ mov word [es:bx + 3], 8103h
+ ; error, done, error code: unknown command
+ popf ; flags
+ retf ; far return to DOS
+
+
+error:
+ push cs
+ pop ds
+ mov si, msg.error
+ call disp_msg
+ pop si
+ call disp_msg
+ test byte [cs:loadmode], 2
+ jz .dos_or_bios
+ jmp near [cs:dev_exit]
+
+.dos_or_bios:
+ test byte [cs:loadmode], 1
+ jz .bios
+
+ mov ax, 4C01h
+ int 21h
+
+.bios:
+ xor ax, ax
+ int 16h
+ int 19h
+
+disp_msg_asciz:
+ push ds
+ push si
+ push ax
+ push cs
+ pop ds
+ mov si, dx
+ call disp_msg
+ pop ax
+ pop si
+ pop ds
+ retn
+
+disp_msg:
+@@:
+ lodsb
+ test al, al
+ jz @F
+ call disp_al
+ jmp short @B
+
+disp_al:
+ push ax
+ push bx
+ push dx
+ push bp
+ test byte [cs:loadmode], 1 | 2
+ jz .bios
+
+ mov dl, al
+ mov ah, 02h
+ int 21h
+ jmp .common
+
+.bios:
+ mov ah, 0Eh
+ mov bx, 7
+ int 10h
+
+.common:
+ pop bp
+ pop dx
+ pop bx
+ pop ax
+@@:
+ retn
+
+disp_msg_length:
+ push cx
+ jcxz .ret
+.loop:
+ lodsb
+ call disp_al
+ loop .loop
+.ret:
+ pop cx
+ retn
+
+
+sig_valid:
+ ; S0 +28
+; pushf ; +26
+; push ax ; +24
+; push cs ; +22
+; call .push_ip
+;.push_ip:
+; pop ax
+; sub ax, .push_ip
+; push ax ; +20 IP
+; push es ; +18
+; push bx ; +16
+ sti
+ cld
+ push cx ; +14
+ push dx ; +12
+ push si ; +10
+ push di ; +8
+ push bp ; +6
+ mov ax, sp
+ add ax, 22
+ push ax ; +4 SP
+ push ds ; +2
+ push ss ; +0
+
+ test byte [cs:loadmode], 2
+ jz @F
+
+ mov word [cs:dev_sp], sp
+ mov word [cs:dev_exit], dev_exit.2
+ mov word [cs:dev_request_header], bx
+ mov word [cs:dev_request_header + 2], es
+
+ cmp byte [es:bx + 2], 0 ; command code 0 (init) ?
+ jne dev_exit.2 ; else immediately return -->
+
+ mov byte [es:bx + 13], 0 ; number of units = 0
+ mov word [es:bx + 14 + 2], cs
+ and word [es:bx + 14], 0 ; -> after end of memory to allocate
+ or word [cs:device_header.next], -1
+ ; fill in offset of device header link
+@@:
+
+ mov si, sp
+ push ss
+ pop ds
+ mov di, table
+ push cs
+ pop es
+loop_table:
+ mov bx, [es:di + 0]
+ mov al, 32
+ call disp_al
+ mov ax, [es:di + 2]
+ call disp_al
+ xchg al, ah
+ call disp_al
+ cmp bx, -1
+ je @F
+ mov al, '='
+ call disp_al
+ mov ax, [si + bx]
+ call disp_ax_hex
+@@:
+ add di, 4
+ cmp di, table.end
+ jb loop_table
+
+listnames:
+ test byte [cs:loadmode], 1 | 2
+ jnz .skip
+
+ mov bx, msg.names
+ push ss
+ pop ds
+ lea si, [bp + bsBPB + ebpbNew + BPBN_size]
+ mov cx, (512 - (bsBPB + ebpbNew + BPBN_size)) - 2
+ ; -2 = AA55h sig
+ cmp word [bp + bsBPB + bpbSectorsPerFAT], 0
+ je @F
+ mov cx, (512 + (ebpbNew - bpbNew) - (bsBPB + ebpbNew + BPBN_size)) - 2
+@@:
+.nextname:
+ call findname
+ lahf
+ mov dx, [cs:bx]
+ call disp_msg_asciz
+ mov dx, msg.name_before
+ call disp_msg_asciz
+ sahf
+ jc @F ; skip quote if no name -->
+ mov dx, msg.name_quote
+ call disp_msg_asciz
+@@:
+ mov dx, msg.foundname
+ call disp_msg_asciz
+ sahf
+ jc @F ; skip quote if no name -->
+ mov dx, msg.name_quote
+ call disp_msg_asciz
+@@:
+ mov dx, msg.name_after
+ call disp_msg_asciz
+ sahf
+ mov ax, 0
+ jc @F ; set to zero if no name -->
+ lea ax, [si - 11] ; -> name in buffer
+@@:
+ mov word [cs:bx + 2], ax ; -> name in buffer, or 0
+ add bx, 4
+ cmp word [cs:bx], 0
+ jne .nextname
+
+.skip:
+
+ push cs
+ pop ds
+ mov si, msg.test
+ call disp_msg
+
+ test byte [cs:loadmode], 1
+ jz .skip_psp_dos
+
+ mov si, msg.psp_and_size_before
+ call disp_msg
+ mov ah, 51h
+ int 21h
+ mov ax, bx
+ call disp_ax_hex
+ mov si, msg.psp_and_size_between
+ call disp_msg
+ dec bx
+ mov es, bx
+ mov ax, word [es:3]
+ call disp_ax_hex
+ mov si, msg.psp_and_size_after
+ call disp_msg
+
+
+ mov si, msg.cmdline_before.app
+ call disp_msg
+
+ mov ah, 51h
+ int 21h
+ mov ds, bx
+ mov si, 81h
+ xor cx, cx
+ mov cl, byte [si - 1]
+ call disp_msg_length
+
+ push cs
+ pop ds
+ mov si, msg.cmdline_after
+ call disp_msg
+
+ jmp .after_cmdline
+
+.skip_psp_dos:
+ test byte [cs:loadmode], 2
+ jz .skip_device
+
+ mov si, msg.cmdline_before.device
+ call disp_msg
+
+ les bx, [cs:dev_request_header]
+ les di, [es:bx + 18]
+ push es
+ pop ds
+ mov si, di
+
+ ; Writing MS-DOS Device Drivers, second edition, page 349
+ ; specifies the following as to the command line termination:
+ ; "Note that the DEVICE= command string is terminated by an
+ ; Ah when there are no arguments. When there are arguments,
+ ; the string is terminated with the following sequence:
+ ; 0h, Dh, Ah."
+ db __TEST_IMM8
+@@:
+ inc di
+ cmp byte [di], 0
+ je @F
+ cmp byte [di], 13
+ je @F
+ cmp byte [di], 10
+ jne @B
+@@: ; di -> at terminator
+ sub di, si
+ mov cx, di
+ call disp_msg_length
+
+ push cs
+ pop ds
+ mov si, msg.cmdline_after
+ call disp_msg
+
+ jmp .after_cmdline
+
+.skip_device:
+ mov si, msg.cmdline.kern.none
+ cmp word [bp + ldCommandLine], 0FF00h
+ je .no_kernel_cmdline
+
+ mov si, msg.cmdline_before.kern
+ call disp_msg
+
+ push ss
+ pop ds
+ lea si, [bp + ldCommandLine]
+ call disp_msg
+
+ push cs
+ pop ds
+ mov si, msg.cmdline_after
+.no_kernel_cmdline:
+ call disp_msg
+
+.after_cmdline:
+ int3
+
+ test byte [cs:loadmode], 2
+ jz .dos_or_bios
+
+ jmp near [cs:dev_exit]
+
+.dos_or_bios:
+ test byte [cs:loadmode], 1
+ jz .bios
+
+ mov ax, 4C00h
+ int 21h
+
+.bios:
+ xor ax, ax
+ int 16h
+ int 19h
+
+
+disp_ax_hex: ; ax
+ xchg al,ah
+ call disp_al_hex ; display former ah
+ xchg al,ah ; and fall trough for al
+disp_al_hex: ; al
+ push cx
+ mov cl,4
+ ror al,cl
+ call disp_al_lownibble_hex ; display former high-nibble
+ rol al,cl
+ pop cx
+ ; and fall trough for low-nibble
+disp_al_lownibble_hex:
+ push ax ; save ax for call return
+ and al,00001111b ; high nibble must be zero
+ add al,'0' ; if number is 0-9, now it's the correct character
+ cmp al,'9'
+ jna .decimalnum ; if we get decimal number with this, ok -->
+ add al,7 ; otherwise, add 7 and we are inside our alphabet
+ .decimalnum:
+ call disp_al
+ pop ax
+ retn
+
+
+ ; INP: ds:si -> first byte to check for name
+ ; cx = number of bytes left
+ ; OUT: (8+1+3+1)bytes[es:msg.foundname] = found name,
+ ; converted to 8.3 ASCIZ format,
+ ; "(None)" if none
+ ; CY if no filename found,
+ ; si = INP:si + INP:cx
+ ; cx = 0
+ ; NC if filename found,
+ ; ds:si -> byte behind the name, thus ds:(si-11)-> name
+ ; cx = number of bytes left
+ ; CHG: di, ax
+findname:
+.:
+ cmp cx, 11 ; enough for another name ?
+ jb .none ; no -->
+ ; (cx == 0 jumps here too)
+.check:
+ push cx
+ push si
+ mov cx, 11
+ lodsb
+ mov ah, al ; check for same char in all 11 places
+ cmp al, 32 ; first character must not be blank
+ je .check_fail ; if it is -->
+; cmp al, 5 ; first character may be 05h to indicate 0E5h
+; je .check_pass
+ db __TEST_IMM8 ; (skip lodsb)
+.check_loop_same:
+ lodsb
+ cmp ah, al
+ jne .check_loop_differs
+ call .check_character
+ jc .check_fail
+ loop .check_loop_same
+ ; if we arrive here, all characters (while valid) are the
+ ; same character repeated 11 times. we disallow this in case
+ ; that the padding character is an allowed one (eg '&' 26h).
+.check_fail:
+ pop si
+ pop cx
+ dec cx ; lessen the counter
+ inc si ; -> next position to check
+ jmp .
+
+.check_character:
+ cmp al, 32
+ jb .check_character_fail
+ cmp al, 127
+; je .check_character_fail
+ jae .check_character_fail
+ ; note: with all characters >= 128 allowed,
+ ; we get false positives in our sectors.
+ cmp al, '.'
+ je .check_character_fail
+ cmp al, '/'
+ je .check_character_fail
+ cmp al, '\'
+ je .check_character_fail
+ cmp al, 'a'
+ jb .check_character_pass
+ cmp al, 'z'
+ ja .check_character_pass
+.check_character_fail:
+ stc
+ retn
+
+.check_character_pass:
+ clc
+ retn
+
+.check_loop:
+ lodsb
+.check_loop_differs:
+ call .check_character
+ jc .check_fail
+.check_pass:
+ loop .check_loop
+
+ pop ax ; (discard si)
+ sub si, 11 ; -> at name
+
+ call convert_name_to_asciz
+ ; si -> behind name
+ pop cx
+ sub cx, 11 ; lessen the counter
+ clc
+ retn
+
+.none:
+ add si, cx
+ mov di, msg.foundname
+ push si
+ push ds
+ push cs
+ pop ds
+ mov si, msg.foundname_none
+ mov cx, (msg.foundname_none_size + 1) >> 1
+ rep movsw
+ pop ds
+ pop si
+ xor cx, cx
+ stc
+ retn
+
+
+ ; INP: ds:si -> 11-byte blank-padded name
+ ; es:msg.foundname -> (8+1+3+1)-byte buffer
+ ; OUT: ds:si -> behind 11-byte blank-padded name
+ ; es:msg.foundname filled
+ ; CHG: cx, di, ax
+convert_name_to_asciz:
+ mov di, msg.foundname
+ mov cx, 8
+ rep movsb ; copy over base name, si -> extension
+ cmp byte [es:di - 8], 05h ; is it 05h ?
+ jne @F ; no -->
+ mov byte [es:di - 8], 0E5h ; yes, convert to 0E5h
+@@:
+
+ db __TEST_IMM8 ; (skip dec)
+@@:
+ dec di ; decrement -> at previous trailing blank
+ cmp byte [es:di - 1], 32 ; trailing blank ?
+ je @B ; yes -->
+
+ mov al, '.'
+ stosb ; store dot (if needed)
+ mov cl, 3
+ rep movsb ; copy over extension, si -> behind name
+
+ db __TEST_IMM8 ; (skip dec)
+@@:
+ dec di ; decrement -> at previous trailing blank
+ cmp byte [es:di - 1], 32 ; trailing blank ?
+ je @B ; yes -->
+
+ cmp byte [es:di - 1], '.' ; trailing dot ? (only occurs if all-blank ext)
+ jne @F ; no -->
+ dec di ; -> at the dot
+@@:
+ mov al, 0
+ stosb ; store filename terminator
+ retn
+
+
+ align 4
+table:
+ dw +0, "SS"
+ dw +6, "BP"
+ dw +4, "SP"
+ dw +22, "CS"
+ dw +20, "IP"
+ dw +26, "FL"
+ db -1, -1, 13,10
+ dw +2, "DS"
+ dw +10, "SI"
+ dw +18, "ES"
+ dw +8, "DI"
+ db -1, -1, 13,10
+ dw +24, "AX"
+ dw +16, "BX"
+ dw +14, "CX"
+ dw +12, "DX"
+ db -1, -1, 13,10
+ dw +28, "S0"
+ dw +30, "S1"
+ dw +32, "S2"
+ dw +34, "S3"
+ dw +36, "S4"
+ dw +38, "S5"
+ dw +40, "S6"
+ dw +42, "S7"
+ db -1, -1, 13,10
+ dw +44, "S8"
+ dw +46, "S9"
+ dw +48, "SA"
+ dw +50, "SB"
+ dw +52, "SC"
+ dw +54, "SD"
+ dw +56, "SE"
+ dw +58, "SF"
+ db -1, -1, 13,10
+.end:
+
+
+signature:
+ dw 2638h
+ align 16, db 0
+
+%if _LARGE
+ times 64 * 1024 db 0
+%endif
+
+signature2:
+ dw 2638h
+
+%if _PADDING
+ times _PADDING - ($ - $$) db 0
+%endif
diff --git a/test/ldosboot/testwrit.asm b/test/ldosboot/testwrit.asm
new file mode 100644
index 0000000..d07c96c
--- /dev/null
+++ b/test/ldosboot/testwrit.asm
@@ -0,0 +1,1023 @@
+
+%if 0
+
+Write success indicator after file system booting
+ by C. Masloch, 2020
+
+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
+
+
+%assign __lMACROS1_MAC__DEBUG_DEFAULTS 1
+%include "lmacros3.mac"
+
+ 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 LOADSTACKVARS, -10h
+lsvFirstCluster: resd 1
+lsvFATSector: resd 1
+lsvFATSeg: resw 1
+lsvLoadSeg: resw 1
+lsvDataStart: resd 1
+ endstruc
+
+lsvclSignature equ "CL"
+lsvclBufferLength equ 256
+
+ struc LOADDATA, LOADSTACKVARS - 10h
+ldMemoryTop: resw 1
+ldLoadTop: resw 1
+ldSectorSeg: resw 1
+ldFATType: resb 1
+ldHasLBA: resb 1
+ldClusterSize: resw 1
+ldParaPerSector:resw 1
+ldLoadingSeg: resw 1
+ldLoadUntilSeg: resw 1
+ endstruc
+
+ struc LOADCMDLINE, LOADDATA - lsvclBufferLength
+ldCommandLine:
+.start: resb lsvclBufferLength
+ endstruc
+
+ struc LBAPACKET
+lpSize: resw 1
+lpCount: resw 1
+lpBuffer: resd 1
+lpSector: resq 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
+
+
+%ifndef _MAP
+%elifempty _MAP
+%else ; defined non-empty, str or non-str
+ [map all _MAP]
+%endif
+
+ defaulting
+
+ numdef QUERY_GEOMETRY, 1 ; query geometry via 13.08 (for CHS access)
+ numdef CHS, 1 ; support CHS (if it fits)
+ numdef LBA, 1 ; support LBA (if available)
+ numdef LBA_33_BIT, 1 ; support 33-bit LBA
+ numdef LBA_CHECK_NO_33, 1 ; else: check that LBA doesn't carry
+ numdef LBA_SKIP_CHECK, 0 ; don't use proper LBA extensions check
+ numdef LBA_RETRY, 1 ; retry LBA reads
+ numdef CHS_RETRY, 1 ; retry CHS reads
+
+ strdef FILE_NAME, "RESULT"
+ strdef FILE_EXT, "TXT" ; name of file to write
+ strdef FILE_SUCCESS_MSG,"success"
+
+
+ cpu 8086
+ org 0
+start:
+ push ss
+ pop ds
+ mov word [cs:dirsp], sp
+
+ mov ax, cs
+ cmp ax, 200h + 200h ; loaded high ?
+ mov dx, 200h ; yes, use low buffer
+ jae @F
+ add ax, paras(end - $$) ; => behind used load image
+ mov dx, ax
+ neg ax ; - behind used
+ add ax, word [bp + ldLoadTop] ; top - used = length of free
+ cmp ax, 200h ; enough ?
+ jb error_outofmemory ; no -->
+ ; (For simplicity we do not attempt to relocate our load image.)
+@@:
+ mov word [cs:dirbuf], dx
+
+%if _QUERY_GEOMETRY || !_LBA_SKIP_CHECK
+ call query_geometry
+ ; The ebpbNew BPBN needs to be initialised
+ ; to use this function. It must be called
+ ; before using read_sector.
+%endif
+
+ cmp byte [bp + ldFATType], 16
+ ja search32
+ cmp byte [bp + ldFATType], 0
+ ja search1216
+ call error
+ asciz "Unknown file system (ldFATType = 0)."
+
+search1216:
+ xor cx, cx
+ mov bx, word [bp + ldParaPerSector]
+ shr bx, 1 ; = entries per sector
+
+; number of sectors used for root directory (store in CX)
+ mov si, [bp + bsBPB + bpbNumRootDirEnts]
+ mov ax, bx
+ ; The ax value here is the last value of bx, which is set
+ ; by the shr instruction. Therefore, it cannot be higher
+ ; than 7FFFh, so this cwd instruction always zeros dx.
+ cwd
+ dec ax ; rounding up
+ add ax, si ; from BPB
+ adc dx, dx ; account for overflow (dx was zero)
+ div bx ; get number of root sectors
+ xchg ax, cx ; cx = number of root secs, ! ah = 0
+
+; first sector of root directory
+ mov al, [bp + bsBPB + bpbNumFATs]; ! ah = 0, hence ax = number of FATs
+ mul word [bp + bsBPB + bpbSectorsPerFAT]
+ add ax, [bp + bsBPB + bpbReservedSectors]
+ adc dl, dh ; account for overflow (dh was and is 0)
+
+; Scan root directory for file. We don't bother to check for deleted
+; entries (E5h) or entries that mark the end of the directory (00h).
+ ; number of root entries in si here
+.next_sect:
+ mov cx, bx ; entries per sector as loop counter
+ call read_sector_to_dirbuf
+ mov bx, cx ; restore bx for next iteration later
+
+ xor di, di ; es:di-> first entry in this sector
+.next_ent:
+ call check_entry
+ dec si ; count down entire root's entries
+ loopnz .next_ent ; count down sector's entries (jumps iff si >0 && cx >0)
+ jnz .next_sect ; (jumps iff si >0 && cx ==0)
+ ; ends up here iff si ==0
+ ; ie all root entries checked unsuccessfully
+ jmp error_filenotfound
+
+
+search32:
+ mov ax, [bp + bsBPB + ebpbRootCluster]
+ mov dx, [bp + bsBPB + ebpbRootCluster + 2]
+ mov si, word [bp + lsvFATSector + 2]
+ mov di, word [bp + lsvFATSector]
+ call check_clust
+ jc error_filenotfound
+
+.next_root_clust:
+ call clust_to_first_sector
+ push cx
+ push bx
+ mov cx, [bp + ldClusterSize]
+.next_root_sect:
+ push cx
+ mov cx, [bp + ldParaPerSector]
+ shr cx, 1
+
+; Scan root directory for file. We don't bother to check for deleted
+; entries (E5h) or entries that mark the end of the directory (00h).
+ call read_sector_to_dirbuf
+
+ push di
+ xor di, di ; es:di-> first entry in this sector
+.next_ent:
+ call check_entry
+ loop .next_ent ; count down sector's entries (jumps iff cx >0)
+ pop di
+ pop cx
+ loop .next_root_sect
+ pop bx
+ pop cx
+ call clust_next
+ jnc .next_root_clust
+ jmp error_filenotfound
+
+
+read_sector_to_dirbuf:
+ mov bx, word [cs:dirbuf]
+ mov word [cs:dirsec], ax
+ mov word [cs:dirsec + 2], dx
+ jmp read_sector
+
+check_entry:
+ test byte [es:di + deAttrib], ATTR_DIRECTORY | ATTR_VOLLABEL
+ jnz @F ; directory, label, or LFN entry --> (NZ)
+ push si
+ push di
+ push cx
+ push ds
+ push cs
+ pop ds
+ mov si, msg.filename ; ds:si-> name to match
+ mov cx, 11 ; length of padded 8.3 FAT filename
+ repe cmpsb ; check entry
+ pop ds
+ pop cx
+ pop di
+ pop si
+@@: ; ZR = match, NZ = mismatch
+ je found_it ; found entry -->
+ lea di, [di + DIRENTRY_size]
+ retn
+
+found_it:
+ mov sp, word [cs:dirsp]
+ mov word [cs:dirofs], di
+
+ cmp word [es:di + deSize], 0
+ jne @F
+ cmp word [es:di + deSize + 2], 0
+ jne @F
+
+error_emptyfile: equ $
+ call error
+ asciz "File is empty."
+
+@@:
+; get starting cluster of file
+ mov ax, [es:di + deClusterLow]
+ mov dx, [es:di + deClusterHigh] ; dx:ax = first cluster
+ cmp byte [bp + ldFATType], 16
+ ja @F
+ xor dx, dx
+@@:
+
+ mov di, [bp + lsvFATSector]
+ mov si, [bp + lsvFATSector + 2]
+ call check_clust
+ jc error_emptyfile
+
+ call clust_to_first_sector
+ mov bx, cs
+ add bx, paras(filbuf - $$)
+ call write_sector
+
+ mov es, word [cs:dirbuf]
+ mov bx, word [cs:dirofs]
+ mov word [es:bx + deSize], filbuf.size
+ and word [es:bx + deSize + 2], 0
+ mov bx, es
+ mov ax, word [cs:dirsec]
+ mov dx, word [cs:dirsec + 2]
+ call write_sector
+
+ push cs
+ pop ds
+ mov si, msg.success
+ call disp_error
+
+quit:
+ int3
+
+ mov ax, 0F000h
+ mov es, ax
+ push cs
+ pop ds ; avoid "repe cs cmpsw" (8086 bug)
+ mov di, 0FFF5h
+ mov si, msg.dosemudate
+ mov cx, 4
+ repe cmpsw ; running in DosEmu?
+ jne .quit_not_dosemu
+
+ xor bx, bx
+ mov ax, -1
+ int 0E6h ; dosemu quit
+
+.quit_not_dosemu:
+
+; from https://stackoverflow.com/a/5240330/738287
+ mov ax, 5301h
+ xor bx, bx
+ int 15h ; connect to APM API
+
+ mov ax, 530Eh
+ xor bx, bx
+ mov cx, 0102h
+ int 15h ; set APM version to 1.02
+
+ mov ax, 5307h
+ mov bx, 1
+ mov cx, 3
+ int 15h ; shut down system
+
+ ; setopt [cs:quitrecurse], 1
+ call error
+ asciz "Quit failed."
+
+
+error:
+ push cs
+ pop ds
+ mov si, msg.error
+ call disp_error
+ pop si
+ call disp_error
+ mov si, msg.error.trailer
+ call disp_error
+%if 0
+ testopt [cs:quitrecurse], 1
+ jz quit
+%endif
+ int3
+ xor ax, ax
+ int 16h
+ int 19h
+
+disp_error:
+.:
+ lodsb
+ test al, al
+ jz .ret
+ mov ah, 0Eh
+ mov bx, 7
+ push bp
+ ; (call may change bp)
+ int 10h
+ pop bp
+ jmp short .
+
+
+ align 4
+dirsec: dd 0
+dirofs: dw 0
+dirbuf: dw 0
+dirsp: dw 0
+; quitrecurse: db 0
+
+msg:
+.error: asciz "Load error: "
+.success: db "Test writer loaded successfully. Quitting the machine."
+.error.trailer: asciz 13,10
+.filename: fill 8, 32, db _FILE_NAME
+ fill 3, 32, db _FILE_EXT
+ align 4
+.dosemudate: db "02/25/93"
+
+ align 16, db 38
+filbuf:
+.:
+ db _FILE_SUCCESS_MSG,13,10
+.size: equ $ - .
+%if .size > 32
+ %error File success message is too long
+%endif
+ _fill 8192, 38, .
+
+
+query_geometry:
+%if _QUERY_GEOMETRY ; +30 bytes
+ mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
+ %if !_LBA_SKIP_CHECK
+ push dx
+ %endif
+; test dl, dl ; floppy?
+; jns @F ; don't attempt query, might fail -->
+ ; Note that while the original PC BIOS doesn't support this function
+ ; (for its diskettes), it does properly return the error code 01h.
+ ; https://sites.google.com/site/pcdosretro/ibmpcbios (IBM PC version 1)
+ mov ah, 08h
+ xor cx, cx ; initialise cl to 0
+ stc ; initialise to CY
+ int 13h ; query drive geometry
+ jc @F ; apparently failed -->
+ and cx, 3Fh ; get sectors
+ jz @F ; invalid (S is 1-based), don't use -->
+ mov [bp + bsBPB + bpbCHSSectors], cx
+ mov cl, dh ; cx = maximum head number
+ inc cx ; cx = number of heads (H is 0-based)
+ mov [bp + bsBPB + bpbCHSHeads], cx
+@@:
+%endif
+
+%if !_LBA_SKIP_CHECK
+ mov ah, 41h
+ %if _QUERY_GEOMETRY
+ pop dx
+ %else
+ mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
+ %endif
+ mov bx, 55AAh
+ stc
+ int 13h ; 13.41.bx=55AA extensions installation check
+ mov al, 0 ; zero in case of no LBA support
+ jc .no_lba
+ cmp bx, 0AA55h
+ jne .no_lba
+ test cl, 1 ; support bitmap bit 0
+ jz .no_lba
+ inc ax ; al = 1 to indicate LBA support
+.no_lba:
+ mov byte [bp + ldHasLBA], al
+%endif
+
+%if 1 || _QUERY_GEOMETRY || !_LBA_SKIP_CHECK
+disp_error.ret:
+ retn
+%endif
+
+
+ ; Read a sector using Int13.02 or Int13.42
+ ;
+ ; INP: dx:ax = sector number within partition
+ ; bx:0-> buffer
+ ; (_LBA) ds = ss
+ ; OUT: If unable to read,
+ ; ! jumps to error instead of returning
+ ; If sector has been read,
+ ; dx:ax = next sector number (has been incremented)
+ ; bx:0-> next buffer (bx = es+word[para_per_sector])
+ ; es = input bx
+ ; CHG: -
+ ; STT: ds = ss
+ ;
+ ; Note: If error 09h (data boundary error) is returned,
+ ; the read is done into the ldSectorSeg buffer,
+ ; then copied into the user buffer.
+read_sector:
+ clropt [bp + ldHasLBA], 2
+ jmp @F
+
+write_sector:
+ setopt [bp + ldHasLBA], 2
+@@:
+
+ push dx
+ push cx
+ push ax
+ push si
+
+ push bx
+
+; DX:AX==LBA sector number
+; add partition start (= number of hidden sectors)
+ add ax,[bp + bsBPB + bpbHiddenSectors + 0]
+ adc dx,[bp + bsBPB + bpbHiddenSectors + 2]
+
+ %if (!_LBA || !_LBA_33_BIT) && _LBA_CHECK_NO_33
+ jc .err_CY
+ %endif
+%if _LBA ; +70 bytes (with CHS, +63 bytes without CHS)
+ %if _LBA_33_BIT
+ sbb si, si ; -1 if was CY, 0 else
+ neg si ; 1 if was CY, 0 else
+ %endif
+ xor cx, cx ; cx = 0 (needed if jumping to .no_lba_checked)
+ %if !_LBA_SKIP_CHECK
+ test byte [bp + ldHasLBA], 1
+ jz .no_lba_checked
+ %endif
+ push cx
+ %if _LBA_33_BIT
+ push si ; bit 32 = 1 if operating in 33-bit space
+ %else
+ push cx ; second highest word = 0
+ %endif
+ push dx
+ push ax ; qword sector number (lpSector)
+ push bx
+ push cx ; bx:0 -> buffer (lpBuffer)
+ inc cx
+ push cx ; word number of sectors to read (lpCount)
+ mov cl, 10h
+ push cx ; word size of disk address packet (lpSize)
+ mov si, sp ; ds:si -> disk address packet (on stack)
+
+ mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
+ call .get_ah_3_write_2_read
+ or ah, 40h ; 42h extensions read or 43h extensions write
+%if _LBA_RETRY
+ call .int13_retry
+%else
+ call .int13_preserve_lpcount
+%endif
+ jnc .lba_done
+
+%if _LBA_SKIP_CHECK
+ cmp ah, 1 ; invalid function?
+ je .no_lba_skip ; try CHS instead -->
+%endif
+ cmp ah, 9 ; data boundary error?
+ jne .lba_error
+
+ testopt [bp + ldHasLBA], 2
+ jz @F
+
+ mov es, word [si + 4 + 2] ; user buffer
+ call .sectorseg_helper_write
+ mov word [si + 4 + 2], es ; => sector buffer
+ mov ah, 43h
+%if _LBA_RETRY
+ call .int13_retry
+%else
+ int 13h
+ ; (don't need .int13_preserve_lpcount as no further call)
+%endif
+ jc .lba_error
+ jmp .lba_done
+
+@@:
+ ; push word [si + 4 + 0]
+ push word [si + 4 + 2] ; user buffer
+ push word [bp + ldSectorSeg]
+ pop word [si + 4 + 2]
+ ; and word [si + 4 + 0], byte 0
+
+ mov ah, 42h
+%if _LBA_RETRY
+ call .int13_retry
+%else
+ int 13h
+ ; (don't need .int13_preserve_lpcount as no further call)
+%endif
+ jc .lba_error
+
+ pop es
+ ; pop cx
+ call .sectorseg_helper_read
+
+.lba_done:
+ add sp, 10h
+ pop bx
+ jmp short .chs_done
+
+.lba_error: equ .err
+
+ %if !_CHS
+.no_lba_skip: equ .err
+.no_lba_checked: equ .err
+ %elif _LBA_SKIP_CHECK
+.no_lba_skip:
+ ; si == sp
+ add sp, 8
+ pop ax
+ pop dx
+ %if _LBA_33_BIT
+ pop si
+ pop cx ; cx = 0 (needed as input for next cwd instruction)
+ test si, si
+ mov si, sp ; si == sp
+ %else
+ pop cx
+ pop cx
+ ; si == sp - 16
+ %endif
+ %else
+.no_lba_checked:
+ %if _LBA_33_BIT
+ test si, si
+ %endif
+ mov si, sp ; si == sp
+ %endif
+%endif
+
+%if _CHS ; +70 bytes
+ %if _LBA && _LBA_33_BIT
+ jnz .err
+ %endif
+; dx:ax = LBA sector number, (if _LBA) cx = 0
+; divide by number of sectors per track to get sector number
+; Use 32:16 DIV instead of 64:32 DIV for 8088 compatability
+; Use two-step 32:16 divide to avoid overflow
+ %if !_LBA
+ xchg cx, ax ; cx = low word of sector, clobbers ax
+ xchg ax, dx ; ax = high word of sector, clobbers dx
+ xor dx, dx ; dx:ax = high word of sector
+ %else
+ xchg cx, ax ; cx = low word of sector, ax = 0
+ push dx ; stack = high word of sector
+ cwd ; dx = 0 (because ax was 0)
+ pop ax ; ax = high word of sector
+ ; dx:ax = high word of sector
+ %endif
+ div word [bp + bsBPB + bpbCHSSectors]
+ xchg cx,ax
+ div word [bp + bsBPB + bpbCHSSectors]
+ xchg cx,dx
+
+; DX:AX=quotient, CX=remainder=sector (S) - 1
+; divide quotient by number of heads
+ xchg bx, ax ; bx = low word of quotient, clobbers ax
+ xchg ax, dx ; ax = high word of quotient, clobbers dx
+ xor dx, dx ; dx = 0
+ div word [bp + bsBPB + bpbCHSHeads]
+ ; ax = high / heads, dx = high % heads
+ xchg bx, ax ; bx = high / heads, ax = low quotient
+ div word [bp + bsBPB + bpbCHSHeads]
+
+; bx:ax=quotient=cylinder (C), dx=remainder=head (H)
+; move variables into registers for INT 13h AH=02h
+ mov dh, dl ; dh = head
+ inc cx ; cl5:0 = sector
+ xchg ch, al ; ch = cylinder 7:0, al = 0
+ shr ax, 1
+ shr ax, 1 ; al7:6 = cylinder 9:8
+ ; bx has bits set iff it's > 0, indicating a cylinder >= 65536.
+ or bl, bh ; collect set bits from bh
+ or cl, al ; cl7:6 = cylinder 9:8
+ ; ah has bits set iff it was >= 4, indicating a cylinder >= 1024.
+ or bl, ah ; collect set bits from ah
+ mov dl, [bp + bsBPB + ebpbNew + bpbnBootUnit]
+ ; dl = drive
+ jnz .err ; error if cylinder >= 1024 -->
+ ; ! bx = 0 (for 13.02 call)
+
+; we call INT 13h AH=02h once for each sector. Multi-sector reads
+; may fail if we cross a track or 64K boundary
+ pop es
+
+ call .get_ah_3_write_2_read
+ mov al, 01h ; access one sector
+
+%if _CHS_RETRY
+ call .int13_retry
+%else
+ int 13h
+%endif
+ jnc .done
+
+ cmp ah, 9 ; data boundary error?
+ jne .err
+
+ testopt [bp + ldHasLBA], 2
+ jz @F
+
+ push es ; user buffer
+ call .sectorseg_helper_write
+ mov ax, 0301h
+%if _LBA_RETRY
+ call .int13_retry
+%else
+ int 13h
+%endif
+ jc .err
+ pop es
+ jmp .done
+
+@@:
+ push es ; user buffer
+ mov es, word [bp + ldSectorSeg]
+
+ mov ax, 0201h
+%if _CHS_RETRY
+ call .int13_retry
+%else
+ int 13h
+%endif
+ jc .err
+
+ pop es
+ call .sectorseg_helper_read
+
+.done:
+; increment segment
+ mov bx, es
+%endif
+
+.chs_done:
+ mov es, bx
+ add bx, word [bp + ldParaPerSector]
+
+ pop si
+ pop ax
+ pop cx
+ pop dx
+; increment LBA sector number
+ inc ax
+ jne @F
+ inc dx
+@@:
+ retn
+
+
+%if (_LBA && _LBA_RETRY) || (_CHS && _CHS_RETRY)
+.int13_retry:
+ push ax
+%if _LBA
+ call .int13_preserve_lpcount
+%else
+ int 13h ; first try
+%endif
+ jnc @F ; NC, success on first attempt -->
+
+; reset drive
+ xor ax, ax
+ int 13h
+ jc @F ; CY, reset failed, error in ah -->
+
+; try read again
+ pop ax ; restore function number
+%if _LBA
+ call .int13_preserve_lpcount
+%else
+ int 13h ; retry, CF error status, ah error number
+%endif
+ retn
+
+@@: ; NC or CY, stack has function number
+ inc sp
+ inc sp ; discard word on stack, preserve CF
+ retn
+%endif
+
+%if _LBA
+ ; have to reset the LBAPACKET's lpCount, as the handler may
+ ; set it to "the number of blocks successfully transferred".
+
+ ; hack: si points into unclaimed stack space
+ ; when this is called from the CHS handler.
+ ; this should not cause any issues however.
+ ; actually, if !_LBA_SKIP_CHECK, then si is set
+ ; to point to claimed stack space. also legal.
+.int13_preserve_lpcount:
+ push word [si + lpCount]
+ int 13h
+ pop word [si + lpCount]
+ retn
+%endif
+
+ ; INP: word [bp + ldSectorSeg] => source buffer with read data
+ ; es => destination buffer
+ ; CHG: si, cx
+ ; OUT: ss = ds
+ ; data copied to destination buffer
+ ; STT: UP
+.sectorseg_helper_read:
+ xor si, si
+ mov ds, word [bp + ldSectorSeg]
+ push di
+ ; mov di, cx
+ xor di, di
+ mov cx, word [bp + bsBPB + bpbBytesPerSector]
+ rep movsb
+ pop di
+
+ push ss
+ pop ds
+ retn
+
+ ; INP: es => source buffer with data to read
+ ; word [bp + ldSectorSeg] => destination buffer
+ ; CHG: -
+ ; OUT: ss = ds
+ ; es => destination buffer
+ ; data copied to destination buffer
+ ; STT: UP
+.sectorseg_helper_write:
+ push es
+ pop ds
+ push si
+ push di
+ push cx
+ xor di, di
+ mov es, word [bp + ldSectorSeg]
+ xor si, si
+ mov cx, word [bp + bsBPB + bpbBytesPerSector]
+ rep movsb
+ pop cx
+ pop di
+ pop si
+
+ push ss
+ pop ds
+ retn
+
+.get_ah_3_write_2_read:
+ mov ah, 02h ; read
+ testopt [bp + ldHasLBA], 2
+ jz @F
+ mov ah, 03h ; write
+@@:
+ retn
+
+.err:
+error_diskaccess:
+ testopt [bp + ldHasLBA], 2
+ jz @F
+ call error
+ db "Disk write error.", 0
+
+@@:
+ call error
+ db "Disk read error.", 0
+
+error_badchain:
+ call error
+ db "Bad cluster chain.", 0
+
+error_badclusters:
+ call error
+ db "Bad amount of clusters.", 0
+
+error_outofmemory:
+ call error
+ db "Out of memory.", 0
+
+error_filenotfound:
+ call error
+ db "File not found.", 0
+
+
+ ; INP: dx:ax = cluster - 2 (0-based cluster)
+ ; OUT: cx:bx = input dx:ax
+ ; dx:ax = first sector of that cluster
+ ; CHG: -
+clust_to_first_sector:
+ push dx
+ push ax
+ push dx
+ mul word [bp + ldClusterSize]
+ xchg bx, ax
+ xchg cx, dx
+ pop ax
+ mul word [bp + ldClusterSize]
+ test dx, dx
+ jnz short error_badchain
+ xchg dx, ax
+ add dx, cx
+.cy_error_badchain:
+ jc short error_badchain
+ xchg ax, bx
+
+ add ax, [bp + lsvDataStart]
+ adc dx, [bp + lsvDataStart + 2]
+ jc short .cy_error_badchain
+ ; dx:ax = first sector in cluster
+ pop bx
+ pop cx ; cx:bx = cluster
+ retn
+
+
+ ; INP: cx:bx = cluster (0-based)
+ ; si:di = loaded FAT sector, -1 if none
+ ; OUT: CY if no next cluster
+ ; NC if next cluster found,
+ ; dx:ax = next cluster value (0-based)
+ ; si:di = loaded FAT sector
+ ; CHG: cx, bx
+clust_next:
+ mov ax, bx
+ mov dx, cx
+ add ax, 2
+ adc dx, 0
+
+ push es
+ cmp byte [bp + ldFATType], 16
+ je .fat16
+ ja .fat32
+
+.fat12:
+; FAT12 entries are 12 bits, bytes are 8 bits. Ratio is 3 / 2,
+; so multiply cluster number by 3 first, then divide by 2.
+ ; ax = cluster number (up to 12 bits set)
+ mov dx, ax
+ shl ax, 1 ; = 2n (up to 13 bits set)
+ add ax, dx ; = 2n+n = 3n (up to 14 bits set)
+ shr ax, 1 ; ax = byte offset into FAT (0..6129)
+ ; CF = whether to use high 12 bits
+ sbb cx, cx ; = -1 iff CY, else 0
+
+; Use the calculated byte offset as an offset into the FAT
+; buffer, which holds all of the FAT's relevant data.
+ mov es, [bp + lsvFATSeg]
+ xchg bx, ax ; bx -> 16-bit word in FAT to load
+
+; get 16 bits from FAT
+ mov ax, [es:bx]
+
+ and cl, 4 ; = 4 iff CY after shift, else 0
+ shr ax, cl ; shift down iff odd entry, else unchanged
+ and ax, 0FFFh ; insure it's only 12 bits
+ jmp short .gotvalue_zero_dx
+
+.fat32:
+ ; * 4 = byte offset into FAT (0--4000_0000h)
+ add ax, ax
+ adc dx, dx
+.fat16:
+ ; * 2 = byte offset into FAT (0--2_0000h)
+ add ax, ax
+ adc dx, dx
+
+ push ax
+ xchg ax, dx
+ xor dx, dx ; dx:ax = high word
+ div word [bp + bsBPB + bpbBytesPerSector]
+ xchg bx, ax ; bx = high word / divisor
+ pop ax ; dx = remainder, ax = low word
+ div word [bp + bsBPB + bpbBytesPerSector]
+ xchg dx, bx ; dx:ax = result, bx = remainder
+ ; dx:ax = sector offset into FAT (0--200_0000h)
+ ; bx = byte offset into FAT sector (0--8190)
+ cmp dx, si
+ jne @F ; read sector
+ cmp ax, di
+ je @FF ; sector is already buffered
+@@:
+ mov si, dx
+ mov di, ax
+ mov word [bp + lsvFATSector + 2], dx
+ mov word [bp + lsvFATSector + 0], ax
+
+ push bx
+ add ax, [bp + bsBPB + bpbReservedSectors]
+ adc dx, 0
+ mov bx, [bp + lsvFATSeg]
+ call read_sector
+ pop bx
+@@:
+ mov es, [bp + lsvFATSeg]
+ mov dx, [es:bx + 2]
+ mov ax, [es:bx] ; dx:ax = FAT32 entry
+
+ cmp byte [bp + ldFATType], 16 ; is it FAT32 ?
+ jne @F ; yes -->
+.gotvalue_zero_dx:
+ xor dx, dx ; no, clear high word
+@@:
+ pop es
+
+ ; INP: dx:ax = cluster value, 2-based
+ ; OUT: dx:ax -= 2 (makes it 0-based)
+ ; CY iff invalid cluster
+check_clust:
+ and dh, 0Fh
+ sub ax, 2
+ sbb dx, 0
+
+ cmp byte [bp + ldFATType], 16
+ ja .fat32
+ je .fat16
+
+.fat12:
+ cmp ax, 0FF7h - 2
+ jmp short .common
+
+.fat32:
+ cmp dx, 0FFFh
+ jb @F ; CY here means valid ...-
+
+.fat16:
+ cmp ax, 0FFF7h - 2
+@@: ; -... or if NC first, CY here also
+.common:
+ cmc ; NC if valid
+ retn
+
+
+ align 16, db 38
+end: