;
; app.s
;
; Handling of application blocks
;
;  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.appblock
		GET	sh.linkblock
		GET	sh.dllblock

		GET	sh.misc
		GET	sh.suballoc
		GET	sh.dll

		GET	sh.messages

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

		AREA	|DLLM$$Code|,CODE,READONLY

		GBLL	debug
debug		SETL	{FALSE}

; --- app_init ---
;
; On entry:	--
; On exit:	--

		EXPORT	app_init
app_init	ROUT

		STMFD	R13!,{R1,R2,R14}	;Save some registers

		; --- Save workspace address ---
		;
		; This is so that app__epilogue can find our workspace.

		STR	R12,app__pw		;Store data relocation

		; --- Now locate the OS's pid word ---
		;
		; We do this using a table indexed by the OS version on the
		; grounds that it's easier to update if the address changes.
		; This is unlikely however, since DDEUtils uses this address
		; too, and it's right before the VDU driver workspace at
		; &1000, which seems to indicate it's there forever.

		MOV	R0,#&81			;Read the OS version number
		MOV	R1,#0			;Set up the odd arguments
		MOV	R2,#255			;This is really very strange
		SWI	XOS_Byte		;Do the read operation

		ADR	R14,app__verTable	;Point to the table
00		LDMIA	R14!,{R0,R2}		;Load version and address
		CMP	R0,R1			;How do the versions compare?
		BCC	%b00			;No match -- keep going

		STR	R2,app__pidAddr		;If matched, store address
		LDMFD	R13!,{R1,R2,PC}^	;Restore caller's registers
		BICS	PC,R14,#V_flag		;And return to caller

app__verTable	DCD	-1,&FF8			;All versions have PID here

		LTORG

app__pw		DCD	0			;Where to store private word

; --- app_checkQuit ---
;
; On entry:	--
; On exit:	--

		EXPORT	app_checkQuit
app_checkQuit	ROUT

		LDR	R0,app__list		;Get list head pointer
		CMP	R0,#0			;Are there any applications?
		MOVEQS	PC,R14			;No -- that's OK then
		ADRL	R0,msg_errInUse		;Point to error
		ORRS	PC,R14,#V_flag		;Return an error

		LTORG

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

		EXPORT	app_findDLL
app_findDLL	ROUT

		STMFD	R13!,{R11,R14}		;Stack registers nicely
		BL	app__sFindDLL		;Find the DLL etc.
		BVC	app__ok			;If it worked, make permanent
		B	app__dead		;Otherwise, tidy up

		LTORG

; --- app_loseDLL ---
;
; On entry:	R0 == pointer to DLL to lose
; On exit:	--

		EXPORT	app_loseDLL
