
; Public Domain

; test install command: install; chstool walk 80; chstool uninstall
; test run command: walk 80; .; .

%include "lmacros3.mac"

%include "eld.mac"
%include "eldcall.mac"
%include "elddata.mac"
%include "iniload.mac"

	numdef SPT_MULTIPASS, 1


	cpu 8086

	addsection RELOCATEDDATA, nobits vstart=_ELD_RELOC_VSTART
relocateddata:

	addsection HEADER, start=0

	istruc ELD_HEADERX
at eldhxHeader
		; ELD executable header
	istruc ELD_HEADER
at eldhSignature,	db "ELD1"
			db 0,0,0
			db 26
at eldhCodeOffset,	dd CODEOFFSET
at eldhCodeImageLength,	dw code_size
at eldhCodeAllocLength,	dw 0
at eldhDataOffset,	dd DATAOFFSET
at eldhDataImageLength,	dw data_size
at eldhDataAllocLength,	dw total_data_size - data_size
at eldhCodeEntrypoint,	dw linker - code
at eldhReserved
at eldhExtensionSize,	dw header_extension_end - $$
	iend
at eldhxDescriptionOffset,	dd description
at eldhxHelpOffset,		dd DATAOFFSET + msg.help - datastart
PUT_ELDHX_DATETIME_OFFSET
header_extension_end:
	iend

description:		asciz "CHS calculation tool."


	align 16, db 0

	eldstrict on

CODEOFFSET equ $ - $$
	addsection CODE, follows=HEADER vstart=_ELD_CODE_VSTART
%define CODEFIXUP - code + 0
code:
		; ELD instance header
	istruc ELD_INSTANCE
at eldiStartCode
at eldiEndCode
at eldiStartData
at eldiEndData
at eldiIdentifier,	fill 8, 32, db "CHSTOOL"
at eldiListing,		asciz _ELD_LISTING
	iend


command:
	jmp strict short .entry
.chain:
	extcall cmd3_not_ext, required	; must NOT be extcallcall
	times 10 - ($ - command) nop
.entry:
	push si
	cmp al, '-'
	jne @F
	extcallcall skipcomma
@@:
	dec si

reloc	mov dx, msg.chstool
internaldatarelocation
	extcallcall isstring?
	je .ours
	pop si
	dec si
	lodsb
	jmp .chain

.ours:
	pop ax
	extcallcall skipcomma
	dec si
reloc2	mov word [relocateddata], relocateddata
linkdatarelocation lastcmd, -4
linkdatarelocation dmycmd
reloc	mov dx, relocateddata
linkdatarelocation msg.uninstall
	extcallcall isstring?
	je uninstall

	call run
	extcallcall cmd3

uninstall:
	lodsb
	extcallcall chkeol

	call get_es_ext

	push es
	pop ds
	xor bx, bx		; = 0 (no prior, modify ext_command_handler)
reloc	mov di, command		; di -> us
internalcoderelocation
reloc	mov si, word [ss:relocateddata]
linkdatarelocation ext_command_handler
				; si -> first
	test si, si		; none installed ?
	jz .error		; error -->

.loop:
	cmp di, si		; found ?
	je .bx			; yes, use bx -->
	mov bx, si		; bx -> prior handler
	lodsw			; skip entrypoint jmp strict short
	lodsb			; get first byte of chainer
	cmp al, 0E9h		; expecting jmp near ?
	jne .error		; no, error -->
	lodsw			; get rel16 displacement
	add si, ax		; -> next handler
	jmp .loop

.bx:
	test bx, bx		; any prior ?
	jnz .bxnz		; yes -->
	scasw			; skip entrypoint jmp strict short
	cmp byte [di], 0E8h	; is it a call to cmd3_not_ext ?
	jne @F			; no -->
				; yes, reset ext_command_handler to zero
.setbx:
reloc	mov word [ss:relocateddata], bx
linkdatarelocation ext_command_handler
	jmp .done

@@:
	cmp byte [di], 0E9h	; validate
	jne .error		; failure -->
	inc di			; -> rel16 displacement
	mov bx, word [di]	; get displacement
	scasw			; -> after jmp near
	add bx, di		; -> next handler
	jmp .setbx		; set ext_command_handler to next

.bxnz:
	mov si, bx		; -> prior handler with us as downlink
	xchg di, si		; si -> ours, di -> prior
	cmpsw			; skip entrypoint jmp strict short
	movsb			; copy 0E8h/0E9h
	lodsw			; ax = near rel16 displacement
	add ax, si		; add in our base (= absolute offset)
	sub ax, di
	dec ax
	dec ax			; subtract new base (= relative displacement)
	stosw			; store new rel16 displacement
	movsw			; jmp strict short
	movsw			; linkcall target
	movsb			; trailer
