
%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"

	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
	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 ax, 100h
	je dos_com_entrypoint

	jmp 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

	times 64 * 1024 db 0

signature2:
	dw 2638h
