;++
;
; Quicky DLL to demonstrate how to add instructions to E11.
;
; By John Wilson <wilson@dbit.com>.
;
; 02/23/2006	JMBW	Created.
;
;--
	.386
	.model	flat
	.code
;
opc=	000077o			;our base opcode
nopc=	1			;# of opcodes starting at OPC
;
; All calls in and out of E11 use C __stdcall calling conventions:
; - arguments are pushed on stack, right to left
; - arguments are flushed by callee using "RET nnn" instruction
; - EBX/EBP/ESI/EDI must be preserved by callee
; - DF=0 on entry, must be 0 on return (i.e. string instructions go forward)
; - function return values are in EAX
;
; PC C compilers usually use stupid name decoration to catch really obvious
; bugs, very annoying.  This are stripped out by the linker (so they don't
; appear in the .DLL file's symbol tables), but we have to use them to be able
; to match the same import library (E11.LIB) that works with C code.  So, add a
; "_" prefix, and an "@nn" suffix, where nn is the number of bytes of arguments
; (most arguments are 4 bytes each in E11).
;
	extrn	_GetOpcode@16:near,_RetOpcode@4:near
	extrn	_GetReg@4:near,_SetReg@8:near
;
DLL_PROCESS_ATTACH=1
DLL_PROCESS_DETACH=0
;
FALSE=0
TRUE=1
;+
;
; BOOL DllMain(HINSTANCE hInstDLL,DWORD fdwReason,LPVOID lpvReserved)
;
; Called at INSTALL and REMOVE (or QUIT).
;
; hInstDLL	handle (just a unique number for this instance, not useful)
; fdwReason	reason for calling:
;		DLL_PROCESS_ATTACH => DLL is being installed
;		DLL_PROCESS_DETACH => DLL is being removed
; lpvReserved	on install:	non-NULL means this DLL is the one in the
;				INSTALL command line, so lpvReserved points at
;				a DOS-style command line:
;				.BYTE LENGTH
;				.ASCII / command/
;				.BYTE 15	;<CR>
;		on remove:	NULL means explicit REMOVE
;				non-NULL means QUIT (but it's NOT a command
;				line pointer in this case)
;
; Return value:
; TRUE (=1) for successful install, FALSE (=0) for failure.
; (Ignored on "remove" calls.)
;
;-
DllMain:
	cmp	dword ptr [esp+04h+04h],DLL_PROCESS_ATTACH ;are we newly alive?
	jne	short dmain1	;no, must be shutting down
	; register our opcode(s)
	; GetOpcode(dword first,dword count,void E11API (*exec)(),void E11API
	;	(*init)())
	push	offset opinit	;init = init routine (called on RESET etc.)
	push	offset opexec	;exec = opcode execution routine
	push	nopc		;count = # consecutive opcodes we want to catch
	push	opc		;first = first opcode in block
	call	_GetOpcode@16	;request block
	test	eax,eax		;success?
	jz	short dmain2	;no, return failure (EAX=FALSE)
	mov	ds:ophnd,eax	;save handle for shutdown
	mov	eax,TRUE	;we're happy
	jmp	short dmain2
dmain1:	; unregister our opcode(s)
	; RetOpcode(E11HANDLE handle)
	push	dword ptr ds:ophnd ;handle = GetOpcode handle
	call	_RetOpcode@4	;return our block of opcodes
dmain2:	ret	3*4		;return, flush three dwords of arguments
;+
;
; opinit(void)
;
; Called on RESET instructions, or INIT/BOOT/etc.
;
;-
opinit:	ret			;nothing to do
;+
;
; opexec(dword opcode)
;
; Called when one of the opcodes in our range is executed.
;
;-
opexec:	push	ebp		;save registers
	push	edi
	push	esi
	push	ebx
	mov	ebx,[esp+4*4+04h] ;get opcode (above 4 regs and R.A.)
				;(frame pointer regs are for dweebs!!!)
	sub	ebx,opc		;subtract our base opcode
	call	dword ptr ds:opcode[ebx*4] ;call opcode handler routine
	pop	ebx		;restore
	pop	esi
	pop	edi
	pop	ebp
	ret	4		;return, flush one-dword argument
;+
;
; UMUL
;
; 32x32 unsigned multiply
;
; We'll just use fixed registers, there aren't that many permutations anyway so
; it's not asking a lot of PDP-11 code to cooperate.
;
; R1:R0 = R3:R2 * R1:R0
; PDP-11 flags are not changed
;
;-
umul:	push	3		;GetReg(3) = get R3
	call	_GetReg@4	;get it
	shrd	ebx,eax,16d	;move low 16 bits of EAX into high 16 of EBX
	push	2		;get R2
	call	_GetReg@4	;(preserve EBX, among others)
	mov	bx,ax		;insert AX in low 16 bits of EBX
	push	1		;get R1
	call	_GetReg@4
	shrd	esi,eax,16d	;same deal as above
	push	0		;get R0
	call	_GetReg@4
	mov	si,ax
	mov	eax,esi		;load R1:R0 into EAX
	mul	ebx		;unsigned multiply by R3:R2 in EBX
				;(result in EDX:EAX)
	mov	ebx,eax		;save out of the way of SetReg
	push	eax		;value = low word of result (junk in LH)
	push	0		;save in R0
	call	_SetReg@8	;(preserve EBX)
	shr	ebx,16d		;right-justify MSW
	push	ebx		;value = high word of result
	push	1		;save in R1
	call	_SetReg@8
	ret			;that's it
;
	.data			;initialized data
;
opcode	label	dword		;opcode handler list (# entries must = NOPC)
	dd	umul		;000077 = UMUL
;
	.data?			;uninitialized data
;
ophnd	dd	1 dup(?)	;GetOpcode handle
;
	end	DllMain
