
; Public Domain

; test install command: install; .; useparat uninstall
; test run command: ; .; .

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

	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
header_extension_end:
	iend

description:		asciz "Put separators in disassembly on control flow end."


	align 16, db 0

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

	mov dx, msg.useparat
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
	mov dx, relocateddata
linkdatarelocation msg.uninstall
	extcallcall isstring?
	je uninstall
	mov dx, msg.width
internaldatarelocation
	extcallcall isstring?
	je setwidth
	lodsb
	extcallcall chkeol
	extcallcall cmd3


puts_handler:
.:
	jmp strict short .entry
.chain:
	extcall puts_ext_done, required	; must NOT be extcallcall
	times 10 - ($ - .) nop
.entry:

		; es:dx -> message
		; ax = length
	push si
	push di
	push ax
	push es
	push dx
	push ds
	 push es
	 pop ds

	mov cl, byte [ss:isregdump]
internaldatarelocation
	mov byte [ss:wasregdump], cl
internaldatarelocation
	mov byte [ss:isregdump], 0
internaldatarelocation -3

	mov si, dx
	mov cx, ax
	inc cx

	cmp ax, 3
	jb .notours

	cmp byte [si + 2], '='
	jne @F
	cmp word [si], "DS"
	je .regdump
	cmp word [si], "IP"
	jne @F
.regdump:
	mov byte [ss:isregdump], 0FFh
internaldatarelocation -3
	jmp .notours

@@:
	cmp ax, 4 + 1 + 4 + 1 + 8
	jb .notours

%imacro looplodsb 0
	loop %%skip
	jmp .notours
%%skip:
	lodsb
%endmacro

	mov dx, 4
@@:
	looplodsb
	extcallcall getnyb
	jc .notours
	dec dx
	jnz @B
	looplodsb
	cmp al, ':'
	jne .notours

	mov dl, 4
@@:
	looplodsb
	extcallcall getnyb
	jc .notours
	dec dx
	jnz @B

	mov dl, 4
@@:
	looplodsb
	extcallcall getnyb
	jc @F
	dec dx
	jnz @B

	looplodsb
@@:
	cmp al, 32
	jne .notours
@@:
	looplodsb
	cmp al, 32
	je @B
	extcallcall getnyb
	jc .notours

	cmp word [si - 1], "EB"
	je .notours
@@:
	looplodsb
	extcallcall getnyb
	jnc @B
	cmp al, 32
	jne .notours

@@:
	looplodsb
	cmp al, 32
	je @B

	dec si

	mov dx, msg.int
internaldatarelocation
	 push ss
	 pop es
	extcallcall isstring?
	jne @FF
	extcallcall skipwhite
	dec si

	mov dx, msg.20
internaldatarelocation
	extcallcall isstring?
	je .terminate

	rol byte [ss:wasregdump], 1
internaldatarelocation
	jnc @F

	cmp byte [ss:relocateddata + 1], 4Ch
linkdatarelocation reg_eax, -3
	je .checkint21
	jmp .notours

@@:
	rol byte [ss:mov_ah_4Ch], 1
internaldatarelocation
	jnc .notours

.checkint21:
	mov dx, msg.21
internaldatarelocation
	extcallcall isstring?
	je .terminate
	jmp .notours

@@:

	mov di, ah_4Ch_table - 4
internaldatarelocation
	db __TEST_IMM8			; skip pop
@@:
	pop si
	push si
	lea di, [di + 4]
	mov dx, word [ss:di]
	test dx, dx
	jz .not_ah_4Ch
	 push ss
	 pop es
	extcallcall isstring?
	jne @B
	extcallcall skipwhite
	dec si
	mov dx, word [ss:di + 2]
	extcallcall isstring?
	jne @B
	lodsb
	cmp al, ','
	jne .not_ah_4Ch
	extcallcall skipcomm0
	cmp word [si - 1], "4C"
	jne @B

	pop ax				; discard si
	mov byte [ss:mov_ah_4Ch], 0FFh
internaldatarelocation -3
	jmp .notours_ah_4Ch


.not_ah_4Ch:
	pop si
	mov di, table
internaldatarelocation
@@:
	mov dx, word [ss:di]
	test dx, dx
	jz .notours
	 push ss
	 pop es
	extcallcall isstring?
	lea di, [di + 2]
	jne @B

	pop ds
	pop dx
	pop es
	pop ax
	pop di
	pop si

	mov cx, .chain			; -> our chaining entry
internalcoderelocation
	clc
	extcallcall puts_ext_next	; call subsequent puts handlers

	push es
	 push ss
	 pop es
	mov dx, msg.separator + 80	; es:dx -> message
internaldatarelocation
@@:
	mov ax, word [ss:width]
internaldatarelocation
	sub dx, ax			; -> message to display
	add ax, msg.separator_linebreak_size
					; ax = length
	mov cx, .chain			; -> our chaining entry
internalcoderelocation
	clc
	extcallcall puts_ext_next	; call subsequent puts handlers
	pop es

	stc
	extcall puts_ext_done, required
					; directly jump back to debugger, CY

.terminate:
	pop ds
	pop dx
	pop es
	pop ax
	pop di
	pop si

	mov cx, .chain			; -> our chaining entry
internalcoderelocation
	clc
	extcallcall puts_ext_next	; call subsequent puts handlers

	push es
	 push ss
	 pop es
	mov dx, msg.separator_terminate + 80	; es:dx -> message
internaldatarelocation
	jmp @B


.notours:
	mov byte [ss:mov_ah_4Ch], 0