.done:
reloc	clropt [code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as free
reloc	mov dx, msg.uninstall_done
internaldatarelocation
@@:
	push ss
	pop ds
	extcallcall putsz
	extcallcall cmd3	; return

.error:
	mov ax, 0E01h
	extcallcall setrc
reloc	mov dx, msg.uninstall_error
internaldatarelocation
	jmp @B


get_es_ext:
reloc	mov es, word [relocateddata]
linkdatarelocation extdssel
	extcallcall ispm
	jz @F
reloc	mov es, word [relocateddata]
linkdatarelocation extseg
@@:
	retn


overridedef DEBUG4, 0
partition_table equ load_partition_table
partition_table.end equ load_partition_table.end
%define _SCANPTAB_PREFIX
%define _SCANPTAB_DEBUG4_PREFIX
%assign _PARTITION_TABLE_IN_CS 0
%define _BASE bp
%assign _SPT_ELD 1
%include "scanptab.asm"
resetdef DEBUG4


run:
	push ss
	pop es

	extcallcall ispm
	jnz @F
	extcallcall error
@@:

	xor ax, ax
reloc	mov word [heads], ax
internaldatarelocation
reloc	mov word [sectors], ax
internaldatarelocation

reloc	mov dx, msg.walk
internaldatarelocation
	extcallcall isstring?
	je walk

reloc	mov dx, msg.geometry
internaldatarelocation
	extcallcall isstring?
	je geometry

	mov cl, 80h
.loop:
.heads:
reloc	mov dx, msg.heads
internaldatarelocation
	extcallcall isstring?
	jne .sectors
@@:
	extcallcall skipcomma
	cmp al, '='
	je @B
	extcallcall getword
	extcallcall skipcomm0

	cmp dx, 256
	ja .invalid
	test dx, dx
	jz .invalid

reloc	mov word [heads], dx
internaldatarelocation
	dec si
	jmp .loop

.sectors:
reloc	mov dx, msg.sectors
internaldatarelocation
	extcallcall isstring?
	jne .unit
@@:
	extcallcall skipcomma
	cmp al, '='
	je @B
	extcallcall getbyte
	extcallcall skipcomm0

	mov dh, 0
	cmp dx, 63
	ja .invalid
	test dx, dx
	jz .invalid

reloc	mov word [sectors], dx
internaldatarelocation
	dec si
	jmp .loop

.unit:
reloc	mov dx, msg.unit
internaldatarelocation
	extcallcall isstring?
	jne @FF
@@:
	extcallcall skipcomma
	cmp al, '='
	je @B
	extcallcall getbyte
	extcallcall skipcomm0
	mov cl, dl
	dec si
	jmp .loop

@@:
	lodsb
	extcallcall getdword
	extcallcall chkeol

.entry_bxdx_cl:
	push bx
	push dx

	xor ax, ax
reloc	cmp word [heads], ax
internaldatarelocation
	je @F
reloc	cmp word [sectors], ax
internaldatarelocation
	jne .no_query

@@:
	mov ah, 08h
	mov dl, cl
	stc
	int 13h
	push ss
	pop es
	jc .invalid_pop_2
	and cx, 3Fh
	jz .invalid_pop_2

reloc	cmp word [sectors], strict byte 0
internaldatarelocation -3
	jne @F
reloc	mov word [sectors], cx
internaldatarelocation
@@:
	mov cl, dh
	inc cx
reloc	cmp word [heads], strict byte 0
internaldatarelocation -3
	jne @F
reloc	mov word [heads], cx
internaldatarelocation
@@:

.no_query:
	pop bp
	pop si			; si:bp = tuple
				; (sil67:sih = C, bph = H, sil05 = S)

reloc	mov di, relocateddata
linkdatarelocation line_out

	mov ax, "CH"
	stosw
	mov ax, "S "
	stosw

	mov ax, si		; al67:ah = C
	xchg al, ah		; ah67:al = C
	mov cl, 6
	shr ah, cl		; ax = C

	extcallcall hexword
	mov byte [di], ':'
	inc di

reloc	mul word [heads]
internaldatarelocation
	mov cx, bp		; ch = H
	xchg cl, ch
	mov ch, 0		; cx = H
	xchg ax, cx		; ax = H

	extcallcall hexbyte
	mov byte [di], ':'
	inc di

	add ax, cx
	adc dx, 0

	xchg ax, bx		; bx = low word
	xchg ax, dx		; ax = high word
reloc	mul word [sectors]	; dx:ax = ax = high word result
internaldatarelocation
	xchg ax, bx		; ax = low word, bx = high word result
reloc	mul word [sectors]	; dx:ax = low word result plus overflow
internaldatarelocation
	add dx, bx		; dx:ax = result

	mov cx, si		; cl05 = S
	and cx, 3Fh		; cx = S
	dec cx			; 1-based
	js .invalid

	xchg ax, cx		; ax = S

	extcallcall hexbyte
	mov word [di], " ="
	mov word [di + 2], " L"
	mov word [di + 4], "BA"
	mov byte [di + 6], " "
	add di, 7

	add ax, cx
	adc dx, 0

	call store_lba
	extcallcall putsline_crlf
	retn


store_lba: equ $
	xchg ax, dx
	extcallcall hexword
	mov byte [di], '_'
	inc di
	xchg dx, ax
	extcallcall hexword

	mov word [di], " ="
	scasw			; di += 2
	mov cx, 512
	mov bx, 9
	extcallcall disp_dxax_times_cx_width_bx_size.store
reloc	xor byte [relocateddata + 1], 10h
linkdatarelocation options, -3
	extcallcall disp_dxax_times_cx_width_bx_size.store
reloc	xor byte [relocateddata + 1], 10h
linkdatarelocation options, -3
	retn


.invalid_pop_2:
	pop ax
	pop ax
.invalid:
reloc	mov dx, msg.invalid
internaldatarelocation
	extcallcall putsz
	mov ax, 0E65h
	extcallcall setrc
	retn

walk:
	lodsb
	extcallcall getbyte
reloc	mov byte [load_unit], dl
internaldatarelocation
reloc	mov byte [upto], dl
internaldatarelocation

reloc	mov di, zeroing
internaldatarelocation
	xor ax, ax
	mov cx, words(zeroing_end - zeroing)	; may overflow one byte
	rep stosw
	dec si
.parse:
	extcallcall skipcomma
	dec si
reloc	mov bx, keywordtable
internaldatarelocation
.parse_loop:
	mov dx, [bx]
	extcallcall isstring?
	jne @F
	mov bx, [bx + 2]
	mov byte [bx], -1
	jmp .parse

@@:
	add bx, 4
reloc	cmp bx, strict word keywordtable.end
internaldatarelocation
	jb .parse_loop

reloc	mov dx, msg.mode
internaldatarelocation
	extcallcall isstring?
	jne .notmode
@@:
	extcallcall skipcomma
	cmp al, '='
	je @B
	dec si
reloc	mov bx, modetable
internaldatarelocation
.mode_loop:
	mov dx, [bx]
	extcallcall isstring?
	jne @F
	mov bx, [bx + 2]
reloc	mov word [mode], bx
internaldatarelocation
	jmp .parse

@@:
	add bx, 4
reloc	cmp bx, strict word modetable.end
internaldatarelocation
	jb .mode_loop

	lodsb
	mov bl, al
	cmp al, '+'
	je @F
	cmp al, '-'
	je @F
	mov bl, 0
	dec si
@@:
	lodsb
	call getword
	dec si
	cmp bl, '-'
	je .modeclear
	cmp bl, '+'
	je .modeset
.modereplace:
reloc	mov word [mode], dx
internaldatarelocation
	jmp .parse

.modeset:
reloc	or word [mode], dx
internaldatarelocation
	jmp .parse

.modeclear:
	not dx
reloc	and word [mode], dx
internaldatarelocation
	jmp .parse

.notmode:
reloc	mov dx, msg.upto
internaldatarelocation
	extcallcall isstring?
	jne .notupto
@@:
	extcallcall skipcomma
	cmp al, '='
	je @B
	call getbyte
reloc	mov byte [upto], dl
internaldatarelocation
	dec si
	jmp .parse

.notupto:
	lodsb
	extcallcall chkeol

	mov ax, ss
reloc	mov bx, buffer + 512 - 16
internaldatarelocation
	mov cl, 4
	shr bx, cl
	add ax, bx
	and ax, ~ (paras(512) - 1)
reloc	mov word [buffersegment], ax
internaldatarelocation

%if _SPT_MULTIPASS
reloc	testopt [load_mode], mode_dlasort_zero
internaldatarelocation -3
	jz .once

reloc	mov bx, scan_priority_primary
internalcoderelocation
	call .pass
reloc	mov bx, scan_early_primary
internalcoderelocation
	call .pass
reloc	mov bx, scan_logical
internalcoderelocation
	call .pass
reloc	mov bx, scan_subsequent_primary
internalcoderelocation
	call .pass
	retn


.pass:
reloc	push word [load_unit]
internaldatarelocation
.passloop:
	xor ax, ax
reloc	mov word [load_partition_cycle], ax
internaldatarelocation
	push bx
	call query_geometry
	pop bx

	push bx
reloc	mov cx, .each
internalcoderelocation
	call bx
	pop bx

reloc	mov al, [upto]
internaldatarelocation
reloc	cmp al, [load_unit]
internaldatarelocation
	jbe .passdone
reloc	inc byte [load_unit]
internaldatarelocation
	jmp .passloop

.passdone:
reloc	pop word [load_unit]
internaldatarelocation
	retn


.once:
.loop:
	xor ax, ax
reloc	mov word [load_partition_cycle], ax
internaldatarelocation

	call query_geometry

reloc	mov cx, .each
internalcoderelocation
	call scan_priority_primary
reloc	mov cx, .each
internalcoderelocation
	call scan_early_primary
reloc	mov cx, .each
internalcoderelocation
	call scan_logical
reloc	mov cx, .each
internalcoderelocation
	call scan_subsequent_primary
%else
.loop:
	xor ax, ax
reloc	mov word [load_partition_cycle], ax
internaldatarelocation

	call query_geometry

reloc	mov cx, .each
internalcoderelocation
	call scan_partitions
%endif

reloc	mov al, [upto]
internaldatarelocation
reloc	cmp al, [load_unit]
internaldatarelocation
	jbe .ret
reloc	inc byte [load_unit]
internaldatarelocation
	jmp .loop

.ret:
	retn


		; INP:	es:si -> partition table entry,
		;	 si = load_partition_table .. load_partition_table+48,
		;	 es = ss
		;	bp + di -> above part table metadata,
		;	 dwo [bp + di - 4] = root (outermost extended position)
		;	 dwo [bp + di - 8] = base (current table position)
		; CHG:	ax, bx, (cx), dx
.each:
	houdini

	mov ax, [bp + di - 8]
	mov dx, [bp + di - 6]		; base (current table position)

	add ax, [es:si + 8]
	adc dx, [es:si + 8 + 2]		; add offset to logical partition

	push cx
	push di
	push bp

	push dx
	push ax

	push si

	push word [es:si + 12 + 2]
	push word [es:si + 12]

	push ax

reloc	mov di, relocateddata
linkdatarelocation line_out

	mov al, 'T'
	stosb

	mov al, byte [es:si + piType]

reloc	rol byte [skipext], 1
internaldatarelocation
	jnc .nocheckext
.skipext:
	cmp al, 5
	je .discard
	cmp al, 15
	je .discard
	cmp al, 85h
	je .discard
.nocheckext:

%if _MODE
	call load_is_known
	jnc @F
%else
	jmp @F
%endif

.discard:
	add sp, 6
	pop si
	pop ax
	pop dx
	pop bp
	pop di
	pop cx
	retn

@@:
%if _MODE
	jnz @F
sptrel	setopt [ %[_SCANPTAB_PREFIX]load_scan_flags ], 32
sptinternal -3			; a logical or primary partition found

@@:
%endif

	extcallcall hexbyte
	pop ax

reloc	mov si, msg.startlba
internaldatarelocation
	extcallcall copy_single_counted_string
	call store_lba

	pop ax
	pop dx

reloc	mov si, msg.sizelba
internaldatarelocation
	extcallcall copy_single_counted_string
	call store_lba

	extcallcall putsline_crlf

reloc	rol byte [quiet], 1
internaldatarelocation
	jc .quiet

reloc	mov dx, msg.startchs
internaldatarelocation
	extcallcall putsz

	pop si
	push si
	mov bx, word [es:si + 2]
	mov dx, word [es:si]
reloc	mov cl, byte [load_unit]
internaldatarelocation
	call run.entry_bxdx_cl

reloc	mov dx, msg.endchs
internaldatarelocation
	extcallcall putsz

	pop si
	push si
	mov bx, word [es:si + 6]
	mov dx, word [es:si + 4]
	 push es
reloc	mov cl, byte [load_unit]
internaldatarelocation
	call run.entry_bxdx_cl
	 pop es

.quiet:
	pop si
	 pop ax
	 pop dx
	push si

reloc	rol byte [label], 1
internaldatarelocation
	jnc .nolabel

reloc	mov di, relocateddata
linkdatarelocation line_out	; reset di

	mov bl, byte [es:si + piType]
	cmp bl, ptFAT12
	je .isfat
	cmp bl, ptFAT16_16BIT_CHS
	je .isfat
	cmp bl, ptFAT16_CHS
	je .isfat
	cmp bl, ptFAT32_CHS
	je .isfat
	cmp bl, ptFAT32
	je .isfat
	cmp bl, ptFAT16
	jne .notfat
.isfat:

reloc	mov bx, word [buffersegment]
internaldatarelocation
	call read_ae_512_bytes_no_dump

	cmp word [es:510], 0AA55h
	jne .nolabel
	cmp word [es:bsBPB + bpbBytesPerSector], 0
	je .nolabel
	mov si, bsBPB + bpbNew + bpbnVolumeLabel
	cmp word [es:bsBPB + bpbSectorsPerFAT], 0
	jne @F
	mov si, bsBPB + ebpbNew + bpbnVolumeLabel
@@:
	cmp byte [es:si - bpbnVolumeLabel + bpbnExtBPBSignature], 29h
	jne .nolabel
	mov cx, 11

	push es
	pop ds
	push ss
	pop es

	mov al, 32
	stosb
@@:
	lodsb
	test al, al
	jz @F
	call dotreplace
	stosb
	loop @B
	inc cx			; = 1
@@:
	dec cx			; = how many bytes remain
	add si, cx		; -> behind label

	mov cl, 8
	mov al, 32
	stosb
@@:
	lodsb
	test al, al
	jz @F
	call dotreplace
	stosb
	loop @B
@@:

	push ss
	pop ds
	push ss
	pop es

	mov ax, " U"
	stosw

reloc	mov al, byte [load_unit]
internaldatarelocation
	extcallcall hexbyte

	mov ax, " P"
	stosw

reloc	mov al, byte [load_current_partition]
internaldatarelocation
	extcallcall hexbyte

	call putsline_crlf
.notfat:
.nolabel:

	push ss
	pop es

	pop si
	pop bp
	pop di
	pop cx
	retn


dotreplace:
	inc al			; change 7Fh to 80h, 32 to 33
	cmp al, 32		; printable ?
	jg .keep		; yes -->
	mov al, '.' + 1		; was 7Fh to FFh or 00h to 1Fh
.keep:
	dec ax			; cannot underflow into ah
	retn


%if _MODE
		; INP:	al = partition type
		; OUT:	CY if an LBA or FAT32 type that's disabled
		;	NC, ZR if known type we can access
		;	NC, NZ if unknown type
load_is_known:
	cmp al, ptFAT12
	je .isknown
	cmp al, ptFAT16_16BIT_CHS
	je .isknown
	cmp al, ptFAT16_CHS
	je .isknown
	cmp al, ptFAT32_CHS
	je .isknown
	cmp al, ptFAT32
	je .isknown_lba
	cmp al, ptFAT16
	jne .notknown
.isknown_lba:
sptrel	testopt [ %[_SCANPTAB_PREFIX]load_mode ], mode_disable_lba
sptinternal -3
	jz .isknown
.discard:
	stc
	retn

.isknown:
	cmp al, ptFAT32
	je @F
	cmp al, ptFAT32_CHS
@@:
	jne @F
sptrel	testopt [ %[_SCANPTAB_PREFIX]load_mode ], mode_disable_fat32
sptinternal -3
	jnz .discard
@@:
	cmp al, al		; ZR
.notknown:
	clc
	retn
%endif


geometry:
	lodsb
	extcallcall getbyte
reloc	mov byte [load_unit], dl
internaldatarelocation
	extcallcall chkeol

	mov ax, ss
reloc	mov bx, buffer + 512 - 16
internaldatarelocation
	mov cl, 4
	shr bx, cl
	add ax, bx
	and ax, ~ (paras(512) - 1)
reloc	mov word [buffersegment], ax
internaldatarelocation

	houdini

	call query_geometry

	push ss
	pop es

reloc	mov di, relocateddata
linkdatarelocation line_out

reloc	mov ax, [load_heads]
internaldatarelocation
	extcallcall hexword
	mov word [di], " ="
	scasw
	mov byte [di], " "
	inc di
	extcallcall decword

	mov ax, " H"
	stosw
	mov ah, al
	stosw

reloc	mov ax, [load_sectors]
internaldatarelocation
	extcallcall hexword
	mov word [di], " ="
	scasw
	mov byte [di], " "
	inc di
	extcallcall decword

	mov ax, " S"
	stosw
	extcallcall putsline_crlf
	retn


bootcmd:
.fail_read:
	 push ss
	 pop es
reloc	mov di, msg.bootfail_read_errorcode
internaldatarelocation
	mov al, ah
	mov ah, 04h
	extcallcall setrc
	extcallcall hexbyte
reloc	mov dx, msg.bootfail_read
internaldatarelocation

.fail:
	 push ss
	 pop es
	push dx
reloc	mov dx, msg.bootfail
internaldatarelocation
	extcallcall putsz
	pop dx
	extcallcall putsz
	mov ax, 02FFh
	extcallcall setrc

	extcallcall cmd3


%idefine d5 comment
%idefine d4 comment

query_geometry:
reloc	mov dl, [load_unit]
internaldatarelocation
;	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
reloc	mov [load_heads], cx
internaldatarelocation
reloc	mov [load_sectors], cx
internaldatarelocation
	xor bx, bx
	mov bl, dl
%if 1
reloc	testopt [word relocateddata + bx], lufForceGeometry
linkdatarelocation load_unit_flags, -3
	jnz .try_bootsector
%endif
		; Note:	The int 13h function 08h call may change or
		;	 set ax, bx, cx, dx, es, di. es is left as
		;	 indeterminate afterwards, until the code at
		;	 .try_bootsector runs, or bootcmd.fail, or
		;	 the "mov es" in .got_sectors_heads.
	stc			; initialise to CY
	call .int13_retry	; query drive geometry
	jc .try_bootsector	; apparently failed -->
	mov dl, dh
	mov dh, 0		; dx = maximum head number
	inc dx			; dx = number of heads (H is 0-based)
	mov ax, cx		; ax & 3Fh = maximum sector number
	and ax, 3Fh		; get sectors (number of sectors, S is 1-based)
	jnz .got_sectors_heads	; valid (S is 1-based), use these -->
				; zero = invalid
.try_bootsector:
reloc	mov es, word [buffersegment]	; es => buffer
internaldatarelocation
	xor bx, bx			; es:bx -> buffer
	mov ax, 0201h			; read sector, 1 sector
	mov cx, 1			; sector 1 (1-based!), cylinder 0 (0-based)
	mov dh, 0			; head 0 (0-based)
reloc	mov dl, [load_unit]
internaldatarelocation
	stc
	call .int13_retry
	jc .access_error

		; note: the smallest supported sector size, 32 bytes,
		;  does contain these entries (offset 18h and 1Ah in sector)
		;  within the first BPB sector.
	mov ax, word [es:bx + bsBPB + bpbCHSSectors]
	mov dx, word [es:bx + bsBPB + bpbCHSHeads]

.got_sectors_heads:
reloc	mov word [load_sectors], ax
internaldatarelocation
reloc	mov word [load_heads], dx
internaldatarelocation

	test ax, ax
	jz .invalid_sectors
	cmp ax, 63
	ja .invalid_sectors
	test dx, dx
	jz .invalid_heads
	cmp dx, 100h
	ja .invalid_heads

reloc	mov es, word [buffersegment]	; es => buffer
internaldatarelocation
	xor bx, bx			; es:bx -> buffer
	xor ax, ax

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 -> buffer, es:di = same
	rep stosw			; fill buffer, di -> behind (buffer+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)
reloc	mov dl, [load_unit]
internaldatarelocation
	stc
	call .int13_retry
	jc .access_error

	std				; _AMD_ERRATUM_109_WORKAROUND does not apply
	scasw				; -> buffer+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
reloc	mov dl, [load_unit]
internaldatarelocation
	stc
	call .int13_retry
	jc .access_error

	std				; _AMD_ERRATUM_109_WORKAROUND does not apply
	scasw				; di -> buffer+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:
reloc	mov word [load_sectorsize], ax
internaldatarelocation
	mov cl, 4
	shr ax, cl
reloc	mov word [load_sectorsizepara], ax
internaldatarelocation

reloc	mov byte [load_flags], 0
internaldatarelocation -3
reloc	mov dl, [load_unit]
internaldatarelocation
	xor bx, bx
	mov bl, dl
%if 1
reloc	testopt [word relocateddata + bx], lufForceCHS
linkdatarelocation load_unit_flags, -3
	jnz .no_lba
reloc	testopt [word relocateddata + bx], lufForceLBA
linkdatarelocation load_unit_flags, -3
	jnz .yes_lba
%endif
	mov ah, 41h
	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

.yes_lba:
%if ldfHasLBA != 1
 %error Assuming ldfHasLBA is 1
%endif
reloc	inc byte [load_flags]
internaldatarelocation
.no_lba:

	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		; CF
	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


.out_of_memory_error:
reloc	mov dx, msg.boot_out_of_memory_error
internaldatarelocation
	mov al, 21h
	jmp .error_common_j
.access_error:
reloc	mov dx, msg.boot_access_error
internaldatarelocation
	mov al, 22h
	jmp .error_common_j
.sector_too_large:
reloc	mov dx, msg.boot_sector_too_large
internaldatarelocation
	mov al, 23h
	jmp .error_common_j
.sector_too_small:
reloc	mov dx, msg.boot_sector_too_small
internaldatarelocation
	mov al, 24h
	jmp .error_common_j
.sector_not_power:
reloc	mov dx, msg.boot_sector_not_power
internaldatarelocation
	mov al, 25h
	jmp .error_common_j
.invalid_sectors:
reloc	mov dx, msg.boot_invalid_sectors
internaldatarelocation
	mov al, 26h
	jmp .error_common_j
.invalid_heads:
reloc	mov dx, msg.boot_invalid_heads
internaldatarelocation
	mov al, 27h
.error_common_j:
	mov ah, 02h
	extcallcall setrc
	jmp bootcmd.fail


read_ae_512_bytes_no_dump:
	push cx
	push bx
	mov cx, 512
@@:
	call read_sector
reloc	sub cx, word [load_sectorsize]
internaldatarelocation
	ja @B
	pop es
	pop cx
	retn


		; INP:	dx:ax = first sector
		;	bx:0 -> buffer
		;	NC if loading table the first time
		;	CY if reloading table
		; OUT:	dx:ax = sector number after last read
		;	es = input bx
		;	bx:0 -> buffer after last written
		; CHG:	-
		; STT:	ds = ss
read_ae_512_bytes:
	push cx
	push bx
	pushf
	mov cx, 512
@@:
	call read_sector
reloc	sub cx, word [load_sectorsize]
internaldatarelocation
	ja @B
	popf
	pop ds
	 push ss
	 pop es
	push si
	push di
	push dx
	push bx
	push ax
	push ds

	jnc @F				; first time -->
reloc	rol byte [ss:dumpreload], 1	; want to dump on reload ?
internaldatarelocation
	jnc .skip			; no -->
@@:
reloc	rol byte [ss:dump], 1		; want to dump at all ?
internaldatarelocation
	jnc .skip			; no -->

	mov si, 512 - 2 - 16 * 4
	mov cx, 4
.loop:
	cmp byte [si + piType], ptEmpty
	je .next
reloc	mov di, relocateddata
linkdatarelocation line_out
	push si
	mov ax, si
	extcallcall hexword
	mov ch, 2
.innerloopchs:
	mov ax, "  "
	stosw
	lodsb
	extcallcall hexbyte	; piBoot or piType
	mov al, " "
	stosb
	lodsb			; low byte tuple
	push ax
	lodsw
	extcallcall hexword	; high and middle byte
	pop ax
	extcallcall hexbyte	; low byte
	dec ch
	jnz .innerloopchs	; do twice, for start and end tuple
	mov ch, 2
.innerlooplba:
	mov ax, "  "
	stosw
	lodsw			; low word
	push ax
	lodsw			; high word
	extcallcall hexword	; high word
	mov al, '_'		; separator
	stosb
	pop ax
	extcallcall hexword	; low word
	dec ch
	jnz .innerlooplba
	push ds
	 push ss
	 pop ds
	 push cx
	extcallcall putsline_crlf
	 pop cx
	pop ds
	pop si
.next:
	add si, 16		; skip
	loop .loop		; four times

.skip:
	pop es
	pop ax
	pop bx
	pop dx
	pop di
	pop si
	 push ss
	 pop ds
	pop cx
	retn


		; Read a sector using Int13.02 or Int13.42
		;
		; INP:	dx:ax = sector number (within partition)
		;	bx:0-> buffer
		;	(_LBA) ds = ss
		;	dword[load_data - LOADDATA2 + bsBPB + bpbHiddenSectors]
		;	 = base sector number (dx:ax is added to this to get
		;	    the absolute sector number in the selected unit.)
		; 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[load_sectorsizepara])
		;	 es = input bx
		; CHG:	-
		;
		; Note:	If error 09h (data boundary error) is returned,
		;	 the read is done into the load_sectorseg buffer,
		;	 then copied into the user buffer.