app_loseDLL	ROUT

		STMFD	R13!,{R1-R3,R11,R14}	;Look after some registers
		MOV	R11,R0			;Keep hold of pointer

		; --- Find the application's handle ---

		BL	app_getHandle		;Find application's time
		BLVC	app__find		;Find the application block
		LDMVSFD	R13!,{R1-R3,R11,PC}	;Return an error if any

		; --- Find the link block ---

		MOV	R3,R0			;Keep hold of app pointer
		MOV	R1,R11			;Point to application
		BL	app__findLink		;Find the link block
		LDMVSFD	R13!,{R1-R3,R11,PC}	;Return an error if any

		; --- Remove the link block ---

		MOV	R1,R0			;Point to the link block
		MOV	R0,R3			;Point to the application
		BL	app__freeLink		;Free the link block up

		; --- Decrement the DLL count ---
		;
		; This will release the DLL if it no longer has any clients

		MOV	R0,R11			;Point to DLL
		BL	dll_dec			;Decrement its counter

		; --- Uncache the current link if we've removed it ---

		LDR	R0,[R3,#app_cachedll]	;Find which DLL we cached
		CMP	R0,R11			;Have we just unlinked it?
		MOVEQ	R0,#0			;Yes -- clear out the handle
		STREQ	R0,[R3,#app_cachedll]	;Won't match any more
		STREQ	R0,[R3,#app_cacheptr]

		; --- Return, deleting application if necessary ---

		MOV	R0,R3			;Point to application
		LDMFD	R13!,{R1-R3,R11,R14}	;Unstack registers
		B	app__freeUnused		;Kill app block if no DLLs

		LTORG

; --- app_setname ---
;
; On entry:	R0 == pointer to application's name
; On exit:	--

		EXPORT	app_setname
app_setname	ROUT

		STMFD	R13!,{R1,R14}		;Store registers and things
		MOV	R1,R0			;Keep pointer to the name
		BL	app__add		;Make sure the app exists

		ADDVC	R0,R0,#app_name		;Point to the name
		BLVC	misc_strcpy		;Copy the string across
		LDMFD	R13!,{R1,PC}		;Return to caller

		LTORG

; --- app_fromtable ---
;
; On entry:	R0 == pointer to start of external DLL block
;		R1 == pointer to limit of same
; On exit:	--

		EXPORT	app_fromtable
app_fromtable	ROUT

		STMFD	R13!,{R11,R14}		;Look after registers
		BL	app_sfromtbl		;Do primitive operation
		BVC	app__ok			;If it worked, to gadget
		B	app__dead		;If it failed,tidy up

		LTORG

; --- app_sfromtbl ---
;
; On entry:	R0 == pointer to start of external DLL block
;		R1 == pointer to limit of same
; On exit:	--

		EXPORT	app_sfromtbl
app_sfromtbl	ROUT

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

		MOV	R10,R0			;Keep pointer to base
		MOV	R11,R1			;Keep pointer to limit

00app_sfromtbl	CMP	R10,R11			;Is there anything to do?
		LDMEQFD	R13!,{R1-R5,R10,R11,PC}^ ;No -- return to caller

		; --- Load an entry from the table ---

		LDMIA	R10!,{R0-R3}		;Load values from table
		BL	app__sFindDLL		;Try to find the DLL
		LDMVSFD	R13!,{R1-R5,R10,R11,PC}	;Return the error if any

		; --- Fill in the veneer table ---

		MOV	R1,R2			;Point to first name to load
		MOV	R2,R0			;Keep pointer to DLL base
		LDR	R5,[R2,#dl_entries]	;Load the entry count
		BIC	R4,R5,#&FF000000	;Clear entry type bits

01app_sfromtbl	LDR	R0,[R3,#0]		;Load value from table
		CMP	R0,R4			;Is it a valid ordinal?
		BHS	%05app_sfromtbl		;No -- look up the name then

		LDR	R14,[R2,#dl_eveneer]	;Yes -- load entry point base
		TST	R5,#dl_shortEntries	;Are the entries short?
		ADDEQ	R0,R14,R0,LSL #4	;No -- find base of veneer
		LDRNE	R0,[R14,R0,LSL #2]	;Yes -- load address
		B	%10app_sfromtbl		;And skip past name lookup

05app_sfromtbl	LDRB	R0,[R1]			;Load first byte from string
		CMP	R0,#0			;Is it a zero (end of list)?
		BEQ	%00app_sfromtbl		;Yes -- do next table entry

		; --- Find entry point address ---

		MOV	R0,R2			;Point to DLL base
		BL	dll_findEntry		;Find the entry point
		LDMVSFD	R13!,{R1-R5,R10,R11,PC}	;Return the error if any
		MOV	R4,#0			;Remember to bump name

		; --- Convert it into a branch ---

10app_sfromtbl	SUB	R0,R0,R3		;Convert to offset from R3
		SUB	R0,R0,#8		;Subtract 8 (pipeline)
		MOV	R0,R0,LSR #2		;Shift right to word align
		BIC	R0,R0,#&FF000000	;Clear top (opcode and cond)
		ORR	R0,R0,#&EA000000	;Put in BAL (Branch always)
		STR	R0,[R3],#4		;Store in veneer table
		CMP	R4,#0			;Did we look up a name?
		BNE	%01app_sfromtbl		;No -- examine next entry pt

		; --- Find next entry point address ---

02app_sfromtbl	LDRB	R0,[R1],#1		;Load a byte from the name
		CMP	R0,#0			;Is it the string end?
		BNE	%02app_sfromtbl		;No -- get another
		B	%01app_sfromtbl		;Fill in another entry point

		LTORG

; --- app_findNamed ---
;
; On entry:	R0 == name of an application
; On exit:	R0 == pointer to application block base

		EXPORT	app_findNamed
app_findNamed	ROUT

		STMFD	R13!,{R1-R3,R14}	;Preserve registers
		MOV	R1,R0			;Keep pointer to string
		LDR	R3,app__list		;And load the pointer to list

00app_findNamed	CMP	R3,#0			;Is this the end of the line?
		BEQ	%40app_findNamed	;Yes -- give an error
		ADD	R0,R3,#app_name		;Find the name string
		MOV	R2,#0			;Caseless compare
		BL	misc_strcmp		;Compare the strings
		LDRNE	R3,[R3,#app_next]	;If no match, move on...
		BNE	%00app_findNamed	;... and try again
		MOV	R0,R3			;Point to DLL (give handle)
		LDMFD	R13!,{R1-R3,PC}^	;And return to caller

40app_findNamed	ADRL	R0,msg_errAppNotFound	;Couldn't find DLL name
		BL	misc_error		;... create an error message
		LDMFD	R13!,{R1-R3,R14}	;... restore registers
		ORRS	PC,R14,#V_flag		;... and return an error

		LTORG

; --- app_setBtable ---
;
; On entry:	R0 == pointer to entry point table
;		R1 == pointer to name table
; On exit:	--

		EXPORT	app_setBtable
app_setBtable	ROUT

		STMFD	R13!,{R1,R2,R14}	;Stick 'em on the stack
		MOV	R2,R0			;Keep this safe
		BL	app__add		;Create an entry for the app
		STRVC	R2,[R0,#app_btable]	;Store the entry table ptr
		STRVC	R1,[R0,#app_nametable]	;Store the name table ptr
		LDMFD	R13!,{R1,R2,PC}		;Return to caller

		LTORG

; --- app_appEntry ---
;
; On entry:	R0 == pointer to name to find
; On exit:	R0 == pointer to entry point

		EXPORT	app_appEntry
app_appEntry	ROUT

		STMFD	R13!,{R1,R2,R14}	;Stack registers away

		; --- Find the application ---

		MOV	R2,R0			;Point to name start
		BL	app_getHandle
		BLVC	app__find		;Try to find the app block
		LDMVSFD	R13!,{R1,R2,PC}		;If not there, error

		; --- Get the entry table info out ---

		LDR	R1,[R0,#app_nametable]	;Find name table address
		LDR	R0,[R0,#app_btable]	;Find entry table address
		CMP	R0,#0			;Is there no entry table?
		BEQ	%50app_appEntry		;No -- complain about it

		; --- Find the entry point ---

		BL	dll_appEntry		;Do the actual find
		LDMFD	R13!,{R1,R2,PC}		;Return to caller

		; --- Application has no entry points ---

50app_appEntry	ADRL	R0,msg_errAppNoEntry	;Point to error message
		LDMFD	R13!,{R1,R2,R14}	;Unstack registers
		ORRS	PC,R14,#V_flag		;Return the error to caller

		LTORG

		ALIGN

; --- app_fixExtension ---
;
; On entry:	R0 == pointer to name table
;		R1 == pointer to branch table to fill in
; On exit:	--

		EXPORT	app_fixExtension
app_fixExtension

		STMFD	R13!,{R14}		;Save return address
		BL	app_fix			;Do the real job
		LDMVSFD	R13!,{PC}		;If it failed, return now
		MOV	R0,#0			;Otherwise do global resync
		SWI	XOS_SynchroniseCodeAreas ;Do that then
		LDMFD	R13!,{R14}		;Restore return address
		BICS	PC,R14,#V_flag		;And return with V clear

		LTORG

; --- app_fix ---
;
; On entry:	R0 == pointer to name table
;		R1 == pointer to branch table to fill in
; On exit:	--

		EXPORT	app_fix
app_fix		ROUT

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

		; --- Find the application ---

		MOV	R2,R0			;Point to name start
		MOV	R3,R1			;Keep entry table safe
		BL	app_getHandle
		BLVC	app__find		;Try to find the app block
		LDMVSFD	R13!,{R1-R6,PC}		;If not there, error

		; --- Get the entry table info out ---

		LDR	R4,[R0,#app_btable]	;Find entry table address
		LDR	R5,[R0,#app_nametable]	;Find name table address
		MOV	R6,#&1000		;Guess number of entries
		CMP	R4,#0			;Is there no entry table?
		BEQ	%50app_fix		;No -- complain about it

		; --- Set up for a nice loop ---

00app_fix	LDR	R0,[R3,#0]		;Load the word from btable
		CMP	R0,R6			;Is it moderately sensible?
		ADDLO	R0,R4,R0,LSL #2		;Yes -- find the entry
		MOVLO	R14,#1			;Remember we done this
		BLO	%10app_fix		;And fill in the branch
		LDRB	R0,[R2]			;Get the first entry byte
		CMP	R0,#0			;Is it the end of the table?
		LDMEQFD	R13!,{R1-R6,PC}^	;Yes -- we did it OK

		; --- Find another entry point address ---

		MOV	R0,R4			;Point to entry table
		MOV	R1,R5			;Point to name table
		BL	dll_appEntry		;Find the actual entry name
		LDMVSFD	R13!,{R1-R6,PC}		;If not there, return error
		MOV	R6,#0			;Move on to next string

		; --- Convert it to a branch instruction ---

10app_fix	SUB	R0,R0,R3		;Convert to offset from R3
		SUB	R0,R0,#8		;Subtract 8 (pipeline)
		MOV	R0,R0,LSR #2		;Shift right to word align
		BIC	R0,R0,#&FF000000	;Clear top (opcode and cond)
		ORR	R0,R0,#&EA000000	;Put in BAL (Branch always)
		STR	R0,[R3],#4		;Store in veneer table

		; --- Find next entry point address ---

		CMP	R6,#0			;Do we need to move on?
		BNE	%00app_fix		;No -- just loop then
01app_fix	LDRB	R0,[R2],#1		;Load a byte from the name
		CMP	R0,#0			;Is it the string end?
		BNE	%01app_fix		;No -- get another
		B	%00app_fix		;Fill in another entry point

		; --- Application has no entry points to offer ---

50app_fix	ADRL	R0,msg_errAppNoEntry	;Point to error message
		LDMFD	R13!,{R1-R6,R14}	;Unstack registers
		ORRS	PC,R14,#V_flag		;Return the error to caller

		LTORG

; --- app_listDLLs ---
;
; On entry:	R0 == pointer to name of an application
; On exit:	--

		EXPORT	app_listDLLs
app_listDLLs	ROUT

		STMFD	R13!,{R1,R14}		;Stack registers nicely
		BL	app_findNamed		;Find the application block
		LDMVSFD	R13!,{R1,PC}		;Return if not found
		LDR	R1,[R0,#app_dlls]	;Load head of DLL list
		CMP	R1,#0			;This shouldn't be 0
		BEQ	%01app_listDLLs		;If it is, print a message

		BL	dll_writeTitle		;Display the line along top

		; --- Main loop thing ---

00app_listDLLs	LDR	R0,[R1,#lk_dll]		;Point to the DLL
		BL	dll_writeInfo		;Display information about it
		LDR	R1,[R1,#lk_next]	;Move onto the next one
		CMP	R1,#0			;Is that all?
		BNE	%00app_listDLLs		;No -- continue round

		LDMFD	R13!,{R1,PC}^		;Return to caller

		; --- No DLLs to list.  Hmm... ---

01app_listDLLs	ADRL	R0,msg_noDLLsForApp	;Point to the message
		SWI	OS_Write0		;And display it
		LDMFD	R13!,{R1,PC}^		;Return to caller

		LTORG

; --- app_listUsing ---
;
; On entry:	R0 == pointer to a DLL name
; On exit:	--

		EXPORT	app_listUsing
app_listUsing	ROUT

		STMFD	R13!,{R1,R2,R8-R11,R14}	;Stack registers

		; --- Check that the DLL exists ---

		MOV	R8,R0			;Keep the name pointer safe
		MOV	R1,#0			;Don't care which version
		BL	dll_find		;Try to find it
		BVS	%11app_listUsing	;If it's not there, error

		; --- Start a loop through the apps ---

		LDR	R11,app__list		;Find the list head pointer
		MOV	R10,#0			;No matching DLLs found yet
		CMP	R11,#0			;Are there any apps?
		BEQ	%10app_listUsing	;No -- print a message then

		; --- Start a loop through the DLLs with this app

00app_listUsing	LDR	R9,[R11,#app_dlls]	;Find the DLL list
		CMP	R9,#0			;Are there any DLLs?
		BEQ	%03app_listUsing	;No -- move on to the next

		; --- Loop through DLLs on this app ---

01app_listUsing	LDR	R0,[R9,#lk_dll]		;Point to the DLL base
		MOV	R1,R8			;Point to DLL name
		MOV	R2,#0			;Don't care what version
		BL	dll_compare		;Compare with stuff given
		CMP	R0,#1			;Is there a match?
		BNE	%02app_listUsing	;Move onto the next one

		; --- Print out info about this app ---

		CMP	R10,#1			;Have we printed the title?
		ADRNEL	R0,msg_appHeader	;No -- point to header
		SWINE	XOS_Write0		;And print it
		MOV	R10,#1			;We've printed it now
		ADD	R0,R11,#app_name	;Point to application name
		MOV	R1,#12			;Field width
		BL	dll_field		;Print it out
		LDR	R0,[R9,#lk_dll]		;Find DLL pointer again
		LDR	R0,[R0,#dl_version]	;Load version number
		BL	dll_convertVersion	;Convert it into a string
		SWI	XOS_Write0		;Write it out as a string
		SWI	XOS_NewLine		;And move onto the next line

		; --- Finish off loop through app's DLLs ---

02app_listUsing	LDR	R9,[R9,#lk_next]	;Move on to next link block
		CMP	R9,#0			;Anything to do here?
		BNE	%01app_listUsing	;Yes -- check that one too

		; --- Finish off loop through all apps ---

03app_listUsing	LDR	R11,[R11,#app_next]	;Move on to next application
		CMP	R11,#0			;Is there any more to do?
		BNE	%00app_listUsing	;Yes -- do its DLLs

		CMP	R10,#0			;Were any messages printed?
		BEQ	%10app_listUsing	;No -- print one
		LDMFD	R13!,{R1,R2,R8-R11,PC}^	;Return to caller

		; --- No matching DLLs were found ---

10app_listUsing	ADRL	R0,msg_noAppsForDLL	;Point to message skeleton
		MOV	R1,R8			;Point to the DLL name
		BL	misc_subst		;Do the substitution
		SWI	XOS_Write0		;Display the string
		LDMFD	R13!,{R1,R2,R8-R11,PC}^

		; --- Print an error message about this ---

11app_listUsing	MOV	R1,R8			;Point to DLL name
		ADRL	R0,msg_errDLLNotInMem	;Point to error block
		BL	misc_error		;Create error message fully
		LDMFD	R13!,{R1,R2,R8-R11,R14}	;Unstack all registers
		ORRS	PC,R14,#V_flag		;Return to caller with error

		LTORG

; --- app_list ---
;
; On entry:	--
; On exit:	--

		EXPORT	app_list
app_list	ROUT

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

		; --- Prepare for a loop ---

		LDR	R1,app__list		;Point to first entry
		CMP	R1,#0			;Are there any entries?
		BEQ	%10app_list		;No -- give a message

		; --- Display entries in order ---

00app_list	ADD	R0,R1,#app_name		;Point to name in structure
		SWI	XOS_Write0		;Display the app's name
		SWI	XOS_NewLine		;Display a new line char
		LDR	R1,[R1,#app_next]	;Get next entry in the list
		CMP	R1,#0			;Is it the end yet?
		BNE	%00app_list		;No -- do the next one
		LDMFD	R13!,{R1,PC}^		;Return to caller

		; --- Display message about no apps ---

10app_list	ADRL	R0,msg_noApps		;Point to the message
		SWI	XOS_Write0		;Display it on the screen
		LDMFD	R13!,{R1,PC}^		;Return to caller

		LTORG

; --- app_dying ---
;
; On entry:	--
; On exit:	--

		EXPORT	app_dying
app_dying	ROUT

		STMFD	R13!,{R14}		;Stack return address
		BL	app_getHandle		;Find application's time
		BLVC	app__find		;Find the application block
		LDMFD	R13!,{R14}		;Retrieve the return address
		BVC	app_kill		;If there's no error, kill it
		ORRS	PC,R14,#V_flag		;Return to caller with error

		LTORG

; --- app_kill --
;
; On entry:	R0 == pointer to application base
; On exit:	--

		EXPORT	app_kill
app_kill	ROUT

		STMFD	R13!,{R1,R2,R14}	;Stack registers nicely
		MOV	R2,R0			;Keep hold of this pointer

		; --- Free each DLL link ---

		LDR	R1,[R0,#app_dlls]	;Find head of link list
		CMP	R1,#0			;Are there any DLLs?
		BEQ	%01app_kill		;No -- don't free them then

00app_kill	LDR	R0,[R1,#lk_dll]		;Find the DLL pointer
		BL	dll_dec			;Decrement its counter
		MOV	R0,R1			;Point to old block
		LDR	R1,[R0,#lk_next]	;Point to next block
		BL	sub_free		;Free the old block
		CMP	R1,#0			;Are there any more to do?
		BNE	%00app_kill		;Yes -- do the next one

		; --- Free the application block ---

01app_kill	MOV	R0,R2			;Point to application block
		LDMFD	R13!,{R1,R2,R14}	;Restore registers
		B	app__free		;Free the application block

		LTORG

; --- app_killAll ---
;
; On entry:	--
; On exit:	--

		EXPORT	app_killAll
app_killAll	ROUT

		STMFD	R13!,{R1,R2,R14}

		; --- Start up the main loop ---

		LDR	R2,app__list		;Point to application list
		CMP	R2,#0			;Is there anything to do?
		BEQ	%01app_killAll		;No -- skip the loop
		MOV	R0,#7			;Free memory reason code

00app_killAll	LDR	R1,[R2,#app_next]	;Find next block in the list
		SWI	XOS_Module		;Free the block
		MOVS	R2,R1			;Point to the next one
		BNE	%00app_killAll		;Loop round for the next one

01app_killAll	STR	R2,app__list		;Clear out list head pointer
		STR	R2,app__cachePtr	;Clear out cached pointer
		STR	R2,app__cacheHnd	;Clear out cached handle
		LDMFD	R13!,{R1,R2,PC}^	;Return to caller

		LTORG

; --- app_instvars ---
;
; On entry:	R0 == anything for first call, or pointer to data block
;		R4 == 0 for first call, or pointer to link block
; On exit:	R0 == size of block required
;		R4 == pointer to next link block, or 0 to end

		EXPORT	app_instvars
app_instvars	ROUT

		STMFD	R13!,{R1,R14}		;Keep hold of link register
		CMP	R4,#0			;Is this the first call?
		BNE	%00app_instvars		;No -- skip ahead quickly

		; --- Find start of application list ---

		BL	app_getHandle		;Get app's start time
		BLVC	app__find		;Locate the application
		LDMVSFD	R13!,{R1,PC}^		;Return to caller on error
		LDR	R4,[R0,#app_dlls]	;Load list head pointer
		B	%01app_instvars		;Don't try registering vars

		; --- Register instance variables ---

00app_instvars	CMP	R0,#0			;Check the allocator worked
		BEQ	%40app_instvars		;If not, give an error
		MOV	R1,R0			;Point to the new block
		LDR	R0,[R4,#lk_dll]		;Point to the DLL block
		BL	dll_instvars		;Register the instance vars
		LDR	R0,[R4,#lk_dll]		;Point to the DLL block again
		BL	dll_convreloc		;Convert to a relocation
		STR	R0,[R4,#lk_work]	;Store relocation

		; --- Is this the end of the line? ---

02app_instvars	LDR	R4,[R4,#lk_next]	;Move to next block in list
01app_instvars	CMP	R4,#0			;Is this the end?
		LDMEQFD	R13!,{R1,PC}^		;Yes -- return to caller
		LDR	R0,[R4,#lk_work]	;Find workspace pointer
		CMP	R0,#0			;Is there one yet?
		BNE	%02app_instvars		;Yes -- skip this one
		LDR	R0,[R4,#lk_dll]		;Point to DLL
		BL	dll_datasize		;Find how much space we need
		CMP	R0,#0			;Is there any required?
		BEQ	%02app_instvars		;No -- skip to next block
		LDMFD	R13!,{R1,PC}^		;Return -- that's done

40app_instvars	ADRL	R0,msg_errDLLNoMem	;Point to error message
		LDMFD	R13!,{R1,R14}		;Restore registers
		ORRS	PC,R14,#V_flag		;Return the error

		LTORG

; --- app_giveclib ---
;
; On entry:	R0 == pointer to CLib data (__iob)
; On exit:	--

		EXPORT	app_giveclib
app_giveclib	ROUT

		STMFD	R13!,{R1,R14}		;Stack registers
		MOV	R1,R0			;Keep pointer to data
		BL	app__add		;Make sure there's an app
		STRVC	R1,[R0,#app_clibdata]	;Store the pointer nicely
		LDMFD	R13!,{R1,PC}		;Return to caller nicely

		LTORG

; --- app_appdata ---
;
; On entry:	R0 == application's stack limit
; On exit:	--

		EXPORT	app_appdata
app_appdata	ROUT

		STMFD	R13!,{R1,R14}		;Stack registers
		LDR	R1,[R0,#-536]		;Find application's reloc
		BL	app__add		;Ensure the app is present
		STRVC	R1,[R0,#app_owndata]	;Store the relocation nicely
		LDMFD	R13!,{R1,PC}		;Return to caller nicely

		LTORG

; --- app_findclib ---
;
; On entry:	--
; On exit:	R0 == pointer to CLib data

		EXPORT	app_findclib
app_findclib	ROUT

		STMFD	R13!,{R14}		;Keep return address safe
		BL	app_getHandle		;Find app's start time
		BLVC	app__find		;Look up application's block
		LDRVC	R0,[R0,#app_clibdata]	;Find C Library data pointer
		LDMFD	R13!,{PC}		;Return to caller

		LTORG

; --- app_readstackptr ---
;
; On entry:	--
; On exit:	R0 == `magic cookie' for the stack pointer

		EXPORT	app_readstkptr
app_readstkptr	ROUT

		STMFD	R13!,{R14}		;Keep return address safe
		BL	app_getHandle		;Find app's start time
		BLVC	app__find		;Locate the app's data block
		LDRVC	R0,[R0,#app_stackPtr]	;Find the stack pointer
		LDMFD	R13!,{PC}^		;Return to caller

		LTORG

; --- app_setstkptr ---
;
; On entry:	R0 == `magic cookie' from app_readstkptr
;		R1 == app's current stack limit

		EXPORT	app_setstkptr
app_setstkptr	ROUT

		STMFD	R13!,{R11,R14}		;Keep return address safe

		; --- Find the application block ---

		MOV	R11,R0			;Keep hold of new pointer
		BL	app_getHandle		;Find the app's start time
		BLVC	app__find		;Find the app's data block
		LDMVSFD	R13!,{R11,PC}		;If failed, return error

		; --- Find out what there is to do ---

		LDR	R14,[R0,#app_stackPtr]	;Load the old pointer
		CMP	R11,R14			;How does it shape up?
		LDMEQFD	R13!,{R11,PC}^		;Nothing doing here

		; --- Put back the new stack pointer and set relocation ---

		STR	R11,[R0,#app_stackPtr]	;Store new pointer
		CMP	R11,#0			;Was the stack at top level?
		ADDNE	R0,R0,#app_stack	;Point to base of stack
		SUBNE	R11,R11,#8		;Point to base of stack entry
		LDRNE	R0,[R0,R11]		;Find data relocation
		LDREQ	R0,[R0,#app_owndata]	;If toplevel, get client data
		STR	R0,[R1,#-536]		;Store in stack limit struct
		LDMFD	R13!,{R11,PC}^		;Return to caller

		LTORG

; --- app_prologue ---
;
; On entry:	R0 == return address for routine
;		R1 == pointer to stack limit structure
;		R2 == pointer to base of DLL code
; On exit:	R0 == new return address
;
; Note -- I'm directly accessing the DLL data here, which may be considered
; naughty by some.  I don't care.

		EXPORT	app_prologue
app_prologue	ROUT

		STMFD	R13!,{R9-R11,R14}	;Stack some registers

		; --- Find application block ---

		MOV	R11,R0			;Keep hold of this
		BL	app__add		;Make sure the app exists
		LDMVSFD	R13!,{R9-R11,PC}	;Return the error on fail

		; --- Stack current relocation and return address ---

		LDR	R14,[R0,#app_stackPtr]	;Find the stack pointer
		CMP	R14,#4096		;Where is it currently?
		BGE	%00app_prologue		;Give error on overflow
		ADD	R10,R0,#app_stack	;Point to stack base
		ADD	R10,R10,R14		;Add on stack pointer
		LDR	R9,[R1,#-536]		;Find the current reloc
		STMIA	R10,{R9,R11}		;Store registers on the stack
		MOV	R10,R1			;Keep pointer to stack limit
		ADD	R14,R14,#8		;Bump along stack pointer
		STR	R14,[R0,#app_stackPtr]	;Store stack pointer

		; --- Find workspace pointer ---

		CMP	R2,#0			;What's the DLL pointer?
		LDREQ	R1,[R0,#app_owndata]	;0 -- find app workspace
		BEQ	%10app_prologue		;... and finish search
		LDR	R14,[R2,#dl_next-dl_extra] ;Find DLL's next ptr
		CMP	R14,#-1			;Is it shared?
		LDREQ	R1,[R2,#dl_wspace-dl_extra] ;No -- find DLL workspace
		BEQ	%10app_prologue		;... and finish search

		; --- Get shared DLL workspace pointer ---

		SUB	R1,R2,#dl_extra		;Point to DLL handle
		BL	app__findLink		;Find the link block
		LDMVSFD	R13!,{R9-R11,PC}	;Return error if it failed
		LDR	R1,[R0,#lk_work]	;Find relocation offset

		; --- Round everything off ---

10app_prologue	ADR	R0,app__epilogue	;Point to tidy-up routine
		STR	R1,[R10,#-536]		;Store new data relocation
		LDMFD	R13!,{R9-R11,PC}^	;Return to caller happy

		; --- Stack overflow error ---

00app_prologue	ADRL	R0,msg_errStackOvf	;Point to error
		LDMFD	R13!,{R9-R11,R14}	;Unstack registers
		ORRS	PC,R14,#V_flag		;Return the error

		LTORG

; --- app_restoreHandle ---
;
; On entry:	R0 == an application handle to assume
; On exit:	--

		EXPORT	app_restoreHandle
app_restoreHandle ROUT

		[	:LNOT::DEF:ddeutils_pid

		STMFD 	R13!,{R0-R2,R14}	;Save some registers
		SWI	XOS_GetEnv		;Read current command string
		MOV	R1,R13			;Point to new start time
		SWI	XOS_WriteEnv		;Set the start time back
		LDMFD	R13!,{R0-R2,PC}^	;And return to caller

		|

		MOVS	PC,R14			;No need to do this

		]

		LTORG

; --- app_getHandle ---
;
; On entry:	--
; On exit:	R0 == application's start time
;
; Since the application's start time is held in *five* bytes we use only the
; least significant 4 (the four that change most).

		EXPORT	app_getHandle
app_getHandle	ROUT

		[	:LNOT::DEF:ddeutils_pid

		STMFD 	R13!,{R1,R2,R14}	;Save some registers
		SWI	XOS_GetEnv		;Get the session info
		LDR	R0,[R2,#0]		;Load the start time nicely
		LDMFD	R13!,{R1,R2,PC}^	;And return to caller

		|

		LDR	R0,app__pidAddr		;Load the PID address
		LDR	R0,[R0]			;And load the current PID
		MOVS	PC,R14			;Return to caller when done

		]

		LTORG

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

; --- app__epilogue ---
;
; On entry:	--
; On exit:	a1,a2 preserved. otherwise as APCS-R
;
; I've used APCS register names throughout here.

app__epilogue	ROUT

		STMFD	sp!,{a1,a2}		;Keep two return values
		LDR	ip,app__pw		;Point to private word ptr
		BL	app_getHandle		;Find app's start time
		BL	app__find		;Find app block
		LDR	a2,[a1,#app_stackPtr]	;Find the stack pointer
		ADD	a3,a1,#app_stack	;Point to the stack base
		ADD	a3,a3,a2		;Point to current stack pos
		LDMDB	a3!,{a4,lr}		;Find relocation and ret addr
		SUB	a2,a2,#8		;Bump down the stack ptr
		STR	a2,[a1,#app_stackPtr]	;Store back in app block
		STR	a4,[sl,#-536]		;Store relocation offset
		LDMFD	sp!,{a1,a2}		;Restore return values
		MOVS	pc,lr			;Return to caller

		LTORG

; --- app__find ---
;
; On entry:	R0 == application handle (i.e. start time)
; On exit:	R0 == pointer to application block, or error

app__find	ROUT

		STMFD	R13!,{R1,R2,R14}	;Preserve nice registers

		; --- Try looking at the cached value ---

		LDR	R1,app__cacheHnd	;Load the cached handle
		CMP	R1,R0			;Is it a match?
		LDREQ	R0,app__cachePtr	;Yes -- load the pointer
		LDMEQFD	R13!,{R1,R2,PC}^	;And return!

		; --- Otherwise, scan the list ---

		LDR	R2,app__list		;Get list head pointer
		CMP	R2,#0			;Is there an entry here?
		BEQ	%10app__find		;No -- return an error
00app__find	LDR	R1,[R2,#app_handle]	;Get the app's handle
		CMP	R1,R0			;Is it a match?
		BEQ	%01app__find		;Yes -- deal with it
		LDR	R2,[R2,#app_next]	;No -- get the next pointer
		CMP	R2,#0			;Is this the end?
		BNE	%00app__find		;No -- continue round

		; --- Couldn't find the application ---

10app__find	ADRL	R0,msg_errUnknownApp	;Point to error message
		LDMFD	R13!,{R1,R2,R14}	;Pull back registers
		ORRS	PC,R14,#V_flag		;And return with an error

		; --- Found an application -- update the cache ---

01app__find	STR	R0,app__cacheHnd	;Store the new handle
		STR	R2,app__cachePtr	;And the new pointer
		MOV	R0,R2			;Return the pointer to caller
		LDMFD	R13!,{R1,R2,PC}^	;And live happily ever after

		LTORG

; --- app__add ---
;
; On entry:	--
; On exit:	Application handle

app__add	ROUT

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

		; --- Find out whether the app is registered yet ---

		BL	app_getHandle		;Find app's start time
		MOV	R1,R0			;Look after a copy
		BL	app__find		;Call the find routine
		LDMVCFD	R13!,{R1-R3,PC}^	;If already there, return

		; --- Allocate a block from the RMA ---

		MOV	R0,#6			;Code to allocate some store
		LDR	R3,=app_strSize		;The size of an app block
		SWI	XOS_Module		;Allocate the memory
		LDMVSFD	R13!,{R1-R3,PC}		;If it failed, return error

		; --- Link the block into the list ---

		LDR	R0,app__list		;Find the list header
		STR	R0,[R2,#app_next]	;Store in next pointer
		CMP	R0,#0			;Is there a next block?
		STRNE	R2,[R0,#app_prev]	;Yes -- link in new block
		MOV	R0,#0			;Zero out the previous link
		STR	R0,[R2,#app_prev]	;Store in previous entry
		STR	R2,app__list		;Store this as new block

		; --- Set this block up in the cache ---

		STR	R1,app__cacheHnd	;Cache the app's start time
		STR	R2,app__cachePtr	;Cache the app's pointer

		; --- Set up the block's data ---

		MOV	R0,#0			;Zero some entries in block
		STR	R0,[R2,#app_cachedll]	;No cached DLL yet
		STR	R0,[R2,#app_cacheptr]	;No cached DLL yet
		STR	R0,[R2,#app_dlls]	;No DLLs registered yet
		STR	R0,[R2,#app_stackPtr]	;No entries on the stack
		STR	R0,[R2,#app_btable]	;App has no entry table
		STR	R1,[R2,#app_handle]	;Store the app's time info
		ADD	R0,R2,#app_name		;Point to app's name field
		ADRL	R1,msg_appName		;Point to default name
		BL	misc_strcpy		;Copy the string across

		; --- Return the app's handle ---

		MOV	R0,R2			;Return the app's pointer
		LDMFD	R13!,{R1-R3,PC}^	;Return to caller

		LTORG

; --- app__free ---
;
; On entry:	R0 == pointer to app to free
; On exit:	--

app__free	ROUT

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

		; --- Remove the block from the list ---

		LDR	R1,[R0,#app_next]	;Get next block in the list
		LDR	R2,[R0,#app_prev]	;Get previous block too
		CMP	R1,#0			;Is there a next block?
		STRNE	R2,[R1,#app_prev]	;Yes -- fix up its prev
		CMP	R2,#0			;Is there a previous block?
		ADREQ	R2,app__list		;No -- point to list head
		STR	R1,[R2,#app_next]	;Fix up next pointer

		; --- Remove this app from the cache ---

		LDR	R2,app__cachePtr	;Find which app was cached
		CMP	R2,R0			;Is it this one?
		MOVEQ	R2,#0			;Yes -- overwrite handle
		STREQ	R2,app__cacheHnd	;Won't match this app now
		STREQ	R2,app__cachePtr

		; --- Free the block ---

		MOV	R2,R0			;Point to the block to free
		MOV	R0,#7			;Magic reason code
		SWI	XOS_Module		;Free the block
		LDMFD	R13!,{R1,R2,PC}		;Return to caller if failed

		LTORG

; --- app__freeUnused ---
;
; On entry:	R0 == pointer to application
; On exit:	--

app__freeUnused	ROUT

		STMFD	R13!,{R14}		;Keep link register
		LDR	R14,[R0,#app_dlls]	;Find DLL link list
		CMP	R14,#0			;Are there any in the list?
		LDMFD	R13!,{R14}		;Restore link register
		MOVNES	PC,R14			;Return is still attached
		B	app__free		;Otherwise kill the app

		LTORG

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

app__sFindDLL	ROUT

		STMFD	R13!,{R1,R2,R9-R11,R14}	;Stack loads of registers

		MOV	R11,R0			;Keep pointer safe

		; --- Ensure that the application structure is there ---

		BL	app__add		;Add the application if reqd.
		LDMVSFD	R13!,{R1,R2,R9-R11,PC}	;If it failed, return now
		MOV	R10,R0			;Keep pointer to app block

		; --- Find out if there's anything to do ---

		MOV	R2,R1			;Put version number nicely
		MOV	R1,R11			;Point to required name
		BL	app__findNamedLink	;Is there anything to do?
		LDMVCFD	R13!,{R1,R2,R9-R11,PC}^	;If found, return to caller
		MOV	R1,R2			;Put version number back

		; --- Load the DLL if necessary ---

		MOV	R0,R11			;Move DLL pointer back
		BL	dll_ensure		;Load the DLL into memory
		MOVVS	R9,R0			;If it failed, keep the error
		BVS	%50app__sFindDLL	;And tidy up properly
		MOV	R11,R0			;Keep pointer to DLL
		BL	dll_tentative		;Mark the DLL as tentative

		; --- Add the link between the two ---

		MOV	R0,R10			;Point to application
		MOV	R1,R11			;Point to DLL block
		BL	app__addLink		;Link them together
		MOVVS	R9,R0			;Keep error if any
		BVS	%51app__sFindDLL	;And tidy things up

		; --- Now we're done ---

		MOV	R0,R11			;Point to DLL structure
		LDMFD	R13!,{R1,R2,R9-R11,PC}^	;Restore registers

		; --- Tidy up if link allocation failed ---

51app__sFindDLL
50app__sFindDLL	MOV	R0,R10			;Point to application block
		BL	app__freeUnused		;If not being used, kill it

		; --- Return the error to the caller ---

		MOV	R0,R9			;Point to the error message
		LDMFD	R13!,{R1,R2,R9-R11,R14}	;Restore registers
		ORRS	PC,R14,#V_flag		;Return to caller

		LTORG

; --- app__ok ---
;
; On entry:	--
; On exit:	All registers preserved
;
; Expects a stacked R11 and R14

app__ok		ROUT

		MOV	R11,R0			;Look after error pointer
		BL	dll_confirm		;Cancel tentative DLL blocks
		BL	app_getHandle		;Find app time info
		BLVC	app__find		;Find the application block
		BLVC	app__confLink		;Cancel tentative links

		; --- Resync code areas ---
		;
		; This seems as good a place as any to do this.  I'm going
		; to do a global resync, because I suspect that lots of
		; local ones are going to be rather slower, actually.
		; Besides, loading libraries isn't something which gets done
		; /that/ often.

		MOV	R0,#0			;Resync everything
		SWI	XOS_SynchroniseCodeAreas ;Do that then, please

		MOV	R0,R11			;Point to error again
		LDMFD	R13!,{R11,R14}		;Restore registers
		BICS	PC,R14,#V_flag		;Return to caller with V clr

		LTORG

; --- app__dead ---
;
; On entry:	--
; On exit:	All registers preserved
;
; Expects a stacked R11 and R14

app__dead	ROUT

		MOV	R11,R0			;Look after error pointer
		CMP	R0,R0			;Clear the V flag
		BL	dll_retrace		;Destroy tentative DLL blocks
		BL	app_getHandle		;Find app time info
		BLVC	app__find		;Find the application block
		BLVC	app__cancel		;Destroy tentative links
		MOV	R0,R11			;Point to error again
		LDMFD	R13!,{R11,R14}		;Restore registers
		ORRS	PC,R14,#V_flag		;Return to caller with V set

		LTORG

; --- app__findNamedLk ---
;
; On entry:	R0 == pointer to application
; 		R1 == pointer to DLL's name
; 		R2 == DLL's version number
; On exit:	R0 == V set if couldn't be found, clear otherwise

app__findNamedLink ROUT

		STMFD	R13!,{R3,R4,R14}	;Stack registers safely
		LDR	R3,[R0,#app_dlls]	;Point to first DLL block
		CMP	R3,#0			;Is there anything to do?
		BEQ	%20app__findNamedLink	;No -- that's an error

		; --- Now construct the proper DLL name ---

		MOV	R0,R1			;Point to the DLL name
00		LDRB	R14,[R0],#1		;Load the next byte
		CMP	R14,#'['		;Is this a new-style DAN?
		BEQ	%f05			;Yes -- sort this out then
		CMP	R14,#'.'		;Or is it a dot?
		MOVEQ	R1,R0			;Yes -- remember this place
		CMP	R14,#&20		;End of the string yet?
		BCS	%b00			;No -- keep going

		ADR	R0,misc__sharedBuf	;Find the shared buffer
00		LDRB	R14,[R1],#1		;Load a byte from the name
		CMP	R14,#&20		;Finished yet?
		MOVCC	R14,#0			;Yes -- zero-terminate
		STRB	R14,[R0],#1		;And store it out
		BCS	%b00			;No -- keep going
		B	%10app__findNamedLink	;And skip to main matcher

		; --- Handle a new-style DAN ---

05		ADR	R1,misc__sharedBuf	;Find the shared buffer
00		LDRB	R14,[R0],#1		;Load a byte from the name
		CMP	R14,#']'		;End of the name yet?
		MOVEQ	R14,#0			;Yes -- terminate here then
		CMP	R14,#&20		;Finished yet?
		MOVCC	R14,#0			;Yes -- zero-terminate
		STRB	R14,[R1],#1		;And store it out
		BCS	%b00			;No -- keep going

10app__findNamedLink
		ADR	R1,misc__sharedBuf	;Point to the DLL name

00		LDR	R0,[R3,#lk_dll]		;Find DLL pointer for block
		BL	dll_compare		;Compare with passed values
		CMP	R0,#1			;Did it return TRUE?
		BEQ	%30app__findNamedLink	;Yes -- be happy
		LDR	R3,[R3,#lk_next]	;Find next pointer
		CMP	R3,#0			;Is there any more to do?
		BNE	%b00			;Yes -- loop round and do it

20app__findNamedLink
		LDMFD	R13!,{R3,R4,R14}	;Restore registers
		ORRS	PC,R14,#V_flag		;Return with V set

30app__findNamedLink
		LDR	R0,[R3,#lk_dll]		;Load DLL pointer again
		LDMFD	R13!,{R3,R4,R14}	;Restore registers
		BICS	PC,R14,#V_flag		;Return with V clear

		LTORG

; --- app__findLink ---
;
; On entry:	R0 == pointer to application
; 		R1 == pointer to DLL
; On exit:	R0 == pointer to link structure

app__findLink	ROUT

		STMFD	R13!,{R1,R2,R14}	;Preserve nice registers

		; --- Try looking at the cached value ---

		LDR	R2,[R0,#app_cachedll]	;Load the cached handle
		CMP	R2,R1			;Is it a match?
		LDREQ	R0,[R0,#app_cacheptr]	;Yes -- load the pointer
		LDMEQFD	R13!,{R1,R2,PC}^	;And return!

		; --- Otherwise, scan the list ---

		LDR	R2,[R0,#app_dlls]	;Get list head pointer
		CMP	R2,#0			;Is there an entry here?
		BEQ	%10app__findLink	;No -- return an error
00app__findLink	LDR	R14,[R2,#lk_dll]	;Get the DLL's pointer
		CMP	R1,R14			;Is it a match?
		BEQ	%01app__findLink	;Yes -- deal with it
		LDR	R2,[R2,#lk_next]	;No -- get the next pointer
		CMP	R2,#0			;Is this the end?
		BNE	%00app__findLink	;No -- continue round

		; --- Couldn't find the application ---

10app__findLink	ADRL	R0,msg_errLinkNotFound	;Point to error message
		LDMFD	R13!,{R1,R2,R14}	;Pull back registers
		ORRS	PC,R14,#V_flag		;And return with an error

		; --- Found an application -- update the cache ---

01app__findLink	STR	R1,[R0,#app_cachedll]	;Store the new handle
		STR	R2,[R0,#app_cacheptr]	;And the new pointer
		MOV	R0,R2			;Return the pointer to caller
		LDMFD	R13!,{R1,R2,PC}^	;And live happily ever after

		LTORG

; --- app__addLink ---
;
; On entry:	R0 == pointer to application
;		R1 == pointer to DLL
; On exit:	--

app__addLink	ROUT

		STMFD	R13!,{R1,R11,R14}	;Stack registers
		MOV	R11,R0			;Keep pointer to application

		; --- Find out whether the link is registered yet ---

		BL	app__findLink		;Call the find routine
		LDMVCFD	R13!,{R1,R11,PC}^	;If already there, return

		; --- Allocate a block from the RMA ---

		CMP	R0,R0			;Clear V flag
		BL	sub_alloc		;Allocate a link block
		LDMVSFD	R13!,{R1,R11,PC}	;If it failed, return error

		; --- Link the block into the list ---

		LDR	R14,[R11,#app_dlls]	;Find the list header
		STR	R14,[R0,#lk_next]	;Store in next pointer
		CMP	R14,#0			;Is there a next block?
		STRNE	R0,[R14,#lk_prev]	;Yes -- link in new block
		MOV	R14,#0			;Zero out the previous link
		STR	R14,[R0,#lk_prev]	;Store in previous entry
		STR	R0,[R11,#app_dlls]	;Store this as new block

		; --- Set this block up in the cache ---

		STR	R1,[R11,#app_cachedll]	;Cache the DLL's pointer
		STR	R0,[R11,#app_cacheptr]	;Cache the app's pointer

		; --- Set up the block's data ---

		STR	R1,[R0,#lk_dll]		;Store pointer to DLL
		MOV	R1,#0			;No workspace yet
		STR	R1,[R0,#lk_work]	;So store in the block

		; --- Mark as currently tentative ---

		MOV	R1,#lk_tentative	;Tentative flag
		STR	R1,[R0,#lk_flags]	;Store in block's flags area

		; --- Return the app's handle ---

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

		LTORG

; --- app__freeLink ---
;
; On entry:	R0 == pointer to application
;               R1 == pointer to link structure
; On exit:	--

app__freeLink	ROUT

		STMFD	R13!,{R1,R2,R14}	;Stack registers

		; --- Fix up list ---

		LDR	R2,[R1,#lk_next]	;Get next pointer
		LDR	R14,[R1,#lk_prev]	;Get previous pointer
		CMP	R2,#0			;Is there a real next block?
		STRNE	R14,[R2,#lk_prev]	;Yes -- fix backwards link
		CMP	R14,#0			;Is there a real prev block?
		ADDEQ	R14,R0,#app_dlls	;No -- point to list head
		STR	R2,[R14,#lk_next]	;Fix forwards link

		; --- Free the link ---

		MOV	R0,R1			;Point to the link
		LDMFD	R13!,{R1,R2,R14}	;Retreive registers
		B	sub_free		;Free the link block

		LTORG

; --- app__confLk ---
;
; On entry:	R0 == application to confirm
; On exit:	--

app__confLink	ROUT

		STMFD	R13!,{R1,R14}		;Stack registers
		LDR	R0,[R0,#app_dlls]	;Find head of the link list
		CMP	R0,#0			;Is it empty?
		LDMEQFD	R13!,{R1,PC}^		;Yes -- return to caller
00app__confLink	LDR	R1,[R0,#lk_next]	;Find the next entry
		LDR	R14,[R0,#lk_flags]	;Load flags word
		BIC	R14,R14,#lk_tentative	;Clear the magic flag
		STR	R14,[R0,#lk_flags]	;Store the flags word back
		MOVS	R0,R1			;Move to next entry
		BNE	%00app__confLink	;If more to do, do next one
		LDMFD	R13!,{R1,PC}^		;Return to caller, confirmed

		LTORG

; --- app__cancel ---
;
; On entry:	R0 == app to cancel
; On exit:	--

app__cancel	ROUT

		STMFD	R13!,{R1,R2,R14}	;Stack registers
		MOV	R2,R0			;Keep pointer to app block
		LDR	R0,[R0,#app_dlls]	;Find head of the link list
		CMP	R0,#0			;Is it empty?
		LDMEQFD	R13!,{R1,R2,PC}^	;Yes -- return to caller
00app__cancel	LDR	R1,[R0,#lk_next]	;Find the next entry
		LDR	R14,[R0,#lk_flags]	;Load flags word
		TST	R14,#lk_tentative	;Is the flag set?
		BEQ	%01app__cancel		;No -- move on to next one

		LDR	R14,[R0,#lk_prev]	;Find pointer to previous
		CMP	R1,#0			;Is there a next block?
		STRNE	R14,[R1,#lk_prev]	;Fix up previous pointer
		CMP	R14,#0			;Is there a previous block?
		ADDEQ	R14,R2,#app_dlls	;No -- point to list base
		STR	R1,[R14,#lk_next]	;Fix up next pointer
		BL	sub_free		;Free the block

01app__cancel	MOVS	R0,R1			;Move to next entry
		BNE	%00app__cancel		;If more to do, do next one

		MOV	R0,R2			;Point to application block
		BL	app__freeUnused		;If nothing left, kill app
		LDMFD	R13!,{R1,R2,PC}^	;Return to caller, cancelled

		LTORG

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

		END
