;
; dll.s
;
; Handling of DLL data structures
;
;  1994-1998 Straylight
;

;----- Licensing note -------------------------------------------------------
;
; This file is part of Straylight's Dynamic Linking System (SDLS)
;
; SDLS is free software; you can redistribute it and/or modify
; it under the terms of the GNU General Public License as published by
; the Free Software Foundation; either version 2, or (at your option)
; any later version.
;
; SDLS is distributed in the hope that it will be useful,
; but WITHOUT ANY WARRANTY; without even the implied warranty of
; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
; GNU General Public License for more details.
;
; You should have received a copy of the GNU General Public License
; along with SDLS.  If not, write to the Free Software Foundation,
; 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

;----- Standard header ------------------------------------------------------

		GET	libs:header
		GET	libs:swis

		GET	libs:stream

;----- External dependencies ------------------------------------------------

		GET	sh.wSpace
		GET	sh.dllblock
		GET	sh.linkblock

		GET	sh.misc
		GET	sh.app

		GET	sh.messages

;----- External routines ----------------------------------------------------

		AREA	|DLLM$$Code|,CODE,READONLY

		GBLL	debug
debug		SETL	{FALSE}

; --- dll_find ---
;
; On entry:	R0 == name of DLL to find
;		R1 == minimum version number of DLL
; On exit:	R0 == pointer to DLL block, or error

		EXPORT	dll_find
dll_find	ROUT

		STMFD	R13!,{R1-R4,R14}	;Preserve registers

		MOV	R4,R1			;Keep hold of version number
		MOV	R1,R0			;Keep pointer to string
		LDR	R3,dll__list		;Find the list