read_sector:
	lframe near
	lenter

.err: equ bootcmd.fail_read
d5	call d5dumpregs
d5	call d5message
d5	asciz 13,10,"In read_sector",13,10

	push dx
	push cx
	push ax
	push si

	mov es, bx

; DX:AX==LBA sector number
; add partition start (= number of hidden sectors)
reloc		add ax,[load_hidden_sectors + 0]
internaldatarelocation
reloc		adc dx,[load_hidden_sectors + 2]
internaldatarelocation

	sbb si, si	; -1 if was CY, 0 else
	neg si		; 1 if was CY, 0 else
	xor cx, cx
	push cx
	push si		; bit 32 = 1 if operating in 33-bit space
	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)

reloc	test byte [load_flags], ldfHasLBA
internaldatarelocation -3
	jz .no_lba

d5	call d5message
d5	asciz "In read_sector.lba",13,10

reloc	mov dl, byte [load_unit]
internaldatarelocation
	call .set_ah_function_42_or_43
	int 13h		; 13.42 extensions read
	jnc .lba_done

	xor ax, ax
	int 13h
	jc .lba_error

		; 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 + lpCount], 1

	call .set_ah_function_42_or_43
	int 13h
	jnc .lba_done

	jmp .lba_error

.lba_done:
	add sp, 10h
	jmp .done

