
; Public Domain

; test install command: install; ; sotf uninstall

%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 "Switch output to file (copy in puts)."


	align 16, db 0

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


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


	usesection CODE

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.sotf
internaldatarelocation
	extcallcall isstring?
	je @F
.transfer_to_chain:
	pop si
	dec si
	lodsb
	jmp .chain

@@:
	extcallcall skipcomma
	dec si
	mov dx, relocateddata
linkdatarelocation msg.uninstall
	extcallcall isstring?
	je uninstall
	mov dx, msg.name
internaldatarelocation
	extcallcall isstring?
	je name
	mov dx, msg.on
internaldatarelocation
	extcallcall isstring?
	je on
	mov dx, msg.off
internaldatarelocation
	extcallcall isstring?
	je off
	mov dx, msg.toggle
internaldatarelocation
	extcallcall isstring?
	je toggle
	lodsb
	extcallcall chkeol
@@:
	extcallcall cmd3

name:
	extcallcall skipcomma
	extcallcall InDOS
	extcallcall yy_dos_parse_name
	; extcallcall yy_common_parse_name
	extcallcall chkeol
	call open_file
	push word [active]
internaldatarelocation
	mov byte [active], 0
internaldatarelocation -3
	xchg ax, word [handle]
internaldatarelocation
	xchg bx, ax
	call close
	mov bx, word [handle]
internaldatarelocation
	mov ax, 4202h
	xor cx, cx
	xor dx, dx
	int 21h
	pop ax
	mov byte [active], al
internaldatarelocation
	extcallcall cmd3


close:
	cmp bx, -1
	je @F
	extcallcall InDOS
	stc
	jnz @F
	mov ah, 3Eh
	extcallcall _doscall
	clc
@@:
	retn

on:
	mov byte [active], 0FFh
internaldatarelocation -3
	extcallcall cmd3

off:
	mov byte [active], 00h
internaldatarelocation -3
	extcallcall cmd3

toggle:
	not byte [active]
internaldatarelocation
	extcallcall cmd3


		; INP:	ds:bx -> filename
		; OUT:	File opened,
		;	 ax = file handle
		; STT:	ds = es = ss = debugger data selector/segment
open_file:
	mov di, bx
	call .setup_opencreate			; ds:si -> pathname
	mov ax, 716Ch				; LFN open-create
	push di
	xor di, di				; alias hint
	stc
	extcallcall _doscall
	pop di
	jnc .got		; LFN call succeeded -->

		; Early case for no-LFN-interface available.
	; cmp ax, 1
	; je .try_sfn
	cmp ax, 7100h
	je .try_sfn

	extcallcall yy_check_lfn
	jnc .error		; if LFN interface is available, actual error
				; if LFN interface is not available, try SFN

.try_sfn:
	call .setup_opencreate
	mov ax, 6C00h				; Open-create
	stc
	extcallcall _doscall
	jnc .got

	cmp ax, 1
	je .try_old_open
	cmp ax, 6C00h
	jne .error

.try_old_open:
	mov al, bl				; access and sharing modes
	mov ah, 3Dh				; Open
	mov dx, si				; -> filename
	stc
	extcallcall _doscall
	jnc .got

	mov ah, 3Ch
	stc
	extcallcall _doscall

.error:
	mov dx, msg.error_file_open
internaldatarelocation
	extcallcall putsz
	extcallcall cmd3

.setup_opencreate:
	mov si, di				; -> filename
	mov bx, 0110_0000_0010_0010b		; Auto-commit, no int 24h
						; DENY WRITE, Read-write
	xor cx, cx				; create attribute
	mov dx, 0000_0000_0001_0001b		; create / open, no truncate
.got:
	retn


uninstall:
	lodsb
	extcallcall chkeol

	mov bx, -1
	mov bx, word [handle]
internaldatarelocation
	call close
	jc .error
	or word [handle], strict byte -1
internaldatarelocation -3

	rol byte [putshandlerinstalled], 1
internaldatarelocation
	jnc @F

	mov bx, puts_handler
internalcoderelocation
	cmp bx, word [relocateddata]
linkdatarelocation ext_puts_handler
	jne .error
	mov ax, word [originalputshandler]
internaldatarelocation
	mov word [relocateddata], ax