00dll_find	CMP	R3,#0			;Is this the end of the line?
		BEQ	%40dll_find		;Yes -- give an error
		LDR	R0,[R3,#dl_name]	;Find the name string
		MOV	R2,#0			;Caseless compare
		BL	misc_strcmp		;Compare the strings
		LDRNE	R3,[R3,#dl_next]	;If no match, move on...
		BNE	%00dll_find		;... and try again

		; --- We found a name match ---

		LDR	R0,[R3,#dl_version]	;Get version of DLL
		CMP	R0,R4			;Check against version here
		LDRLT	R3,[R3,#dl_next]	;If too low, move on...
		BLT	%00dll_find		;... and try again

		; --- The name checked out -- return ---

		MOV	R0,R3			;Point to DLL (give handle)
		LDMFD	R13!,{R1-R4,PC}^	;And return to caller

		; --- It wasn't there.  Return an error ---

40dll_find	MOV	R0,R4			;Get version number
		BL	dll_convertVersion	;Convert it to a string
		MOV	R2,R0			;Keep pointer to the string
		ADRL	R0,msg_errDLLNotFound	;Couldn't find DLL name
		BL	misc_error		;... create an error message
		LDMFD	R13!,{R1-R4,R14}	;... restore registers
		ORRS	PC,R14,#V_flag		;... and return an error

		LTORG

; --- dll_ensure ---
;
; On entry:	R0 == pointer to name to load
;		R1 == version number of DLL
; On exit:	R0 == DLL handle

		EXPORT	dll_ensure
dll_ensure	ROUT

		STMFD	R13!,{R1-R5,R9-R11,R14}	;Stash registers somewhere

		; --- Find the name, and see if it's in memory ---

		MOV	R10,R1			;Save the version number
		BL	dll__createName		;Set up the name
		CMP	R0,#0			;Was it in memory?
		LDMNEFD	R13!,{R1-R5,R9-R11,PC}	;If so, we should be happy
		MOV	R9,R2			;Keep pointer to leafname

		; --- Find the size of the DLL and allocate memory ---

01dll_ensure	MOV	R0,#17			;Read catalogue information
		SWI	XOS_File		;Read the information
		LDMVSFD	R13!,{R1-R5,R9-R11,PC}	;If it failed, return now
		CMP	R0,#1			;Check that the file was OK
		BNE	%08dll_ensure		;If not, complain properly
		ADD	R3,R4,#dl_extra		;Size of memory to get
		MOV	R0,#6			;Allocate memory from RMA
		SWI	XOS_Module		;Allocate it
		LDMVSFD	R13!,{R1-R5,R9-R11,PC}	;If no memory, return error

		; --- Load the DLL into the block ---

		MOV	R0,R2			;Pointer to the block to use
		MOV	R11,R0			;Keep hold of DLL handle
		BL	dll__load		;Load the DLL into the block
		BVS	%10dll_ensure		;If it failed, return error

		; --- Check the DLL's version number ---

		LDR	R1,[R11,#dl_version]	;Load the DLL's version
		CMP	R1,R10			;Compare against version
		BLT	%09dll_ensure		;If too old, give an error

		; --- Link the new DLL into the list ---
		;
		; This also marks the DLL as being shared, since the shared
		; marker is dl_next not being -1.

		LDR	R2,dll__list		;Point to list head
		STR	R11,dll__list		;Store the next one
		MOV	R0,#0			;Ready to zero bits
		STR	R0,[R11,#dl_prev]	;No previous DLL yet
		STR	R2,[R11,#dl_next]	;Fix up next link
		CMP	R2,#0			;Is there another block?
		STRNE	R11,[R2,#dl_prev]	;If so, fix up prev link

		; --- Fix up other bits of data ---

		MOV	R0,#dl_tentative	;Set DLL's `tentative' bit
		STR	R0,[R11,#dl_clients]	;No clients registered yet

		; --- Start up any required DLLs ---

		LDR	R1,[R11,#dl_dllLimit]	;Find limit of DLL block
		LDR	R0,[R11,#dl_dllBase]	;Find base of same
		BL	app_sfromtbl		;Load any other required DLLs

		; --- Now return the DLL handle ---

		MOVVC	R0,R11			;Get the DLL handle
		LDMFD	R13!,{R1-R5,R9-R11,PC}	;Return

		; --- Say that the file wasn't found ---

08dll_ensure	ADRL	R0,msg_errFileNotFound	;Point to error skeleton
		BL	misc_error		;Create the message
		B	%10dll_ensure		;Return to an error

		; --- Create an error about old version ---

09dll_ensure	MOV	R0,R10			;Get the required version
		BL	dll_convertVersion	;Convert to a string
		MOV	R2,R0			;Make that fillin 2
		MOV	R1,R9			;Point to DLL name string
		ADRL	R0,msg_errDLLTooOld	;Point to error skeleton
		BL	misc_error		;Set up the error block

		; --- Free up memory and return an error ---

10dll_ensure	MOV	R1,R0			;Keep hold of the error
		MOV	R0,#7			;Free the block I allocated
		MOV	R2,R11			;Point to the DLL block
		SWI	XOS_Module		;Do the free operation
		MOV	R0,R1			;Put the error pointer back
		LDMFD	R13!,{R1-R5,R9-R11,R14} ;Retreive registers
		ORRS	PC,R14,#V_flag		;Return with an error

		LTORG

; --- dll_check ---
;
; On entry:	R0 == pointer to name to load <SPACE> version number
; On exit:	--

		; --- The format string for the command line ---

dll__checkfmt	DCB	"/a/g,"
		DCB	"/a/g",0
		ALIGN

		EXPORT	dll_check
dll_check	ROUT

		STMFD	R13!,{R1-R5,R9-R11,R14}	;Stack cunning registers

		; --- Allocate a nice buffer ---

		MOV	R10,R0			;Keep pointer to cmd string
		MOV	R0,#6			;Allocate some memory
		MOV	R3,#256			;The size of said memory
		SWI	XOS_Module		;Get it
		LDMVSFD	R13!,{R1-R5,R9-R11,PC}	;Return if we can't have it
		MOV	R11,R2			;Save the pointer

		; --- Parse up the command string ---

		ADR	R0,dll__checkfmt	;Point to format string
		MOV	R1,R10			;Point to command string
		MOV	R2,R11			;Point to my nice buffer
		MOV	R3,#256			;And do the business
		SWI	XOS_ReadArgs		;Get the OS to parse it up
		BVS	%90dll_check		;If it failed, return

		; --- Read the version number ---

		LDR	R5,[R11,#4]		;Get info about version strng
		LDRB	R4,[R5,#0]		;Get LSB of its length
		LDRB	R3,[R5,#1]		;Get MSB of its length
		ORR	R4,R4,R3,LSL #8		;Turn this into a length

		MOV	R3,#10			;We're multiplying a lot
		MOV	R10,#0			;The version as we know it
		MOV	R2,#0			;Decimal part of version
		ADD	R5,R5,#2		;Point to the real string

		CMP	R4,#0			;Is there a string there?
		BEQ	%70dll_check		;No -- that's an error
00dll_check	LDRB	R0,[R5],#1		;Get a character
		CMP	R0,#'.'			;Is it a decimal point?
		BEQ	%01dll_check		;Yes -- next bit please
		BL	%80dll_check		;Convert it to a number
		MLA	R10,R3,R10,R0		;Add the digit on
		SUBS	R4,R4,#1		;Decrement length count
		BNE	%00dll_check		;Get another digit if I can
		B	%03dll_check		;Now check for the DLL...

01dll_check	CMP	R4,#1			;Is the string empty now?
		BEQ	%03dll_check		;Yes -- get on with it
		LDRB	R0,[R5]			;Get the next digit
		BL	%80dll_check		;Convert it to a number
		MUL	R2,R3,R0		;And put it nicely away
		CMP	R4,#2			;Is there only one char?
		BEQ	%03dll_check		;Yes -- we've done it now
		LDRB	R0,[R5,#1]		;Get the remaining digit
		BL	%80dll_check		;Convert it to a number
		ADD	R2,R2,R0		;And put it in
		CMP	R4,#3			;Make sure that's all
		BNE	%70dll_check		;If not, complain

03dll_check	MOV	R3,#100			;Now join the two together
		MLA	R10,R3,R10,R2		;Now we have a version!

		; --- Now mangle the name to something usable ---

		LDR	R0,[R11,#0]		;Point to name info
		LDRB	R4,[R0,#0]		;Get LSB of name length
		LDRB	R3,[R0,#1]		;Get MSB of name length
		ORR	R4,R4,R3,LSL #8		;Convert to a real length
		ADD	R0,R0,#2		;Point to the actual name
		MOV	R1,#0			;Zero-terminate it
		STRB	R1,[R0,R4]		;Store that in right place

		; --- Find out a good name to use ---

		MOV	R1,R10			;Get the version number
		BL	dll__createName		;Turn this into a filename
		CMP	R0,#0			;Was it in memory?
		BEQ	%10dll_check		;No -- continue onwards
		MOV	R0,#7			;Free that buffer
		MOV	R2,R11			;Point to it
		SWI	XOS_Module		;Free it for real now
		LDMFD	R13!,{R1-R5,R9-R11,PC}^	;Return to caller

		; --- Free the OS_ReadArgs buffer now ---

10dll_check	MOV	R9,R2			;Keep pointer to leafname
		MOV	R0,#7			;Free that buffer
		MOV	R2,R11			;Point to it
		SWI	XOS_Module		;Free it for real now
		ADD	R11,R1,#200		;Keep a pointer to misc_buf

		; --- Open a file for the DLL ---

		MOV	R0,#&4F			;Open, with errors, no path
		SWI	XOS_Find		;Find the file
		BVS	%90dll_check		;If it failed, give error
		MOV	R5,R0			;Look after the handle

		; --- Now load a bit of the file ---

		MOV	R2,R11			;Point to the misc_buf
		MOV	R1,R5			;Get the file handle
		MOV	R3,#20			;Get the first twenty bytes
		MOV	R4,#0			;Read from the beginning
		MOV	R0,#3			;Read bytes from file
		SWI	XOS_GBPB		;Quick, now, do it

		; --- Close the file ---

		MOV	R0,#0			;Close the file
		MOV	R1,R5			;Get the file handle
		SWI	XOS_Find		;Close the file

		; --- Now check the fields in the block ---

		LDR	R0,[R11,#dl_magic-dl_extra] ;Get the magic DLL word
		LDR	R1,=dl_MAGIC		;Get the real version
		CMP	R0,R1			;Check it's kosher
		BNE	%91dll_check		;If not, make an error

		LDR	R0,[R11,#dl_bversion-dl_extra] ;Get format version
		LDR	R1,=dl_VERSION		;Get the one we're on now
		CMP	R0,R1			;Compare the versions
		BGT	%92dll_check		;If too late, complain

		LDR	R0,[R11,#dl_version-dl_extra] ;Get DLL version
		CMP	R0,R10			;Cmp with caller's version
		BLT	%93dll_check		;If too late, complain

		LDMFD	R13!,{R1-R5,R9-R11,PC}^	;Return, job well done

		; --- Get a digit, and convert ---

80dll_check	CMP	R0,#'0'			;Is it less than 0?
		BLT	%70dll_check		;Yes -- that's an error
		CMP	R0,#'9'			;Is it greater than 9?
		BGT	%70dll_check		;Yes -- that's an error
		SUB	R0,R0,#'0'		;Convert to a digit
		MOVS	PC,R14			;Return to caller

		; --- Make an error about a mangled version ---

70dll_check	ADRL	R0,msg_errBadVersion	;Point to error message
		B	%90dll_check		;Free memory and return error

		; --- Tidy up after an error and return ---

90dll_check	MOV	R9,R0			;Look after error pointer
		MOV	R0,#7			;Free that buffer
		MOV	R2,R11			;Point to it
		SWI	XOS_Module		;Free it for real now
		MOV	R0,R9			;Point to the error
		LDMFD	R13!,{R1-R5,R9-R11,R14}	;Unstack all the registers
		ORRS	PC,R14,#V_flag		;And return the error

		; --- A file wasn't a real DLL ---

91dll_check	ADRL	R0,msg_errNotADLL	;Point to error
		MOV	R1,R9			;Point to leafname
		BL	misc_error
		LDMFD	R13!,{R1-R5,R9-R11,R14}	;Unstack all the registers
		ORRS	PC,R14,#V_flag		;And return the error

		; --- A file had a silly version number ---

92dll_check	ADRL	R0,msg_errTooNew	;Point to error
		MOV	R1,R9			;Point to leafname
		BL	misc_error
		LDMFD	R13!,{R1-R5,R9-R11,R14}	;Unstack all the registers
		ORRS	PC,R14,#V_flag		;And return the error

		; --- A file was too old for the caller ---

93dll_check	MOV	R0,R10			;Get version number wanted
		BL	dll_convertVersion	;Convert to printable form
		MOV	R2,R0			;That's fillin number 2
		ADRL	R0,msg_errDLLTooOld	;Point to error
		MOV	R1,R9			;Point to leafname
		BL	misc_error
		LDMFD	R13!,{R1-R5,R9-R11,R14}	;Unstack all the registers
		ORRS	PC,R14,#V_flag		;And return the error

		LTORG

; --- dll_load ---
;
; On entry:	R0 == DLL handle (block to load into)
; 		R1 == filename of DLL
; On exit:	--

		EXPORT	dll_load
dll_load	ROUT

		STMFD	R13!,{R1,R10,R11,R14}	;Keep link register safe

		; --- Load the DLL into the block

		MOV	R11,R0			;Keep DLL pointer safe
		BL	dll__load		;Load the DLL
		LDMVSFD	R13!,{R1,R10,R11,PC}	;Return if there's an error

		; --- If it's late enough, fit it up to the application ---

		LDR	R10,[R11,#dl_bversion]	;Get the format version
		CMP	R10,#100		;Is it the old version?

		LDRGT	R1,[R11,#dl_appStubs]	;Find the app stubs table
		CMPGT	R1,R11			;Is this pointer sensible?
		BLE	%90dll_load		;Yes -- skip this bit
		LDR	R0,[R11,#dl_appStubNames] ;Find the names table
		CMP	R0,R0			;Clear V flag
		BL	app_fix			;Yes -- fix up the table
		LDMVSFD	R13!,{R1,R10,R11,PC}	;Return if there's an error

		; --- Load any required shared DLLs ---

90dll_load	LDR	R1,[R11,#dl_dllLimit]	;Find limit of DLL block
		LDR	R0,[R11,#dl_dllBase]	;Find base of same
		BL	app_fromtable		;Load any other required DLLs
		LDMFD	R13!,{R1,R10,R11,PC}	;Return to caller

		LTORG

; --- dll_appEntry ---
;
; On entry:	R0 == pointer to application's entry table
;		R1 == pointer to application's name table
;		R2 == pointer to entry point name
; On exit:	R0 == pointer to entry point (if present)

		EXPORT	dll_appEntry
dll_appEntry	ROUT

		STMFD	R13!,{R1-R5,R14}	;Stack registers

		MOV	R3,R2			;Keep pointer to entry point

		; --- Main matching loop ---

00dll_appEntry	LDRB	R4,[R1],#1		;Get first byte of next name
		CMP	R4,#0			;Is it a null byte?
		BEQ	%41dll_appEntry		;Yes -- couldn't find entry
		CMP	R4,#1			;Is it a dummy entry?
		BEQ	%10dll_appEntry		;Yes -- don't check it then
01dll_appEntry	LDRB	R5,[R3],#1		;Get byte from name too
		CMP	R4,R5			;Do they match?
		BNE	%02dll_appEntry		;No -- find the next name
		CMP	R4,#0			;Is this the end?
		LDRNEB	R4,[R1],#1		;No -- get another name byte
		BNE	%01dll_appEntry		;And go round again

		; --- We found it ---

		LDR	R0,[R0,#0]		;Get the actual entry address
		LDMFD	R13!,{R1-R5,PC}^	;Return to caller happy

		; --- Move on to next name in the table ---

02dll_appEntry	CMP	R4,#0			;Is this the end of the name?
		LDRNEB	R4,[R1],#1		;No -- get another byte
		BNE	%02dll_appEntry		;And go round again

10dll_appEntry	ADD	R0,R0,#4		;Move on to next entry ptr
		MOV	R3,R2			;Point to start of entry name
		B	%00dll_appEntry		;And try that one out

		; --- Entry point could not be found ---

41dll_appEntry	MOV	R1,R2			;Point to entry point name
		ADRL	R0,msg_errAppEntry	;Point to error message
		BL	misc_error		;Create the error message
		LDMFD	R13!,{R1-R5,R14}	;Find saved registers
		ORRS	PC,R14,#V_flag		;Return the error to caller

		ALIGN

; --- dll_findEntry ---
;
; On entry:	R0 == pointer to DLL
; 		R1 == pointer to entry point name
; On exit:	R0 == pointer to entry point

		EXPORT	dll_findEntry
dll_findEntry	ROUT

		STMFD	R13!,{R1-R7,R14}	;Stack registers

		; --- Set up for main loop ---

		LDR	R7,[R0,#dl_entries]	;Find number of entry points
		LDR	R3,[R0,#dl_enames]	;Point to start of name table
		LDR	R4,[R0,#dl_eveneer]	;No entry points found yet
		TST	R7,#dl_noNames		;No names?
		BNE	%42dll_findEntry	;Then deal with this
		BICS	R2,R7,#&FF000000	;Clear entry type flags
		BEQ	%40dll_findEntry	;No entry points -- weird

		; --- Find whether this name matches ---

00dll_findEntry	MOV	R5,R1			;Point to string to match
		LDRB	R14,[R3,#0]		;Load first byte from name
		CMP	R14,#1			;Is this a dummy entry?
		ADDEQ	R3,R3,#1		;Yes -- skip past the byte
		BEQ	%10dll_findEntry	;Yes -- don't check the name

01dll_findEntry	LDRB	R6,[R5],#1		;Read byte from pattern
		LDRB	R14,[R3],#1		;Read byte from target
		CMP	R6,R14			;Do they match?
		BNE	%02dll_findEntry	;No -- try another string
		CMP	R6,#0			;Is this the string end?
		BNE	%01dll_findEntry	;No -- try another char

		; --- We found the entry point ---

		TST	R7,#dl_shortEntries	;Are these APCS veneers?
		MOVEQ	R0,R4			;Yes -- point to veneer base
		LDRNE	R0,[R4,#0]		;No -- return base address
		LDMFD	R13!,{R1-R7,PC}^	;Return to caller

		; --- No luck -- try another entry point ---

02dll_findEntry	CMP	R14,#0			;Is this the end of the name?
		LDRNEB	R14,[R3],#1		;No -- get another character
		BNE	%02dll_findEntry	;And try again

10dll_findEntry	SUBS	R2,R2,#1		;Decrement entry point count
		BEQ	%41dll_findEntry	;If we ran out, that's it

		TST	R7,#dl_shortEntries	;Are these APCS veneers?
		ADDEQ	R4,R4,#16		;Yes -- move to next veneer
		ADDNE	R4,R4,#4		;No -- move to next word
		B	%00dll_findEntry	;And try again

		; --- DLL has no entry points ---

40dll_findEntry	LDR	R1,[R0,#dl_name]	;Point to DLL name
		ADRL	R0,msg_errNoEntry	;Point to error message
		BL	misc_error		;Create the error message
		LDMFD	R13!,{R1-R7,R14}	;Find saved registers
		ORRS	PC,R14,#V_flag		;Return the error to caller

		; --- Entry point could not be found ---

41dll_findEntry	MOV	R2,R1			;Point to entry point name
		LDR	R1,[R0,#dl_name]	;Point to DLL name
		ADRL	R0,msg_errDLLEntry	;Point to error message
		BL	misc_error		;Create the error message
		LDMFD	R13!,{R1-R7,R14}	;Find saved registers
		ORRS	PC,R14,#V_flag		;Return the error to caller

		; --- DLL has no named entry points ---

42dll_findEntry	LDR	R1,[R0,#dl_name]	;Point to DLL name
		ADRL	R0,msg_errNoNames	;Point to error message
		BL	misc_error		;Create the error message
		LDMFD	R13!,{R1-R7,R14}	;Find saved registers
		ORRS	PC,R14,#V_flag		;Return the error to caller

		LTORG

; --- dll_showInfo ---
;
; On entry:	R0 == pointer to argument string
; On exit:	--

		EXPORT	dll_showInfo
dll_showInfo	ROUT

		STMFD	R13!,{R1-R4,R14}	;Stack some registers

		; --- Parse some arguments ---

		MOV	R1,R0			;Point to the command tail
		ADR	R0,dll_sinfoDef		;Point to definition string
		ADR	R2,misc__sharedBuf	;Point to scratch buffer
		MOV	R3,#256			;Size of my buffer
		SWI	XOS_ReadArgs		;Read the command line
		LDMVSFD	R13!,{R1-R4,PC}		;Return if it didn't work

		MOV	R4,#0			;Clear some flags
		LDR	R14,[R2,#0]		;Load the `full' flag
		CMP	R14,#0			;Is it clear?
		ORRNE	R4,R4,#1		;No -- set the flag then

		LDR	R0,[R2,#4]		;Load the string pointer
		CMP	R0,#0			;Is it there?
		ADREQL	R0,synt_DLLInfo - 4	;No -- point to syntax string
		LDMEQFD	R13!,{R1-R4,R14}	;Restore registers
		ORREQS	PC,R14,#V_flag		;And return to caller

		; --- Find the DLL ---

		MOV	R2,R0			;Keep hold of the pointer
		MOV	R1,#0			;Don't care about version
		BL	dll_find		;Find the DLL pointer
		BVS	%10dll_showInfo		;Give an error if not found

		; --- Show the info :-) ---

		MOV	R3,R0			;Keep pointer to DLL block
		ADRL	R0,msg_dinfoName
		SWI	XOS_Write0
		LDR	R0,[R3,#dl_name]	;Point to the DLL's name
		SWI	XOS_Write0
		SWI	XOS_NewLine

		ADRL	R0,msg_dinfoAuthor
		SWI	XOS_Write0
		LDR	R0,[R3,#dl_copyright]	;Point to the DLL's author
		SWI	XOS_Write0
		SWI	XOS_NewLine

		ADRL	R0,msg_dinfoVersion
		SWI	XOS_Write0
		LDR	R0,[R3,#dl_version]	;Point to the DLL's version
		BL	dll_convertVersion	;Convert the version number
		SWI	XOS_Write0
		SWI	XOS_NewLine

		ADRL	R0,msg_dinfoReferences
		SWI	XOS_Write0
		LDR	R0,[R3,#dl_clients]	;Get client count
		BIC	R0,R0,#dl_tentative	;Clear tentative bit if any
		SUB	R13,R13,#12		;Give some space for string
		MOV	R1,R13			;Point to this space
		MOV	R2,#12			;Size of the buffer
		SWI	XOS_ConvertInteger4	;Convert it to a string
		SWI	XOS_Write0
		ADD	R13,R13,#12		;Reclaim workspace
		SWI	XOS_NewLine

		TST	R4,#1			;Do we want to print this?
		BEQ	%02dll_showInfo		;No -- don't then

		ADRL	R0,msg_dinfoEntries
		SWI	XOS_Write0

		; --- Write out entry point names ---

		LDR	R2,[R3,#dl_entries]	;Get number of entry points
		TST	R2,#dl_noNames		;No name table?
		BNE	%08dll_showInfo		;No -- tell user
		BICS	R2,R2,#&FF000000	;Clear type bits
		BEQ	%09dll_showInfo		;No entries -- spooky

		LDR	R1,[R3,#dl_enames]	;Point to first entry name
00dll_showInfo	SWI	XOS_WriteI+' '		;Indent the list a bit
		SWI	XOS_WriteI+' '

01dll_showInfo	LDRB	R0,[R1],#1		;Get a name byte
		CMP	R0,#1			;Is it a dummy entry?
		SWIHI	XOS_WriteC		;No -- write it out
		BHS	%01dll_showInfo		;And go round for the next

		SWI	XOS_NewLine		;Start a new line
		SUBS	R2,R2,#1		;Done another one
		BGT	%00dll_showInfo		;If more to do, continue

		; --- Return to caller ---

02dll_showInfo	LDMFD	R13!,{R1-R4,PC}^	;Return with registers nice

		; --- Entry point table suppressed ---

08dll_showInfo	ADRL	R0,msg_dinfoHidden	;Point to the string
		SWI	XOS_Write0		;Display that on the screen
		B	%02dll_showInfo		;And return to caller

		; --- No entry points -- freaky ---

09dll_showInfo	ADRL	R0,msg_dinfoNone	;Point to the string
		SWI	XOS_Write0		;Display that on the screen
		B	%02dll_showInfo		;Resume information

		; --- Couldn't find the DLL ---

10dll_showInfo	MOV	R1,R2			;Point to DLL name
		ADRL	R0,msg_errDLLNotInMem	;Point to the error
		BL	misc_error		;Create error message fully
		LDMFD	R13!,{R1-R4,R14}	;Unstack all registers
		ORRS	PC,R14,#V_flag		;Return to caller with error

dll_sinfoDef	DCB	"full/s,",0

		LTORG

; --- dll_writeTitle ---
;
; On entry:	--
; On exit:	--

		EXPORT	dll_writeTitle
dll_writeTitle	ROUT

		STMFD	R13!,{R14}		;Stack link register for this
		ADRL	R0,msg_dllHeader	;Point to the header line
		SWI	XOS_Write0		;Display it nicely
		LDMFD	R13!,{PC}^		;Return to caller

		LTORG

; --- dll_writeInfo ---
;
; On entry:	R0 == pointer to a DLL block
; On exit:	--

		EXPORT	dll_writeInfo
dll_writeInfo	ROUT

		STMFD	R13!,{R1-R3,R14}	;Stack some registers
		MOV	R2,R0			;Keep hold of pointer
		MOV	R1,#17			;Length of DLL name
		LDR	R0,[R2,#dl_name]	;Point to the name

		MOV	R3,#0			;Length so far
00		LDRB	R14,[R0,R3]		;Load a byte
		CMP	R14,#0			;At the end?
		ADDNE	R3,R3,#1		;No -- inc length
		BNE	%b00			;And keep on looping

		CMP	R3,#16			;Is this to long?
		BL	dll_field		;Display the name
		SWIGT	OS_NewLine		;Too long -- print a newline
		MOVGT	R1,#17			;Print 21 spaces
		ADRGT	R0,dll__nullStr		;Point to the string
		BLGT	dll_field		;And do that thing
		LDR	R0,[R2,#dl_version]	;Load the version
		BL	dll_convertVersion	;Convert it to a string
		MOV	R1,#10			;Length of version field
		BL	dll_field		;Display it on the screen
		LDR	R0,[R2,#dl_copyright]	;Point to copyright string
		SWI	XOS_Write0		;Display on the screen
		SWI	XOS_NewLine		;Follow with a newline
		LDR	R2,[R2,#dl_next]	;Point to next one along
		LDMFD	R13!,{R1-R3,PC}^	;Return to caller

dll__nullStr	DCB	0

		LTORG

; --- dll_list ---
;
; On entry:	--
; On exit:	--

		EXPORT	dll_list
dll_list	ROUT

		STMFD	R13!,{R1,R2,R14}

		; --- Set up for a loop through the list ---

		LDR	R2,dll__list		;Point to list
		CMP	R2,#0			;Check that it's non-null
		BEQ	%01dll_list		;If so, give special message

		; --- Do a bit of screen set-up ---

		BL	dll_writeTitle		;Display the title line

		; --- Main loop ---

00dll_list	MOV	R0,R2			;Point to DLL block
		BL	dll_writeInfo		;Display info about it
		LDR	R2,[R2,#dl_next]	;Point to next one along
		CMP	R2,#0			;Is there another one?
		BNE	%00dll_list		;Yes -- display its stuff
		LDMFD	R13!,{R1,R2,PC}^

01dll_list	ADRL	R0,msg_noDLLs		;Point to the message
		SWI	XOS_Write0		;Display that on the screen
		LDMFD	R13!,{R1,R2,PC}^

		LTORG

; --- dll_convertVersion ---
;
; On entry:	R0 == version number
; On exit:	R0 == pointer to version string

		EXPORT	dll_convertVersion
dll_convertVersion ROUT

		STMFD	R13!,{R1-R3,R14}	;Preserve registers
		MOV	R1,#100			;Divisor
		BL	dll__divide		;Find major and minor version
		LDMVSFD	R13!,{R1-R3,PC}		;Return if there's an error
		MOV	R3,R1			;Keep minor version
		ADR	R1,misc__sharedBuf	;Point to buffer
		MOV	R2,#16			;Give it a sensible size
		SWI	XOS_ConvertCardinal3	;This is excessive, but...
		LDMVSFD	R13!,{R1-R3,PC}		;Return if there's an error
		MOV	R0,#'.'			;To be written to buffer
		STRB	R0,[R1],#1		;Write the decimal separator
		SUB	R2,R2,#1		;Another byte used in buffer
		MOV	R0,R3			;Get minor version number
		SWI	XOS_ConvertCardinal1	;Can't be more than 100
		LDMVSFD	R13!,{R1-R3,PC}		;Return if there's an error
		LDRB	R2,[R0,#1]		;Get second character
		CMP	R2,#0			;Is there one?
		BNE	%00dll_convertVersion	;Yes -- skip ahead a bit
		STRB	R2,[R0,#2]		;Leave exactly 2 digits
		LDRB	R2,[R0,#0]		;Get first character
		STRB	R2,[R0,#1]		;Store in second position
		MOV	R2,#'0'			;And write a leading 0
		STRB	R2,[R0,#0]		;In first position
00dll_convertVersion
		ADR	R0,misc__sharedBuf	;Point to buffer
		LDMFD	R13!,{R1-R3,PC}^	;Return to caller nicely

		LTORG

; --- dll_field ---
;
; On entry:	R0 == pointer to string
; 		R1 == length of field

		EXPORT	dll_field
dll_field	ROUT

		STMFD	R13!,{R1,R2,R14}	;Stash registers
		MOV	R2,R0			;Keep hold of string ptr
00dll_field	LDRB	R0,[R2],#1		;Get a string byte
		CMP	R0,#0			;Check the byte
		BEQ	%01dll_field		;If end, write pad chars
		SWI	XOS_WriteC		;Write the character
		SUB	R1,R1,#1		;Decrement counter thing
		B	%00dll_field		;If allowed, get the next one

01dll_field	SUBS	R1,R1,#1		;Decrement counter thing
		LDMLEFD	R13!,{R1,R2,PC}^	;Return if done
02dll_field	SWI	XOS_WriteI+' '		;Write a space
		SUBS	R1,R1,#1		;Decrement counter thing
		BGT	%02dll_field		;If allowed, write another
		LDMFD	R13!,{R1,R2,PC}^	;Return happy :-)

		LTORG

; --- dll_compare ---
;
; On entry:	R0 == pointer to DLL
;		R1 == pointer to name string
;		R2 == required version number
; On exit:	R0 == 1 for a match, or 0 for no match

		EXPORT	dll_compare
dll_compare	ROUT

		STMFD	R13!,{R1,R2,R14}	;Keep registers safe
		LDR	R14,[R0,#dl_version]	;Find the version number
		CMP	R2,R14			;How does it shape up?
		BGT	%00dll_compare		;If too low, return 0
		MOV	R2,#0			;Case insensitive bitty
		LDR	R0,[R0,#dl_name]	;Point to DLL's name string
		BL	misc_strcmp		;Compare the strings
		MOVEQ	R0,#1			;If there's a match, return 1
		LDMEQFD	R13!,{R1,R2,PC}^	;Return to caller
00dll_compare	MOV	R0,#0			;No match here
		LDMFD	R13!,{R1,R2,PC}^	;Return to caller

		LTORG

; --- dll_tentative ---
;
; On entry:	R0 == pointer to DLL
; On exit:	--

		EXPORT	dll_tentative
dll_tentative	ROUT

		STMFD	R13!,{R14}		;Store return address
		LDR	R14,[R0,#dl_clients]	;Find the magic counter
		ORR	R14,R14,#dl_tentative	;Set the `tentative' bit
		STR	R14,[R0,#dl_clients]	;And store back again
		LDMFD	R13!,{PC}^		;Return to caller

		LTORG

; --- dll_confirm ---
;
; On entry:	--
; On exit:	--

		EXPORT	dll_confirm
dll_confirm	ROUT

		STMFD	R13!,{R14}		;Store return address
		LDR	R0,dll__list		;Find list base address
		CMP	R0,#0			;Are there any entries
		LDMEQFD	R13!,{PC}^		;Return to caller if not
00dll_confirm	LDR	R14,[R0,#dl_clients]	;Find the magic counter
		TST	R14,#dl_tentative	;Is it a tentative one?
		BICNE	R14,R14,#dl_tentative	;Yes -- clear `tentative' bit
		ADDNE	R14,R14,#1		;Increment the counter
		STRNE	R14,[R0,#dl_clients]	;And store back again
		LDR	R0,[R0,#dl_next]	;Find next DLL in the chain
		CMP	R0,#0			;Is this the end yet?
		BNE	%00dll_confirm		;Go round again if not
		LDMFD	R13!,{PC}^		;Return to caller

		LTORG

; --- dll_retrace ---
;
; On entry:	--
; On exit:	--

		EXPORT	dll_retrace
dll_retrace	ROUT

		STMFD	R13!,{R1,R14}		;Keep registers safe
		LDR	R0,dll__list		;Find list base address
		CMP	R0,#0			;Are there any more entries
		LDMEQFD	R13!,{R1,PC}^		;Return to caller if not
00dll_retrace	LDR	R1,[R0,#dl_next]	;Find the next block along
		LDR	R14,[R0,#dl_clients]	;Load the magic counter
		BICS	R14,R14,#dl_tentative	;Clear the `tentative' bit
		STR	R14,[R0,#dl_clients]	;Store back in structure
		BLEQ	dll__free		;Free the block if no count
		MOVS	R0,R1			;Point to next block along
		BNE	%00dll_retrace		;If any more to do, do more
		LDMFD	R13!,{R1,PC}^		;Return to caller

		LTORG

; --- dll_freeAll ---
;
; On entry:	--
; On exit:	--

		EXPORT	dll_freeAll
dll_freeAll	ROUT

		STMFD	R13!,{R1,R2,R14}	;Preserve registers nicely
		MOV	R0,#7			;Freeing memory now
		LDR	R2,dll__list		;Point to first entry
		CMP	R2,#0			;Is there anything to do?
		LDMEQFD	R13!,{R1,R2,PC}^	;No -- just leave now

00dll_freeAll	LDR	R1,[R2,#dl_next]	;Get next pointer in list
		SWI	XOS_Module		;Free the block
		MOVS	R2,R1			;Point to the next entry
		BNE	%00dll_freeAll		;If any more to do, do them

		STR	R2,dll__list		;Store 0 back into list head
01dll_freeAll	LDMFD	R13!,{R1,R2,PC}^	;Return to caller

		LTORG

; --- dll_dec ---
;
; On entry:	R0 == pointer to DLL to decrement
; On exit:	--

		EXPORT	dll_dec
dll_dec		ROUT

		STMFD	R13!,{R14}		;Preserve link register
		LDR	R14,[R0,#dl_next]	;Find whether it's shared
		CMP	R14,#-1			;Just check to make sure
		BEQ	%00dll_dec		;If not, give an error
		LDR	R14,[R0,#dl_clients]	;Load the DLL count
		SUBS	R14,R14,#1		;Chop one off the counter
		STRNE	R14,[R0,#dl_clients]	;Store the count back
		LDMNEFD	R13!,{PC}^		;Return to caller
		LDMFD	R13!,{R14}		;Pull back return address
		B	dll__free		;Free the DLL -- no clients

00dll_dec	LDR	R1,[R0,#dl_name]	;Point to the DLL's name
		ADRL	R0,msg_errNotShared	;Point to error message
		BL	misc_error		;Turn into a real error
		LDMFD	R13!,{R1,R14}		;Pull back return address
		ORRS	PC,R14,#V_flag		;Return the error

		LTORG

; --- dll_instvars ---
;
; On entry:	R0 == pointer to DLL
;		R1 == pointer to data
; On exit:	--

		EXPORT	dll_instvars
dll_instvars	ROUT

		STMFD	R13!,{R1-R6,R14}	;Stack registers

		; --- Store pointer if needs be ---

		MOV	R3,R0			;Look after this pointer
		LDR	R2,[R3,#dl_instBase]	;Find start of data block
		SUB	R4,R1,R2		;Calculate relocation
		LDR	R14,[R3,#dl_next]	;Is there a next pointer?
		CMP	R14,#-1			;Is the DLL shared?
		BNE	%00dll_instvars		;Yes -- don't store reloc
		STR	R4,[R3,#dl_wspace]	;Store in special offset

		; --- Copy the data across ---

00dll_instvars	LDR	R0,[R3,#dl_bversion]	;Get the format version
		CMP	R0,#100			;Is it too old for this?
		BLE	%90dll_instvars		;Yes -- skip ahead then

		LDR	R5,[R3,#dl_zinitBase]	;Get zero-init base address
		LDR	R6,[R3,#dl_zinitLimit]	;Get zero-init limit address

		MOV	R0,R1			;Move destination pointer
		MOV	R1,R2			;Find start of data block
		CMP	R5,R6			;Is there a zinit area?
		MOVLT	R2,R5			;Yes -- use zinit base addr
		LDRGE	R2,[R3,#dl_instLimit]	;No -- use data end address
		SUB	R2,R2,R1		;Convert pointer to length
		BL	misc_memcpy		;Copy the data across

		; --- If the format supports it, do zero-initing ---

		CMP	R5,R6			;Are these sensible?
		ADDLT	R0,R5,R4		;Yes -- add relocation
		ADDLT	R1,R6,R4
		BLLT	misc_zinit		;And zero-init the area

		B	%99dll_instvars

		; --- Copy the whole lot across ---

90dll_instvars	MOV	R0,R1			;Move destination pointer
		MOV	R1,R2			;Find start of data block
		LDR	R2,[R3,#dl_instLimit]	;Find end of data block
		SUB	R2,R2,R1		;Convert pointer to length
		BL	misc_memcpy		;Copy the data across

99dll_instvars	LDMFD	R13!,{R1-R6,PC}^	;Return to caller

		LTORG

; --- dll_findWorkspace ---
;
; On entry:	R0 == DLL handle
; On exit:	R0 == pointer to workspace for the DLL
;
; So that the caller can free it when they kill the DLL

		EXPORT	dll_findWorkspace
dll_findWorkspace ROUT

		STMFD	R13!,{R14}		;Stack link register
		LDR	R14,[R0,#dl_instBase]	;Find workspace base address
		LDR	R0,[R0,#dl_wspace]	;Find the relocation
		ADD	R0,R0,R14		;Relocate the address
		LDMFD	R13!,{PC}^		;Return to the caller

		LTORG

; --- dll_convreloc ---
;
; On entry:	R0 == pointer to DLL
;		R1 == pointer to data block
; On exit:	R0 == relocation offset to use

		EXPORT	dll_convreloc
dll_convreloc	ROUT

		STMFD	R13!,{R14}		;Keep link register
		LDR	R14,[R0,#dl_instBase]	;Find base of static data
		SUB	R0,R1,R14		;Convert to a relocation
		LDMFD	R13!,{PC}^		;Return to caller

		LTORG

; --- dll_info ---
;
; On entry:	R0 == pointer to DLL
; On exit:	R0 preserved
;		R1 == pointer to DLL name
;		R2 == version number
;		R3 == pointer to copyright string
;		R4 == size of instance variables

		EXPORT	dll_info
dll_info	ROUT

		LDR	R3,[R0,#dl_instBase]	;Find base of variables
		LDR	R4,[R0,#dl_instLimit]	;Find limit of variables
		SUB	R4,R4,R3		;Turn this into length
		LDR	R1,[R0,#dl_name]	;Point to DLL's name
		LDR	R2,[R0,#dl_version]	;Load version number
		LDR	R3,[R0,#dl_copyright]	;Point to copyright string
		MOVS	PC,R14			;Return to caller

		LTORG

; --- dll_datasize ---
;
; On entry:	R0 == pointer to DLL
; On exit:	R0 == number of bytes to allocate

		EXPORT	dll_datasize
dll_datasize	ROUT

		STMFD	R13!,{R14}		;Keep link register
		LDR	R14,[R0,#dl_instBase]	;Find base of area
		LDR	R0,[R0,#dl_instLimit]	;Find end of area
		SUB	R0,R0,R14		;Convert to length
		LDMFD	R13!,{PC}^		;Return to caller

		LTORG

;----- Private routines -----------------------------------------------------

; --- dll__createName ---
;
; On entry:	R0 == pointer to name to load
;		R1 == version number to check for
; On exit:	R0 == DLL handle if found in memory, or 0
;		R1 == pointer to filename of DLL to use
;               R2 == pointer to leafname of DLL if not in memory

dll__createName	ROUT

		STMFD	R13!,{R9-R11,R14}	;Stash registers somewhere

		; --- Start off by putting `DLL:' in the buffer ---
		;
		; We may not need it, but at least it's there if we do.

		MOV	R11,R0			;Remember this name pointer
		MOV	R10,R1			;And remember the version
		LDR	R14,dll__pathPrefix	;Load the path prefix
		ADR	R0,misc__sharedBuf+8	;Point to shared buffer
		STR	R14,[R0],#4		;And store that away

		; --- Now set up for our search ---

		ADR	R9,misc__sharedBuf+8	;Filename assumed to be leaf
		MOV	R2,R0			;Assume DLL name is leaf

		; --- The first character is special ---

		LDRB	R14,[R11],#1		;Load the next byte out
		CMP	R14,#'['		;Is this a new-style leaf?
		BEQ	%20dll__createName	;Yes -- skip to copy rest

		; --- Now enter the main loop ---

00		CMP	R14,#'['		;Found new-style name delim?
		ADREQ	R9,misc__sharedBuf+8+4	;Yes -- ignore `DLL:' bit
		MOVEQ	R2,R0			;DLL name starts here
		BEQ	%20dll__createName	;Yes -- skip to copy rest

		STRB	R14,[R0],#1		;Store character in output
		CMP	R14,#'.'		;Found a path separator?
		ADREQ	R9,misc__sharedBuf+8+4	;Yes -- ignore `DLL:' bit
		MOVEQ	R2,R0			;DLL name is here or later
		CMP	R14,#&21		;Is this the end of it all?
		LDRCSB	R14,[R11],#1		;No -- get the next byte
		BCS	%b00			;And keep on looping

		MOV	R14,#0			;Terminate the string
		STRB	R14,[R0,#-1]		;Stuff that over old term
		B	%50dll__createName	;Now go and find the DLL

		; --- Mess about with new-style names ---

20		LDRB	R14,[R11],#1		;Load another byte out
		CMP	R14,#&21		;Dropped off the end?
		MOVCC	R14,#']'		;Yes -- pretend it was right
		CMP	R14,#']'		;Finished yet?
		MOVEQ	R14,#0			;Yes -- zero terminate
		STRB	R14,[R0],#1		;Store in the buffer
		BNE	%20dll__createName	;And keep looping

		; --- We've found all the names now ---

50		MOV	R0,R2			;Point to the DLL leaf
		MOV	R1,R10			;Find the version number
		BL	dll_find		;Try to find the dll
		LDMVCFD	R13!,{R9-R11,PC}	;It was OK -- return then

		MOV	R0,#0			;Couldn't find the DLL
		MOV	R1,R9			;Point to the filename
		LDMFD	R13!,{R9-R11,PC}^	;Restore registers

dll__pathPrefix	DCB	"dll:",0		;Path variable to search
		ALIGN

		LTORG

; --- dll__free ---
;
; On entry:	R0 == DLL handle to release
; On exit:	--

dll__free	ROUT

		STMFD	R13!,{R1,R2,R14}	;Keep registers safe

		; --- Mangle the list nicely ---

		LDR	R1,[R0,#dl_next]	;Get pointer to next DLL
		LDR	R2,[R0,#dl_prev]	;And pointer to previous one
		CMP	R1,#0			;Is there a next one?
		STRNE	R2,[R1,#dl_prev]	;Yes -- fix up previous ptr
		CMP	R2,#0			;Is there a previous one?
		ADREQ	R2,dll__list		;No -- point to list head
		STR	R1,[R2,#dl_next]	;Fix up the pointer

		; --- Free the block ---

		MOV	R2,R0			;Point to the block
		MOV	R0,#7			;Magic number to free it
		SWI	XOS_Module		;Free it now
		LDMFD	R13!,{R1,R2,PC}		;Return to caller

		LTORG

; --- dll__load ---
;
; On entry:	R0 == pointer to block to load
;		R1 == pointer to filename
; On exit:	--

dll__load	ROUT

		STMFD	R13!,{R1-R5,R9-R11,R14}
		MOV	R10,R0			;Keep pointer to block safe
		MOV	R9,R1			;And look after the filename

		; --- Load the DLL into the buffer ---

		MOV	R0,#16			;Load a file into memory
		MOV	R3,#(1<<31)		;Resync code areas when done
		ADD	R2,R10,#dl_extra	;Leave space for extra info
		SWI	XOS_File		;Try to load the file
		LDMVSFD	R13!,{R1-R5,R9-R11,PC}	;Return the error if any

		; --- Check that it really is a DLL ---

		LDR	R0,=dl_MAGIC		;Find the magic DLL number
		LDR	R1,[R10,#dl_magic]	;Get the version from DLL
		CMP	R0,R1			;Check they're the same
		BNE	%40dll__load		;If not, give an error

		LDR	R0,=dl_VERSION		;Get known version number
		LDR	R1,[R10,#dl_bversion]	;Get DLL's version number
		CMP	R0,R1			;How do they match up?
		BLT	%41dll__load		;If too new, complain

		; --- Relocate the image ---

		MOV	R14,PC			;Set up return address
		ADD	PC,R10,#dl_relocate	;Perform the relocation

		; --- Fill in the C library stubs ---

		LDR	R0,[R10,#dl_stubs]	;Get pointer to stubs table
		CMP	R0,R10			;If it isn't invalid
		BLS	%10dll__load		;... don't skip ahead
		BL	misc_copyStubs		;Copy the clib branch table
		LDMVSFD	R13!,{R1-R5,R9-R11,PC}	;Return the error if any

		; --- Mark the DLL as being non-shared ---
		;
		; Here we also clear the data relocation for non-shared DLLs
		; since they don't need to be multiply instantiated.  We *do*
		; allow extension DLLs to be multiply instantiated, though.
		; Clearing the relocation also has the side effect of
		; clearing a *shared* DLL's client counter.

10dll__load	MOV	R0,#-1			;Non-shared is indicated...
		STR	R0,[R10,#dl_next]	;... by dl_next being -1
		MOV	R0,#0			;Also, clear relocation
		STR	R0,[R10,#dl_wspace]	;(also clears client count)

		; --- That's it, then ---

		LDMFD	R13!,{R1-R5,R9-R11,PC}^	;Return to caller

		; --- Give an error about a bad DLL image ---

40dll__load	ADRL	R0,msg_errNotADLL	;Point to error message
		B	%49dll__load		;Go to error generation bit

		; --- Give an error about an unrecognised format version ---

41dll__load	ADRL	R0,msg_errTooNew	;Point to error message
		B	%49dll__load		;Go to error generation bit

		; --- Create an error and leave ---

49dll__load	MOV	R1,R9			;Point to the filename
		BL	misc_error		;Fill in the error message
		LDMFD	R13!,{R1-R5,R9-R11,R14}	;Restore registers
		ORRS	PC,R14,#V_flag		;And return an error

		LTORG

; --- dll__divide ---
;
; On entry:	R0 == dividend
; 		R1 == divisor
; On exit:	R0 == quotient
;		R1 == remainder
;
; This routine is mostly uncommented, 'cos I copied from Acorn's Assembler
; documentation, and it should be accurate.  It's not exactly optimised,
; but it should hold up to the sort of treatment I'm going to be giving it
; (very delicate and occasional use).

dll__divide	ROUT

		STMFD	R13!,{R2,R3,R14}
		CMP	R1,#0			;Check for stupidity
		BEQ	%10dll__divide		;If stupid, give an error

		MOV	R3,R1
		CMP	R3,R0,LSR #1
00dll__divide	MOVLS	R3,R3,LSL #1
		CMP	R3,R0,LSR #1
		BLS	%00dll__divide

		MOV	R2,#0
01dll__divide	CMP	R0,R3
		SUBCS	R0,R0,R3
		ADC	R2,R2,R2
		MOV	R3,R3,LSR #1
		CMP	R3,R1
		BCS	%01dll__divide

		MOV	R1,R0			;Move results into right...
		MOV	R0,R2			;... registers
		LDMFD	R13!,{R2,R3,PC}^

10dll__divide	ADRL	R0,msg_errDivide	;Point to error message
		LDMFD	R13!,{R2,R3,R14}	;Retreive registers
		ORRS	PC,R14,#V_flag		;And returnt the error

		LTORG

;----- That's all folks -----------------------------------------------------

		END