.lba_error: equ .err

.no_lba:
	add sp, 8
	pop ax
	pop dx
	pop si
	pop cx
	test si, si
	jnz .err_4_NZ

; DX:AX=LBA sector number
; 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
			mov cx,ax
			mov ax,dx
			xor dx,dx
reloc			div word [load_sectors]
internaldatarelocation
			xchg cx,ax
reloc			div word [load_sectors]
internaldatarelocation
			xchg cx,dx

; DX:AX=quotient, CX=remainder=sector (S) - 1
; divide quotient by number of heads
			mov bx, ax
			xchg ax, dx
			xor dx, dx
reloc			div word [load_heads]
internaldatarelocation
			xchg bx, ax
reloc			div word [load_heads]
internaldatarelocation

; 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
reloc			mov dl, [load_unit]
internaldatarelocation
					; dl = drive
.err_4_NZ:
			mov ah, 04h	; error number: sector not found
			 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
			mov si, 16 + 1
.loop_chs_retry_repeat:
			call .set_ax_function_0201_or_0301
			int 13h		; read one sector
			jnc .done
			push ax
			xor ax, ax
			int 13h		; reset disk
			pop ax
			dec si		; another attempt ?
			jnz .loop_chs_retry_repeat	; yes -->

	jmp .err

.done:
; increment segment
	mov bx, es