linkdatarelocation ext_puts_handler

	not byte [putshandlerinstalled]
internaldatarelocation
@@:

	mov es, word [relocateddata]
linkdatarelocation extdssel
	extcallcall ispm
	jz @F
	mov es, word [relocateddata]
linkdatarelocation extseg
@@:
	push es
	pop ds

	xor bx, bx		; = 0 (no prior, modify ext_command_handler)
	mov di, command		; di -> us
internalcoderelocation
	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:
	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:
	clropt [code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as free
	mov dx, msg.uninstall_done
internaldatarelocation
@@:
	push ss
	pop ds
	extcallcall putsz
	extcallcall cmd3	; return

.error:
	mov dx, msg.uninstall_error
internaldatarelocation
	jmp @B


puts_handler:
	rol byte [active], 1
internaldatarelocation
	jnc .end
	cmp word [handle], strict byte -1
internaldatarelocation -3
	je .end
	extcallcall InDOS
	jnz .end

	push ax
	push bx
	push cx

	mov cx, ax		; cx = length
	mov bx, word [handle]
internaldatarelocation
	mov ah, 40h

	push es
	pop ds			; ds:dx -> text
	extcallcall ispm
	jnz .86
subcpu 286
	push ds
	call selector_to_segment; pass ds as segment
	push word 21h
	extcall intcall_ext_return_es, PM required
	pop ax
	pop ax
subcpureset
	db __TEST_IMM16		; (skip int)
.86:
	int 21h
	 push ss
	 pop ds

	pop cx
	pop bx
	pop ax

.end:
	clc
	extcall puts_ext_done, required


		; INP:	word [ss:sp] = selector to access
		; OUT:	word [ss:sp] = segment value to use for access
		; CHG:	-
selector_to_segment:
	lframe near
	lpar word,	in_selector_out_segment
	lpar_return
	lenter

	extcallcall ispm
				; is it PM ?
	jnz .ret		; no, 86M --> (selector == segment)

subcpu 286
	push ax
	push bx
	push cx
	push dx

	mov bx, word [bp + ?in_selector_out_segment]
	mov ax, 6
	int 31h			; get segment base to cx:dx
	shr dx, 4
	shl cx, 12
	or dx, cx
	mov word [bp + ?in_selector_out_segment], dx

	pop dx
	pop cx
	pop bx
	pop ax
subcpureset

.ret:
	lleave
	lret


error:
	extcall error

	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


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
	mov bx, word [relocateddata]
linkdatarelocation ext_command_handler
				; -> prior
	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:
	setopt [es:code + eldiFlags], eldifResident
internalcoderelocation -3	; mark block as resident
	mov word [relocateddata], command
linkdatarelocation ext_command_handler, -4
internalcoderelocation		; -> our entrypoint

	mov ax, puts_handler
internalcoderelocation
	xchg ax, word [relocateddata]
linkdatarelocation ext_puts_handler
	mov word [originalputshandler], ax
internaldatarelocation

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

	xor ax, ax
	retf


	usesection DATA

	align 2, db 0

handle:			dw -1
originalputshandler:	dw 0
active:			db -1
putshandlerinstalled:	db -1


msg:
.sotf:			asciz "SOTF"
.name:			asciz "NAME"
.on:			asciz "ON"
.off:			asciz "OFF"
.toggle:		asciz "TOGGLE"
.error_file_open:	asciz "SOTF file open/create failed!",13,10
.uninstall_done:	asciz "SOTF uninstalled. (Don't forget to use reclaim.eld)",13,10
.uninstall_error:	asciz "SOTF unable to uninstall!",13,10

uinit_data: equ $

.installed:	asciz "SOTF installed.",13,10
.help:		db "Install this ELD using an INSTALL keyword.",13,10
		db 13,10
		db "Run with SOTF NAME <filenametowrite>",13,10
		db "Run with SOTF ON to enable writing. (Enabled by default.)",13,10
		db "Run with SOTF OFF to disable writing.",13,10
		db "Run with SOTF TOGGLE to toggle writing.",13,10
		db "Run with SOTF UNINSTALL to uninstall.",13,10
		asciz


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

	absolute uinit_data

	alignb 16
uinit_data_end:
resident_data_end:

%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

%include "eldlink.asm"

	align 16
code_size equ $ - code