internaldatarelocation -3
.notours_ah_4Ch:

	pop ds
	pop dx
	pop es
	pop ax
	pop di
	pop si

	clc
	jmp .chain			; chain if to display -->


setwidth:
	lodsb
	extcallcall getbyte
	extcallcall chkeol
	cmp dl, 80
	jbe @F
	extcallcall error

@@:
	mov byte [width], dl
internaldatarelocation
	extcallcall cmd3


uninstall:
	lodsb
	extcallcall chkeol

	call get_es_ext

	push es
	pop ds

	mov si, hooktable
internaldatarelocation
	lframe
	lenter
	lvar word, table
	 push si

.loop_table:
	rol byte [ss:si + htInstalled], 1
	jnc .next_table
	xor bx, bx		; = 0 (no prior, modify handler)
	mov di, word [ss:si + htEntry]	; di -> us
	mov si, word [ss:si + htHandler]; -> handler
	mov si, word [ss:si]	; 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:
	mov si, word [bp + ?table]
	mov si, word [ss:si + htHandler]; -> handler
	mov word [ss:si], bx
	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:
	mov si, word [bp + ?table]
	not byte [ss:si + htInstalled]

.next_table:
	add si, HOOKTABLE_size
	mov word [bp + ?table], si
	cmp si, strict word hooktable_end
internaldatarelocation
	jb .loop_table


	clropt [code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as free
	mov dx, msg.uninstall_done
internaldatarelocation
@@:
	lleave
	push ss
	pop ds
	extcallcall putsz
	extcallcall cmd3	; return

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


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


	eldcall_dump_callcall ELDCALL_CALLCALL_LIST

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


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


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

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

	mov ax, word [cs:code + eldiEndData]
internalcoderelocation
	sub ax, word [cs:code + eldiStartData]
internalcoderelocation
	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:

	struc HOOKTABLE
htEntry:		resw 1
htHandler:		resw 1
htInstalled:		resw 1
	endstruc

	align 2, db 0
hooktable:
		; command last so uninstall abort will leave command installed
	istruc HOOKTABLE
at htEntry,		dw puts_handler
internalcoderelocation
at htHandler,		dw relocateddata
linkdatarelocation ext_puts_handler
at htInstalled,		dw 0
	iend
	istruc HOOKTABLE
at htEntry,		dw command
internalcoderelocation
at htHandler,		dw relocateddata
linkdatarelocation ext_command_handler
at htInstalled,		dw 0
	iend
hooktable_end:

	align 2, db 0
table:
	dw msg.jmp
internaldatarelocation
	dw msg.retn
internaldatarelocation
	dw msg.retf
internaldatarelocation
	dw msg.iret
internaldatarelocation
	dw 0

width:
	dw 39

ah_4Ch_table:
	dw msg.mov
internaldatarelocation
	dw msg.ah
internaldatarelocation
	dw msg.mov
internaldatarelocation
	dw msg.ax
internaldatarelocation
	dw 0

mov_ah_4Ch:
	db 0
isregdump:
	db 0
wasregdump:
	db 0

msg:
.useparat:		asciz "USEPARAT"
.uninstall_done:	db "USEPARAT uninstalled."
%if _ELD_RECLAIM_HINT
			db " (Don't forget to use reclaim.eld)"
%endif
			asciz 13,10
.uninstall_error:	asciz "USEPARAT unable to uninstall!",13,10
.width:	asciz "WIDTH"
.jmp:	asciz "JMP"
.retn:	asciz "RETN"
.retf:	asciz "RETF"
.iret:	asciz "IRET"
.int:	asciz "INT"
.20:	asciz "20"
.21:	asciz "21"
.mov:	asciz "MOV"
.ah:	asciz "AH"
.ax:	asciz "AX"
.separator:		times 80 db "_"
.separator_linebreak:	db 13,10
endarea .separator_linebreak
.separator_terminate:	times 80 db "="
			db 13,10

uinit_data: equ $

.installed:	asciz "USEPARAT installed.",13,10
.help:		db "Install this ELD using an INSTALL keyword.",13,10
		db 13,10
		db "Puts separators in disassembly on control flow end.",13,10
		db "A line of underscores is inserted after disassembly of far or near jump,",13,10
		db "or interrupt, far, or near return.",13,10
		db "A line of equals signs is inserted after detecting a DOS termination call.",13,10
		db "(The detection is not perfect.)",13,10
		db 13,10
		db "Run with USEPARAT WIDTH expression to set width (0 to #80, default #39).",13,10
		db "Run with USEPARAT UNINSTALL to uninstall.",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
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
	sub word [es:code + eldiEndCode], ax
internalcoderelocation		; adjust size
	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
	sub word [es:code + eldiEndData], ax
internalcoderelocation		; adjust size
	sub word [relocateddata], ax
linkdatarelocation extdata_used	; adjust size
%endif

	mov si, hooktable_end - HOOKTABLE_size
internaldatarelocation

.loop_table:
	mov bx, word [si + htHandler]; -> handler
	mov bx, word [bx]	; -> prior
	mov di, word [si + htEntry]	; -> our handler
	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:
	mov bx, word [si + htHandler]; -> handler
	mov ax, word [si + htEntry]
	mov word [bx], ax	; -> our entrypoint
	not byte [si + htInstalled]

.next_table:
	sub si, HOOKTABLE_size
	cmp si, strict word hooktable
internaldatarelocation
	jae .loop_table

	setopt [es:code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as resident

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


%include "eldlink.asm"

	align 16
code_size equ $ - code