reloc	add bx, word [load_sectorsizepara]
internaldatarelocation

	pop si
	pop ax
	pop cx
	pop dx
; increment LBA sector number
	inc ax
	jne @F
	inc dx
@@:
	lleave code
	retn

.set_ah_function_42_or_43:
	mov ah, 42h
	retn

.set_ax_function_0201_or_0301:
	mov al, 1
.set_ah_function_02_or_03:
	mov ah, 02h
	retn

	lleave ctx


	eldcall_dump_callcall ELDCALL_CALLCALL_LIST

endinstalled equ ($ + CODEFIXUP + 15) & ~15


start:
	mov bx, es
	 push ss
	 pop es
	call skipcomma
	dec si
reloc	mov dx, relocateddata
linkdatarelocation msg.install
	call isstring?
	je install
reloc	mov dx, msg.keyword_help
internaldatarelocation
	call isstring?
	je help
	call run
@@:
	call uninstall_oneshot
	xor ax, ax
	retf

help:
	lodsb
	call chkeol
reloc	mov dx, msg.help
internaldatarelocation
	call putsz
	jmp @B


uninstall_oneshot:
reloc	testopt [ss:relocateddata], 1
linkdatarelocation options7, -3
	jnz @F

reloc	mov ax, word [cs:code + eldiEndCode]
internalcoderelocation
reloc	sub ax, word [cs:code + eldiStartCode]
internalcoderelocation
reloc	sub word [relocateddata], ax
linkdatarelocation extseg_used

reloc	mov ax, word [cs:code + eldiEndData]
internalcoderelocation
reloc	sub ax, word [cs:code + eldiStartData]
internalcoderelocation
reloc	sub word [relocateddata], ax
linkdatarelocation extdata_used
@@:
	retn


DATAOFFSET equ CODEOFFSET + code_size
	addsection DATA, follows=CODE vstart=_ELD_DATA_VSTART
%define DATAFIXUP - datastart + 0
datastart:
PUT_ELD_DATETIME

	align 4, db 0
load_hidden_sectors:	dd 0
keywordtable:
reloc	dw msg.extended
internaldatarelocation
reloc	dw extended
internaldatarelocation

reloc	dw msg.dump
internaldatarelocation
reloc	dw dump
internaldatarelocation

reloc	dw msg.dumpreload
internaldatarelocation
reloc	dw dumpreload
internaldatarelocation

reloc	dw msg.label
internaldatarelocation
reloc	dw label
internaldatarelocation

reloc	dw msg.quiet
internaldatarelocation
reloc	dw quiet
internaldatarelocation

reloc	dw msg.skipext
internaldatarelocation
reloc	dw skipext
internaldatarelocation
.end:

modetable:
reloc	dw msg.ldos2024
internaldatarelocation
	dw mode_first_ext5 | mode_first_ext15 | mode_ignore_ext85 \
		| mode_ignore_subsequent_nested \
		| mode_ignore_subsequent_logical \
		| mode_ignore_subsequent_primary \
		| mode_traditional_order_logical \
		| mode_disable_fat32 \
		| mode_dlasort_zero
reloc	dw msg.ldosnew
internaldatarelocation
	dw mode_all_ext5 | mode_all_ext15 | mode_all_ext85 \
		| mode_disable_fat32
reloc	dw msg.freedos
internaldatarelocation
	dw mode_first_ext5 | mode_first_ext15 | mode_ignore_ext85 \
		| mode_ignore_subsequent_nested \
		| mode_traditional_order_logical \
		| mode_active_priority \
		| mode_subsequent_primary_after \
		| mode_dlasort_zero
reloc	dw msg.freedosdla
internaldatarelocation
	dw mode_first_ext5 | mode_first_ext15 | mode_ignore_ext85 \
		| mode_ignore_subsequent_nested \
		| mode_traditional_order_logical \
		| mode_active_priority \
		| mode_subsequent_primary_after
reloc	dw msg.edrdos
internaldatarelocation
	dw mode_first_ext5 | mode_first_ext15 | mode_ignore_ext85 \
		| mode_ignore_subsequent_nested \
		| mode_traditional_order_logical \
		| mode_dlasort_zero
reloc	dw msg.msdos5
internaldatarelocation
	dw mode_first_ext5 | mode_ignore_ext15 | mode_ignore_ext85 \
		| mode_ignore_subsequent_nested \
		| mode_ignore_subsequent_logical \
		| mode_traditional_order_logical \
		| mode_active_priority \
		| mode_subsequent_primary_after \
		| mode_disable_fat32 \
		| mode_disable_lba \
		| mode_dlasort_zero
reloc	dw msg.msdos7
internaldatarelocation
	dw mode_first_ext5 | mode_first_ext15 | mode_ignore_ext85 \
		| mode_ignore_subsequent_nested \
		| mode_ignore_subsequent_logical \
		| mode_traditional_order_logical \
		| mode_active_priority \
		| mode_subsequent_primary_after \
		| mode_dlasort_zero
.end:

msg:
.walk:			asciz "WALK"
.dump:			asciz "DUMP"
.dumpreload:		asciz "DUMPRELOAD"
.label:			asciz "LABEL"
.quiet:			asciz "QUIET"
.skipext:		asciz "SKIPEXT"
.upto:			asciz "UPTO"
.mode:			asciz "MODE"
.ldos2024:		asciz "LDOS2024"
.ldosnew:		asciz "LDOSNEW"
.freedos:		asciz "FREEDOS"
.freedosdla:		asciz "FREEDOSDLA"
.edrdos:		asciz "EDRDOS"
.msdos5:		asciz "MSDOS5"
.msdos7:		asciz "MSDOS7"
.geometry:		asciz "GEOMETRY"
.unit:			asciz "UNIT"
.heads:			asciz "HEADS"
.sectors:		asciz "SECTORS"
.extended:		asciz "EXTENDED"

.startlba:		counted " Start LBA "
.sizelba:		counted ", size "
.startchs:		asciz "Start "
.endchs:		asciz "End   "
.invalid:		asciz "Invalid geometry!",13,10

.bootfail:	asciz "Boot failure: "
.bootfail_read:	db "Reading sector failed (error "
.bootfail_read_errorcode:	asciz "__h).",13,10
.boot_out_of_memory_error:	asciz "Out of memory.", 13,10
.boot_access_error:	asciz "Read error.", 13,10
.boot_sector_too_large:	asciz "Sector size too small (< 32 bytes).", 13,10
.boot_sector_too_small:	asciz "Sector size too large (> 8192 bytes).", 13,10
.boot_sector_not_power:	asciz "Sector size not a power of two.", 13,10
.boot_invalid_sectors:	asciz "Invalid geometry sectors.", 13,10
.boot_invalid_heads:	asciz "Invalid geometry heads.", 13,10

.bootfail_sig_parttable:	ascii "Partition table signature missing"
				asciz " (is not AA55h).",13,10
.boot_too_many_partitions_error:asciz "Too many partitions (or a loop).",13,10
.boot_partition_cycle_error:	asciz "Partition table cycle detected.",13,10
.chstool:		asciz "CHSTOOL"
.uninstall_done:	db "CHSTOOL uninstalled."
%if _ELD_RECLAIM_HINT
			db " (Don't forget to use reclaim.eld)"
%endif
			asciz 13,10
.uninstall_error:	asciz "CHSTOOL unable to uninstall!",13,10

uinit_data: equ $
.installed:		asciz "CHSTOOL installed.",13,10
.keyword_help:		asciz "HELP"
.help:		db "This ELD can be installed residently or used transiently.",13,10
		db 13,10
		asciz

	align 16, db 0
init_data_end:
data_size equ $ - datastart
transient_data_size equ data_size

	absolute uinit_data

	alignb 16
buffer:
		resb 8192 + 512

load_partition_table:
		resb 16 * 4
.end:

	alignb 4
load_heads:
heads:		resw 1
load_sectors:
sectors:	resw 1

buffersegment:	resw 1
load_partition_cycle:
		resw 1
load_sectorsize:resw 1
load_sectorsizepara:
		resw 1

load_unit:	resb 1
		resb 1	; placeholder to allow writing a word with pop
load_flags:	resb 1
upto:		resb 1
load_current_partition:
		resb 1
	alignb 2
zeroing:
load_mode:
mode:		resw 1
load_scan_flags:resw 1
extended:	resb 1
dump:		resb 1
dumpreload:	resb 1
label:		resb 1
quiet:		resb 1
skipext:	resb 1
zeroing_end:		; may overflow one byte
load_current_extended_and_is:
load_current_extended:
		resb 1
load_is_extended:
		resb 1


	alignb 16
uinit_data_end:
resident_data_end:
resident_data_size equ resident_data_end - datastart

%if uinit_data_end >= init_data_end
 total_data_size equ $ - datastart
%else
 total_data_size equ init_data_end - datastart
%endif
%assign _DATA_SIZE total_data_size


	usesection CODE

install:
	lodsb
	extcall chkeol

	houdini
	mov es, bx		; => ext seg (writable)

	mov ax, endresident - endinstalled
reloc	sub word [es:code + eldiEndCode], ax
internalcoderelocation		; adjust size
reloc	sub word [relocateddata], ax
linkdatarelocation extseg_used	; adjust size

%if (transient_data_size - resident_data_size) > 0
	mov ax, transient_data_size - resident_data_size
reloc	sub word [es:code + eldiEndData], ax
internalcoderelocation		; adjust size
reloc	sub word [relocateddata], ax
linkdatarelocation extdata_used	; adjust size
%endif

reloc	mov bx, word [relocateddata]
linkdatarelocation ext_command_handler
				; -> prior
reloc	mov di, command		; -> our handler
internalcoderelocation
	test bx, bx		; installing as first ?
	jz .only_first		; yes, simple --> (leave as extcall cmd3_not_ext)
	scasw			; skip entrypoint jmp strict short
	mov al, 0E9h		; = jmp near opcode
	stosb			; store
	xchg ax, bx		; ax -> next handler
	sub ax, di
	dec ax
	dec ax			; ax = ax - (di + 2)
	stosw			; store our downlink as rel16 displacement

.only_first:
reloc	setopt [es:code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as resident
reloc2	mov word [relocateddata], command
linkdatarelocation ext_command_handler, -4
internalcoderelocation		; -> our entrypoint

reloc	testopt [relocateddata], 4
linkdatarelocation options7, -3
	jnz @F
reloc	mov dx, msg.installed
internaldatarelocation
	call putsz
@@:
	xor ax, ax
	retf


%include "eldlink.asm"

	align 16
code_size equ $ - code
