From rld1@cec.wustl.edu Mon, 27 Sep 1999 08:35:54 -0500 (CDT)
From: "Robert L  Dickinson" 
To: "Timothy Rue" 
Subject: RE: The unchanging and "REAL" Open direction.
Date: 27 Sep 1999 13:35:54 GMT
Content-type: multipart/mixed; [edited]

>  +++> I reassembled my source last night and tried to get back into
>  +++> it.  I think it would be a good thing for me to do a little
>  +++> clean-up and documentation before making it publicly available.
>  +++> I'll plan on e-mailing you a presentable version sometime this
>  +++> weekend.

Alas, one of my favorite aspects of software programming is that I have
the freedom to change and mold the software any way I like----but
sometimes that get's me into trouble.  After reviewing my original
undocumented code, I convinced myself that it could be done better and in
a more organized fashion.  So, like a fool, I rewrote it.

And besides that, people no doubt will wonder why in the world I code in
assembly in this day and age of "platform independence", not to mention
that I've re-invented several wheels again.  Answers to these wonders
exist, but anyway, lets talk about what this code does.


GENERAL:

To sum it up, right now it does basically nothing, but in a very nice way.
I have organized all re-usable code into "vic.library", which is accessed
by "AI" as well as the other future commands.  In fact, it is intended
that any other application is free to access these common routines
directly from "vic.library" as well, or by calling the "AI" and other
future commands.


SETUP:

To install it, simply put "vic.library" in "LIBS:", then you are ready to
run "AI" as a command from wherever you want.  Test my error detection by
running "AI" before putting "vic.library" in "LIBS:".  No special version
of the Amiga operating system or hardware is required.


TESTING:

To test the current functionality, run "AI" with various arguments.  This
will demonstrate the common error reporting window.  The only recognized
argument at this time is "-n ", but several errors can be seen. Try
each of the following commands:

	AI
	AI -unk
	AI -n
	AI -n name
	AI -n name -n

Obviously the successful error message is there for debugging only and
will be gone in the future.


OK, so thats where it stands right now.  Nothing very exciting, and
certainly nothing that hasn't been seen in zillions of programs before.
But anyway, this is only the starting point.  All suggestions are welcome,
of course.

-Robert

Binary "vic.library" at vic-lib.lha
Source:

; version/revision of this library (this should always match _libinfo declared below)
THIS_VERSION	EQU	0
THIS_REVISION	EQU	1

; miscellaneous numeric constants
ERRBUF_SIZE	EQU	1024
STACK_SIZE	EQU	8192

; Node Type values, see "exec/nodes.i"
NT_LIBRARY	EQU	9

; Library structure, see "exec/libraries.i"
LIB_NODE	EQU	0
LIB_FLAGS	EQU	14
LIB_KBYTE00	EQU	15
LIB_NEGSIZE	EQU	16
LIB_POSSIZE	EQU	18
LIB_VERSION	EQU	20
LIB_REVISION	EQU	22
LIB_IDSTRING	EQU	24
LIB_CHECKSUM	EQU	28
LIB_OPENCNT	EQU	32
LIB_SIZE	EQU	34

; our library extension
L_EXECBASE	EQU	LIB_SIZE+0
L_INTUITIONBASE	EQU	LIB_SIZE+4
L_DATA		EQU	LIB_SIZE+8
L_ERRBUF	EQU	L_DATA+4
L_ERRPOS	EQU	L_DATA+8
L_ERRWINDOW	EQU	L_DATA+12
L_ERRREADPORT	EQU	L_ERRWINDOW+4
L_ERRWRITEPORT	EQU	L_ERRWINDOW+38
L_ERRREADREQ	EQU	L_ERRWINDOW+72
L_ERRWRITEREQ	EQU	L_ERRWINDOW+120
L_ERRCHAR	EQU	L_ERRWINDOW+168
L_ERRFLAGS	EQU	L_ERRWINDOW+169
L_TASK		EQU	L_ERRWINDOW+170
L_TASKSTACK	EQU	L_TASK+92
L_TASKPORT	EQU	L_TASKSTACK+STACK_SIZE
L_SIZE		EQU	L_TASKPORT+34

; function identifications passed to _Indirect used to index _subvec array
MSG_STACKERROR	EQU	4
MSG_PRINTERROR	EQU	5
MSG_SHOWERRWINDOW EQU	6
MSG_HIDEERRWINDOW EQU	7
MSG_COUNT	EQU	8

; exec.library functions we use
_Alert		EQU	-108
_AllocMem	EQU	-198
_FreeMem	EQU	-210
_AddTask	EQU	-282
_RemTask	EQU	-288
_FindTask	EQU	-294
_Wait		EQU	-318
_AllocSignal	EQU	-330
_FreeSignal	EQU	-336
_PutMsg		EQU	-366
_GetMsg		EQU	-372
_ReplyMsg	EQU	-378
_WaitPort	EQU	-384
_CloseLibrary	EQU	-414
_OpenLibrary	EQU	-552
_OpenDevice	EQU	-444
_CloseDevice	EQU	-450
_DoIO		EQU	-456
_SendIO		EQU	-462
_CheckIO	EQU	-468
_AbortIO	EQU	-480
_CacheClearU	EQU	-636	; version 36+ (release 2.0+)

; intuition.library functions we use
_CloseWindow	EQU	-72
_OpenWindow	EQU	-204
_WindowToFront	EQU	-312
_ActivateWindow	EQU	-450	; version 33+ (release 1.2+)


		SECTION	_Lib,CODE

; exit immediately if somebody executes this object module
_Stub		MOVEQ	#-1,D0
		RTS

; library header data
_libhead	DC.W	$4AFC
		DC.L	_libhead,_libtail
		DC.B	$80,THIS_VERSION,NT_LIBRARY,0
		DC.L	_libname,_libinfo,_libmake
_libname	DC.B	"vic.library",0
_libinfo	DC.B	"vic 0.1 (26.9.99)",13,10,0
_libmake	DC.L	L_SIZE,_libvec,0,_LibInit
_libvec		DC.L	_LibOpen,_LibClose,_LibExpunge,_LibReserved
		DC.L	__StackError,__PrintError,__ShowErrWindow,__HideErrWindow
		DC.L	_CopyArg
		DC.L	_StrLen,_StrCopy,_StrCmp,_StrDup,_StrFree
		DC.L	-1

; miscellaneous constants
_ibname		DC.B	"intuition.library",0
_cdname		DC.B	"console.device",0
_setevents	DC.B	$9B,"11{",0
_closeevent	DC.B	$9B,"11;",0
_nwerr		DC.W	0,0,640,130,-1
		DC.L	$00000000,$0000000F,0,0,_libname,0,0
		DC.W	75,50,-1,-1,$0001
_subvec		DC.L	0,0,0,0
		DC.L	_StackError,_PrintError,_ShowErrWindow,_HideErrWindow

; _LibInit
;  initialize this library
;
; INPUTS
;  A6 - pointer to exec library
;  D0 - pointer to this library
;  A0 - pointer to data area
;
; OUTPUTS
;  D0 - pointer to this library or NULL
;
; COMMENTS
;  If our initialization is successful, we need
;  to return our library pointer, otherwise we
;  return NULL to indicate failure.
;
;  Note that the failure cleanup in this routine
;  should match the cleanup in _LibExpunge.
;
; SEE ALSO
;  _LibExpunge
;
_LibInit	MOVEM.L	A2-A3/A5-A6,-(SP)
		MOVEA.L	D0,A5
		MOVE.L	A6,L_EXECBASE(A5)
		MOVE.L	A0,L_DATA(A5)
		MOVE.W	#THIS_REVISION,LIB_REVISION(A5)
		MOVEA.L	L_EXECBASE(A5),A6	; open intuition.library
		LEA	_ibname,A1
		MOVEQ	#0,D0
		JSR	_OpenLibrary(A6)
		MOVE.L	D0,L_INTUITIONBASE(A5)
		BEQ	lifail0			; open intuition.library failed (unlikely)
		MOVEA.L	L_EXECBASE(A5),A6	; allocate error message buffer
		MOVE.L	#ERRBUF_SIZE,D0
		MOVEQ	#0,D1
		JSR	_AllocMem(A6)
		MOVE.L	D0,L_ERRBUF(A5)
		BEQ	lifail1			; error message buffer allocation failed
		MOVEA.L	D0,A0
		ADDA.W	#ERRBUF_SIZE,A0
		MOVE.B	#10,-(A0)
		MOVE.L	A0,L_ERRPOS(A5)
		CLR.L	L_ERRWINDOW(A5)
		MOVE.B	#1,L_TASK+8(A5)		; initialize library task
		MOVE.B	#0,L_TASK+9(A5)
		MOVE.L	#_libname,L_TASK+10(A5)
		LEA	L_TASKSTACK(A5),A0
		MOVE.L	A0,L_TASK+58(A5)
		ADDA.L	#STACK_SIZE,A0
		MOVE.L	A0,L_TASK+54(A5)
		MOVE.L	A0,L_TASK+62(A5)
		MOVE.L	A5,L_TASK+88(A5)
		LEA	L_TASKPORT(A5),A0	; initialize task port
		JSR	_InitPort
		MOVE.B	#0,L_TASKPORT+14(A5)
		MOVE.B	#16,L_TASKPORT+15(A5)
		LEA	L_TASK(A5),A0
		MOVE.L	A0,L_TASKPORT+16(A5)
		LEA	L_TASK(A5),A1
		LEA	_LibTask,A2
		SUBA.L	A3,A3
		JSR	_AddTask(A6)
		MOVE.L	A5,D0
		BRA.S	lipass0			; skip failure processing
		MOVEA.L	L_EXECBASE(A5),A6	; remove task upon failure
		LEA	L_TASK(A5),A1
		JSR	_RemTask(A6)
		MOVEA.L	L_EXECBASE(A5),A6	; free error message buffer upon failure
		MOVEA.L	L_ERRBUF(A5),A1
		MOVE.L	#ERRBUF_SIZE,D0
		JSR	_FreeMem(A6)
lifail1		MOVEA.L	L_EXECBASE(A5),A6	; close intuition.library upon failure
		MOVEA.L	L_INTUITIONBASE(A5),A1
		JSR	_CloseLibrary(A6)
lifail0		MOVEQ	#0,D0			; return NULL upon failure
lipass0		MOVEM.L	(SP)+,A2-A3/A5-A6
		RTS

; _LibTask
;  process library messages and perform respective duties
;
; INPUTS
;
; OUTPUTS
;
; COMMENTS
;  This task is alive for the duration of our library's
;  existance.  Some critical library functions pass their
;  respective calls to this task through it's message
;  port so that they can be processed in a safe manner.
;  (Safe in that there will be no possibility of multiple
;  accessors calling conflicting function simultaneously
;  and having to use the Exec _Forbid/_Permit functions.)
;
;  Note that this routine is self modifying.  When a
;  message is received, the appropriate routine address
;  is copied into a JSR instruction for execution.
;
;  Messages received by this routine are expected to have
;  the register bank as part of the message, which will
;  be updated with the results of the indirect function
;  call before the message is replied.
;
; BUGS
;  _AlertError calls are passed a NULL parameter in A0
;  which causes serious problems.  Hopefully we never need
;  to display an alert anyway, but this should still be
;  fixed to make our failures somewhat graceful at least.
;
; SEE ALSO
;  _Indirect
;
_LibTask	MOVEA.L	$00000004,A6
		SUBA.L	A1,A1
		JSR	_FindTask(A6)
		MOVEA.L	D0,A0
		MOVEA.L	88(A0),A5
		MOVEA.L	L_EXECBASE(A5),A6
		MOVEQ	#16,D0
		JSR	_AllocSignal(A6)
ltloop0		MOVE.L	#$00010000,D0
		TST.L	L_ERRWINDOW(A5)
		BEQ.S	ltskip2
		MOVEQ	#1,D2
		MOVE.B	L_ERRREADPORT+15(A5),D1
		LSL.L	D1,D2
		OR.L	D2,D0
ltskip2		JSR	_Wait(A6)
ltloop1		LEA	L_TASKPORT(A5),A0
		JSR	_GetMsg(A6)
		TST.L	D0
		BEQ.S	ltskip1
		MOVEA.L	D0,A4
		MOVE.L	20+60(A4),D0		; get message number
		CMPI.L	#MSG_COUNT,D0
		BCC	ltfail0			; message number out of range (greater than or equal to MSG_COUNT)
		LSL.L	#2,D0
		LEA	_subvec,A0
		TST.L	0(A0,D0)
		BEQ	ltfail0			; message number has no vector
		MOVE.L	0(A0,D0),ltmod0+2	; self modifying code, see COMMENTS
		CMPI.W	#36,20(A6)
		BCS.S	ltskip0
		JSR	_CacheClearU(A6)	; clear instruction cache so that modified code will be refetched
ltskip0		MOVE.L	A4,_msg
		MOVEM.L	D0-D7/A0-A6,-(SP)
		MOVE.L	SP,_sp
		MOVEM.L	20(A4),D0-D7/A0-A6	; load registers from message
ltmod0		JSR	_SelfModify		; call indirect function (function address modified above, see COMMENTS)
		MOVEA.L	_msg,A7
		MOVEM.L	D0-D7/A0-A6,20(A7)	; save registers to message
		MOVEA.L	_sp,SP
		MOVEM.L	(SP)+,D0-D7/A0-A6
		MOVEA.L	A4,A1			; function call complete, reply
		JSR	_ReplyMsg(A6)
		BRA.S	ltloop1
ltskip1		TST.L	L_ERRWINDOW(A5)
		BEQ	ltloop0
		LEA	L_ERRREADREQ(A5),A1
		JSR	_CheckIO(A6)
		TST.L	D0
		BEQ	ltloop0
		LEA	L_ERRREADPORT(A5),A0
		JSR	_WaitPort(A6)
		TST.L	D0
		BEQ	ltloop0
		LEA	L_ERRREADPORT(A5),A0
		JSR	_GetMsg(A6)
		TST.L	D0
		BEQ	ltloop0
		MOVE.B	L_ERRCHAR(A5),D2
		LEA	L_ERRREADREQ(A5),A1
		MOVE.W	#2,28(A1)
		MOVE.L	#1,36(A1)
		LEA	L_ERRCHAR(A5),A0
		MOVE.L	A0,40(A1)
		JSR	_SendIO(A6)
		MOVE.B	D2,D0
		CMPI.B	#$9B,D0
		BNE.S	ltskipch0
		MOVE.B	#1,L_ERRFLAGS(A5)
		BRA	ltloop0
ltskipch0	MOVEQ	#0,D1
		MOVE.B	L_ERRFLAGS(A5),D1
		LEA	_closeevent,A0
		TST.B	0(A0,D1)
		CMP.B	0(A0,D1),D0
		BNE.S	ltskipch1
		ADDQ.B	#1,D1
		MOVE.B	D1,L_ERRFLAGS(A5)
		TST.B	0(A0,D1)
		BNE	ltloop0
		MOVEA.L	A5,A6
		JSR	_HideErrWindow
		MOVEA.L	L_EXECBASE(A5),A6
		BRA	ltloop0
ltskipch1	CLR.B	L_ERRFLAGS(A5)
		BRA	ltloop0
ltfail0		MOVE.L	#$35080000,D0
		SUBA.L	A0,A0
		JSR	_AlertError
		BRA	ltloop1
		RTS				; this routine should never return
_sp		DC.L	0
_msg		DC.L	0

; _SelfModify
;  intended never to be called
;
; SEE ALSO
;  _LibTask
;
_SelfModify	MOVE.L	#$35000000,D0
		SUBA.L	A0,A0
		JSR	_AlertError
		RTS

; _Indirect
;  pass a function call to vic.library task for
;  processing
;
; INPUTS
;  A6 - pointer to this library
;  all registers - parameters for function call
;  stack parameter - identification of function to call
;
; OUTPUTS
;  all registers - parameters returned by function call
;
; COMMENTS
;  This function should be invoked with a JMP instead of
;  a JSR.  Since this routine is not expected to return to
;  the caller, the stack parameter will be removed from
;  the stack in this routine.  Also, the caller must not
;  allocate anything else on the stack because this routine
;  will not know how to remove it.
;
;  The purpose of this routine is to allow a tiny stub
;  routine to put the function identification number on
;  the stack and JMP to this routine, which will call the
;  specified function indirectly by posting a message to
;  our library port.  Once the message is returned, we
;  will return back to the caller's caller, as if the
;  requested routine had been called directly.  This
;  whole process can be thought of as a way of isolating
;  a subroutine call from the caller's task without the
;  caller knowing it.  Instead of the caller's task
;  executing the subroutine, our library task is told to
;  execute it instead, on it's own time, with it's own
;  task.  The original caller sleeps in the mean time.
;
;  The message that we pass on to the library task is a bit
;  convoluted.  We create the Message structure on the
;  stack frame in such a way that the registers we save
;  on the stack will follow the Message structure in
;  memory, to be used as part of the message.  This is made
;  possible by the fact that the stack grows downward.
;  As the message contents are modified by the library's
;  task while we are a sleep, our stack frame is actually
;  being modified. When we restore the registers, we will
;  actually be restoring the modified values.
;
; SEE ALSO
;  _LibTask
;
_Indirect	MOVEM.L	D0-D7/A0-A6,-(SP)	; save our registers on the stack, part of the Message structure
		SUBA.L	#34+20,SP		; set up our stack frame for Port and Message structures
		MOVEA.L	SP,A4
		MOVEA.L	A6,A5
		MOVEA.L	L_EXECBASE(A5),A6
		MOVEA.L	A4,A0			; initialize Port structure
		JSR	_InitPort
		MOVEQ	#-1,D0			; allocate wait signal
		JSR	_AllocSignal(A6)
		CMPI.L	#31,D0
		BHI	ifail0			; this error isn't trapped
		MOVE.B	#0,14(A4)
		MOVE.B	D0,15(A4)
		SUBA.L	A1,A1
		JSR	_FindTask(A6)
		MOVE.L	D0,16(A4)
		CLR.L	34+0(A4)		; initialize Message structure
		CLR.L	34+4(A4)
		MOVE.B	#5,34+8(A4)
		CLR.B	34+9(A4)
		CLR.L	34+10(A4)
		MOVE.L	A4,34+14(A4)
		MOVE.W	#20+64,34+18(A4)
		LEA	L_TASKPORT(A5),A0	; send message to vic.library task
		LEA	34(A4),A1
		JSR	_PutMsg(A6)
		MOVEQ	#1,D0			; wait for message to be returned
		MOVE.B	15(A4),D1
		LSL.L	D1,D0
		JSR	_Wait(A6)
		MOVE.B	15(A4),D0		; free wait signal
		JSR	_FreeSignal(A6)
ifail0		ADDA.L	#34+20,SP		; clean up our stack frame
		MOVEM.L	(SP)+,D0-D7/A0-A6	; restore registers, as altered by Message processing
		ADDQ.L	#4,SP			; this instruction is critical, read COMMENTS section
		RTS

; _LibOpen
;  request access to this library
;  this standard library function is required by Exec
;
; INPUTS
;  A6 - pointer to this library
;
; OUTPUTS
;  D0 - pointer to this library or NULL
;
; COMMENTS
;  If this open is successful, we need to return
;  our library pointer.  For failure, we simply
;  return NULL.
;
; SEE ALSO
;  _LibClose
;
_LibOpen	ADDQ.W	#1,LIB_OPENCNT(A6)
		BNE.S	loskip0
		MOVE.W	#$FFFF,LIB_OPENCNT(A6)	; prevent wrap around
loskip0		MOVE.L	A6,D0
		RTS

; _LibClose
;  release access to this library
;  this standard library function is required by Exec
;
; INPUTS
;  A6 - pointer to this library
;
; OUTPUTS
;  D0 - pointer to data area or NULL
;
; COMMENTS
;  If we are actually closing down, we need to
;  return the data pointer that we received during
;  initialization so that it can be freed by
;  Exec.  If we are staying loaded, then we must
;  return NULL.
;
; SEE ALSO
;  _LibOpen
;
_LibClose	CMPI.W	#$FFFF,LIB_OPENCNT(A6)	; once we have this many opens, we lose track and stop closing
		BEQ.S	lcskip0
		SUBQ.W	#1,LIB_OPENCNT(A6)	; if this wraps around (to $FFFF), we loose track also
lcskip0		MOVEQ	#0,D0
		RTS

; _LibExpunge
;  expunge this library
;  this standard library function is required by exec
;
; INPUTS
;  A6 - pointer to this library
;
; OUTPUTS
;  D0 - pointer to data area or NULL
;
; COMMENTS
;  If we are actually closing down, we need to
;  return the data pointer that we received during
;  initialization so that it can be freed by
;  Exec.  If we are staying loaded, then we must
;  return NULL.
;
;  Note that the cleanup in this routine should
;  match the initializations in _LibInit.
;
; SEE ALSO
;  _LibInit
;
_LibExpunge	MOVEQ	#0,D0
		TST.W	LIB_OPENCNT(A6)
		BNE.S	leskip0
		MOVEM.L	D2/A5-A6,-(SP)
		MOVEA.L	A6,A5
		JSR	__HideErrWindow
		MOVEA.L	L_EXECBASE(A5),A6
		LEA	L_TASK(A5),A1		; remove task
		JSR	_RemTask(A6)
		MOVEA.L	L_ERRBUF(A5),A1		; free error message buffer
		MOVE.L	#ERRBUF_SIZE,D0
		JSR	_FreeMem(A6)
		MOVEA.L	L_INTUITIONBASE(A5),A1	; close intuition.library
		JSR	_CloseLibrary(A6)
		MOVE.L	L_DATA(A5),D2		; do final cleanup
		MOVE.L	A5,A1
		MOVE.L	(A1)+,A0
		MOVE.L	(A1),A1
		MOVE.L	A0,(A1)
		MOVE.L	A1,4(A0)
		MOVE.L	A5,A1
		MOVEQ	#0,D0
		MOVE.W	LIB_NEGSIZE(A5),D0
		SUBA.L	D0,A1
		ADD.W	LIB_POSSIZE(A5),D0
		JSR	_FreeMem(A6)
		MOVE.L	D2,D0
		MOVEM.L	(SP)+,D2/A5-A6
leskip0		RTS

; _LibReserved
;  this function is reserved for future use
;  this standard library function is required by exec
;
; INPUTS
;
; OUTPUTS
;  D0 - NULL
;
_LibReserved	MOVEQ	#0,D0
		RTS

; SEE ALSO
;  _StackError
;
__StackError	MOVE.L	#MSG_STACKERROR,-(SP)
		JMP	_Indirect

; SEE ALSO
;  _PrintError
;
__PrintError	MOVE.L	#MSG_PRINTERROR,-(SP)
		JMP	_Indirect

; SEE ALSO
;  _ShowErrWindow
;
__ShowErrWindow	MOVE.L	#MSG_SHOWERRWINDOW,-(SP)
		JMP	_Indirect

; SEE ALSO
;  _HideErrWindow
;
__HideErrWindow	MOVE.L	#MSG_HIDEERRWINDOW,-(SP)
		JMP	_Indirect

; _CopyArg
;  copies the next argument in a command line into the
;  specified buffer
;
; INPUTS
;  A0 - pointer to a non-terminated command-line string
;  D0 - length of string in A0
;  A1 - pointer to output string buffer
;
; OUTPUTS
;  D0 - number of characters used from original string
;
; COMMENTS
;  This routine copies the next command-line argument into
;  the buffer specified, terminating the string with a
;  NULL.  Note that the input string is not NULL-terminated
;  and the size must be specified.  The return value of this
;  function is the number of characters that were used up
;  from the input string, which not necessarily match the
;  length of the NULL-terminated output string due to spaces
;  being stripped and such.
;
_CopyArg	MOVE.L	D0,-(SP)
caloop0		TST.L	D0
		BEQ.S	cadone0
		SUBQ.L	#1,D0
		MOVE.B	(A0)+,D1
		CMPI.B	#32,D1
		BLS.S	caloop0
		CMPI.B	#'"',D1
		BEQ.S	caskip0
caloop1		MOVE.B	D1,(A1)+
		TST.L	D0
		BEQ.S	cadone0
		SUBQ.L	#1,D0
		MOVE.B	(A0)+,D1
		CMPI.B	#32,D1
		BLS.S	cadone0
		BRA.S	caloop1
caskip0		NOP
caloop2		TST.L	D0
		BEQ.S	cadone0
		SUBQ.L	#1,D0
		MOVE.B	(A0)+,D1
		CMPI.B	#'"',D1
		BEQ.S	cadone0
		MOVE.B	D1,(A1)+
		BRA.S	caloop2
cadone0		CLR.B	(A1)
		NEG.L	D0
		ADD.L	(SP)+,D0
		RTS

; _StrLen
;  returns the length of a NULL-terminated string
;
; INPUTS
;  A0 - NULL-terminated string
;
; OUTPUTS
;  D0 - length
;
_StrLen		MOVEQ	#-1,D0
slloop0		ADDQ.L	#1,D0
		TST.L	(A0)+
		BNE.S	slloop0
		RTS

; _StrCopy
;  copy a NULL-terminated string
;
; INPUTS
;  A0 - source NULL-terminated string
;  A1 - destination NULL-terminated string
;
; OUTPUTS
;  D0 - address of NULL in destination string
;
_StrCopy	NOP
scloop1		MOVE.B	(A0)+,(A1)+
		BNE.S	scloop1
		MOVE.L	A1,D0
		SUBQ.L	#1,D0
		RTS

; _StrCmp
;  returns zero if the strings are equal
;
; INPUTS
;  A0 - NULL-terminated string
;  A1 - NULL-terminated string
;
; OUTPUTS
;  D0 - zero if equal
;
_StrCmp		MOVEQ	#-1,D0
scloop0		CMPM.B	(A0)+,(A1)+
		BNE.S	scfail0
		TST.B	-1(A0)
		BNE.S	scloop0
		MOVEQ	#0,D0
scfail0		RTS

; _StrDup
;  allocates a copy of a NULL-terminated string
;
; INPUTS
;  A0 - NULL-terminated string
;
; OUTPUTS
;  D0 - NULL-terminated string
;
_StrDup		MOVEM.L	A4-A6,-(SP)
		MOVE.L	A0,A4
		MOVEA.L	A6,A5
		JSR	_StrLen
		MOVEA.L	L_EXECBASE(A5),A6
		ADDQ.L	#1,D0
		MOVEQ	#0,D1
		JSR	_AllocMem(A6)
		TST.L	D0
		BEQ.S	sdfail0
		MOVEA.L	A5,A6
		MOVEA.L	A4,A0
		MOVEA.L	D0,A1
		MOVEA.L	D0,A4
		JSR	_StrCopy
		MOVE.L	A4,D0
sdfail0		MOVEM.L	(SP)+,A4-A6
		RTS

; _StrFree
;  frees a NULL-terminated string
;
; INPUTS
;  A0 - NULL-terminated string
;
_StrFree	MOVEM.L	A4-A6,-(SP)
		MOVE.L	A0,A4
		MOVEA.L	A6,A5
		JSR	_StrLen
		MOVEA.L	L_EXECBASE(A5),A6
		ADDQ.L	#1,D0
		MOVE.L	A4,A1
		JSR	_FreeMem(A6)
		MOVEM.L	(SP)+,A4-A6
		RTS

; _StackError
;  this function concatenates the specified string to
;  the current error message for later outputting.
;
; INPUTS
;  A0 - pointer to a NULL-terminated string (error message)
;
; OUTPUTS
;
; COMMENTS
;  This is done to allow lower-level code to record errors
;  as well as higher level code.  Any routine that fails
;  but returns an error code should call this routine,
;  but routines that fail and don't return an error code
;  should use _PrintError.  This way, the error can be
;  traced from the low-level code outward to the actual
;  routine that printed the error, all in one error message.
;
; SEE ALSO
;  _PrintError
;
_StackError	MOVE.L	A0,D0
seloop0		TST.B	(A0)+
		BNE.S	seloop0
		MOVE.L	L_ERRBUF(A6),D1
		MOVEA.L	L_ERRPOS(A6),A1
		CMPI.B	#32,(A1)		; (A1) is a white space
		BLS.S	seskip0			; skip ': ' separator
		CMPA.L	D1,A1
		BCS.S	sedone1			; A1 less than D1
		MOVE.B	#' ',-(A1)
		CMPA.L	D1,A1
		BCS.S	sedone1			; A1 less than D1
		MOVE.B	#':',-(A1)
seskip0		SUBQ.L	#1,A0
seloop1		CMPA.L	D0,A0
		BLS.S	sedone1			; A0 less than or equal to D0
		CMPA.L	D1,A1
		BCS.S	sedone1			; A1 less than D1
		MOVE.B	-(A0),-(A1)
		BRA.S	seloop1
sedone1		MOVE.L	A1,L_ERRPOS(A6)
		RTS

; _PrintError
;  this function concatenates the specified string to
;  the current error message and outputs the whole
;  message preceeded by the name of the reporter.
;
; INPUTS
;  A0 - pointer to a NULL-terminated error string
;  A1 - pointer to a NULL-terminated name string of reporter
;
; OUTPUTS
;
; COMMENTS
;  Error strings should be reported regardless of whether
;  or not you are returning an error code.  If you are
;  returning an error code, you should use _StackError,
;  but if not then use this routine.  This mechanism allows
;  errors to be reported in a format that shows each detection
;  of the error from the low-level code out to the high-level
;  code, producing a sort of "call-stack" to aid in debugging.
;
; SEE ALSO
;  _StackError
;
_PrintError	MOVEM.L	A5-A6,-(SP)
		MOVEA.L	A1,A5
		JSR	_StackError
		MOVEA.L	A5,A0
		MOVEA.L	A6,A5
		MOVE.L	L_ERRBUF(A5),D1
		MOVEA.L	L_ERRPOS(A5),A1
		CMPA.L	D1,A1
		BCS.S	pedone0			; A1 less than D1
		MOVE.B	#' ',-(A1)
		CMPA.L	D1,A1
		BCS.S	pedone0			; A1 less than D1
		MOVE.B	#'-',-(A1)
		CMPA.L	D1,A1
		BCS.S	pedone0			; A1 less than D1
		MOVE.B	#' ',-(A1)
pedone0		MOVE.L	A1,L_ERRPOS(A5)
		JSR	_StackError
		JSR	_ShowErrWindow
		TST.L	L_ERRWINDOW(A5)
		BEQ.S	pefail0
		MOVE.L	L_ERRBUF(A5),D0
		ADD.L	#ERRBUF_SIZE,D0
		SUB.L	L_ERRPOS(A5),D0
		MOVEA.L	L_EXECBASE(A5),A6
		LEA	L_ERRWRITEREQ(A5),A1
		MOVE.W	#3,28(A1)
		MOVE.L	D0,36(A1)
		MOVE.L	L_ERRPOS(A5),40(A1)
		JSR	_DoIO(A6)
pefail0		MOVEA.L	L_ERRBUF(A5),A0
		ADDA.W	#ERRBUF_SIZE,A0
		MOVE.B	#10,-(A0)
		MOVE.L	A0,L_ERRPOS(A5)
		MOVEM.L	(SP)+,A5-A6
		RTS

_ShowErrWindow	MOVEM.L	A5-A6,-(SP)
		MOVEA.L	A6,A5
		TST.L	L_ERRWINDOW(A5)
		BNE	sewskip0
		MOVEA.L	L_INTUITIONBASE(A5),A6
		LEA	_nwerr,A0
		JSR	_OpenWindow(A6)
		MOVE.L	D0,L_ERRWINDOW(A5)
		BEQ	sewskip0
		MOVEA.L	L_EXECBASE(A5),A6
		LEA	L_ERRWRITEPORT(A5),A0
		JSR	_InitPort
		MOVEQ	#-1,D0
		JSR	_AllocSignal(A6)
		MOVE.B	#0,L_ERRWRITEPORT+14(A5)
		MOVE.B	D0,L_ERRWRITEPORT+15(A5)
		SUBA.L	A1,A1
		JSR	_FindTask(A6)
		MOVE.L	D0,L_ERRWRITEPORT+16(A5)
		LEA	L_ERRWRITEREQ(A5),A0
		LEA	L_ERRWRITEPORT(A5),A1
		JSR	_InitStdIOReq
		LEA	L_ERRREADPORT(A5),A0
		JSR	_InitPort
		MOVEQ	#-1,D0
		JSR	_AllocSignal(A6)
		MOVE.B	#0,L_ERRREADPORT+14(A5)
		MOVE.B	D0,L_ERRREADPORT+15(A5)
		SUBA.L	A1,A1
		JSR	_FindTask(A6)
		MOVE.L	D0,L_ERRREADPORT+16(A5)
		LEA	L_ERRREADREQ(A5),A0
		LEA	L_ERRREADPORT(A5),A1
		JSR	_InitStdIOReq
		MOVE.L	#132,L_ERRWRITEREQ+36(A5)	; size of release 1.2 Window structure
		MOVE.L	L_ERRWINDOW(A5),L_ERRWRITEREQ+40(A5)
		LEA	_cdname,A0
		MOVEQ	#0,D0
		LEA	L_ERRWRITEREQ(A5),A1
		MOVEQ	#0,D1
		JSR	_OpenDevice(A6)
		MOVE.L	L_ERRWRITEREQ+20(A5),L_ERRREADREQ+20(A5)
		MOVE.L	L_ERRWRITEREQ+24(A5),L_ERRREADREQ+24(A5)
		TST.L	D0
		BNE.S	sewfail0
		MOVEA.L	L_EXECBASE(A5),A6
		LEA	L_ERRWRITEREQ(A5),A1
		MOVE.W	#3,28(A1)
		MOVE.L	#4,36(A1)
		MOVE.L	#_setevents,40(A1)
		JSR	_DoIO(A6)

		LEA	L_ERRREADREQ(A5),A1
		MOVE.W	#2,28(A1)
		MOVE.L	#1,36(A1)
		LEA	L_ERRCHAR(A5),A0
		MOVE.L	A0,40(A1)
		JSR	_SendIO(A6)
		BRA.S	sewskip0
sewfail0	MOVEA.L	L_INTUITIONBASE(A5),A6
		MOVEA.L	L_ERRWINDOW(A5),A0
		JSR	_CloseWindow(A6)
		CLR.L	L_ERRWINDOW(A5)
sewskip0	TST.L	L_ERRWINDOW(A5)
		BEQ.S	sewskip1
		MOVEA.L	L_INTUITIONBASE(A5),A6
		MOVEA.L	L_ERRWINDOW(A5),A0
		JSR	_WindowToFront(A6)
;		CMPI.W	#33,LIB_VERSION(A6)
;		BLT.S	sewskip1
;		MOVEA.L	L_ERRWINDOW(A5),A0
;		JSR	_ActivateWindow(A6)
sewskip1	MOVEM.L	(SP)+,A5-A6
		RTS

_HideErrWindow	MOVEM.L	A5-A6,-(SP)
		MOVEA.L	A6,A5
		TST.L	L_ERRWINDOW(A5)
		BEQ.S	hewskip0
		MOVEA.L	L_EXECBASE(A5),A6
		LEA	L_ERRREADREQ(A5),A1
		JSR	_CheckIO(A6)
		TST.L	D0
		BNE.S	hewskip1
		LEA	L_ERRREADREQ(A5),A1
		JSR	_AbortIO(A6)
hewskip1	LEA	L_ERRREADPORT(A5),A0
		JSR	_WaitPort(A6)
		LEA	L_ERRREADPORT(A5),A0
		JSR	_GetMsg(A6)
		TST.L	D0
		BNE.S	hewskip2
		BRA.S	hewskip1
hewskip2	LEA	L_ERRWRITEREQ(A5),A1
		JSR	_CloseDevice(A6)
		MOVE.B	L_ERRWRITEPORT+15(A5),D0
		JSR	_FreeSignal(A6)
		MOVE.B	L_ERRREADPORT+15(A5),D0
		JSR	_FreeSignal(A6)
		MOVEA.L	L_INTUITIONBASE(A5),A6
		MOVEA.L	L_ERRWINDOW(A5),A0
		JSR	_CloseWindow(A6)
		CLR.L	L_ERRWINDOW(A5)
hewskip0	MOVEM.L	(SP)+,A5-A6
		RTS

_InitPort	CLR.L	(A0)
		CLR.L	4(A0)
		MOVE.B	#4,8(A0)
		CLR.B	9(A0)
		CLR.L	10(A0)
		MOVE.B	#2,14(A0)
		CLR.B	15(A0)
		CLR.L	16(A0)
		MOVE.B	#5,32(A0)
		CLR.B	33(A0)
		LEA	20(A0),A0
		JSR	_NewList
		RTS

_InitStdIOReq	CLR.L	(A0)
		CLR.L	4(A0)
		MOVE.B	#5,8(A0)
		CLR.B	9(A0)
		CLR.L	10(A0)
		MOVE.L	A1,14(A0)
		MOVE.W	#48,18(A0)
		CLR.L	20(A0)
		CLR.L	24(A0)
		CLR.W	28(A0)
		CLR.B	30(A0)
		CLR.B	31(A0)
		CLR.L	32(A0)
		CLR.L	36(A0)
		CLR.L	40(A0)
		CLR.L	44(A0)
		RTS

_NewList	MOVE.L	A0,(A0)
		ADDQ.L	#4,(A0)
		CLR.L	4(A0)
		MOVE.L	A0,8(A0)
		RTS

; _AlertError
;  display an alert
;
; INPUTS
;  D0 - alert number
;  A0 - alert message
;
; OUTPUTS
;
; COMMENTS
;  After displaying the alert, this function waits forever
;  or until the task is removed.  This is intended only for
;  our own fatal errors, obviously, but should allow us to
;  freeze gracefully so that the rest of the system doesn't
;  suffer.
;
_AlertError	MOVEM.L	D7/A5-A6,-(SP)
		MOVEA.L	$00000004,A6
		MOVE.L	D0,D7
		MOVEA.L	A0,A5			; need to figure out this parameter
		JSR	_Alert(A6)
		MOVEQ	#0,D0
		JSR	_Wait(A6)		; wait forever
		MOVEM.L	(SP)+,D7/A5-A6
		RTS

_libtail	DS.W	0


		END

Binary "AI" at AI.lha
Source:

; exec.library functions we use
_Forbid		EQU	-132
_AllocMem	EQU	-198
_FreeMem	EQU	-210
_FindTask	EQU	-294
_GetMsg		EQU	-372
_ReplyMsg	EQU	-378
_WaitPort	EQU	-384
_CloseLibrary	EQU	-414
_OpenLibrary	EQU	-552

; dos.library functions we use
_Write		EQU	-48
_Output		EQU	-60

; vic.library functions we use
_StackError	EQU	-30
_PrintError	EQU	-36
;_ShowErrWindow	EQU	-42
;_HideErrWindow	EQU	-48
_CopyArg	EQU	-54
;_StrLen		EQU	-60
;_StrCopy	EQU	-66
_StrCmp		EQU	-72
_StrDup		EQU	-78
_StrFree	EQU	-84

; mode definitions
MODE_CREATE	EQU	1

; absolute pointer to exec.library
_eb		EQU	$00000004


		SECTION	_Prog,CODE

_Setup		MOVE.L	D0,_cmdlen
		MOVE.L	A0,_cmdstr
		MOVEA.L	_eb,A6
		SUBA.L	A1,A1
		JSR	_FindTask(A6)
		MOVE.L	D0,_tc
		MOVEA.L	_tc,A2
		MOVE.L	10(A2),_tcprev
		MOVE.L	#_tcname,10(A2)
		TST.L	172(A2)
		BNE.S	sskipwb0
		CLR.L	_cmdlen
		CLR.L	_cmdstr
		LEA	92(A2),A0
		JSR	_WaitPort(A6)
		LEA	92(A2),A0
		JSR	_GetMsg(A6)
		MOVE.L	D0,_wbmsg
sskipwb0	LEA	_dbname,A1
		MOVEQ	#0,D0
		JSR	_OpenLibrary(A6)
		MOVE.L	D0,_db
		BEQ.S	sfail0
		LEA	_vbname,A1
		MOVEQ	#0,D0
		JSR	_OpenLibrary(A6)
		MOVE.L	D0,_vb
		BEQ.S	sfail1
		JSR	_ParseCmdLine
		MOVEA.L	_vb,A1
		JSR	_CloseLibrary(A6)
		BRA.S	spass1
sfail1		MOVEA.L	_db,A6
		JSR	_Output(A6)
		BEQ.S	spass1
		LEA	_vberr,A0
		MOVE.L	D0,D1
		MOVE.L	A0,D2
		MOVEQ	#-1,D3
sloop0		ADDQ.L	#1,D3
		TST.B	(A0)+
		BNE.S	sloop0
		JSR	_Write(A6)
		MOVEA.L	_eb,A6
spass1		MOVEA.L	_db,A1
		JSR	_CloseLibrary(A6)
sfail0		MOVEA.L	_tc,A2
		TST.L	172(A2)
		BNE.S	sskipwb1
		JSR	_Forbid(A6)
		MOVEA.L	_wbmsg,A1
		JSR	_ReplyMsg(A6)
sskipwb1	MOVE.L	_tcprev,10(A2)
		MOVEQ	#0,D0
		RTS

_ParseCmdLine	MOVEM.L	D4/A4-A6,-(SP)
		MOVEA.L	_eb,A6
		MOVE.L	_cmdlen,D0
		MOVEQ	#0,D1
		JSR	_AllocMem(A6)
		TST.L	D0
		BEQ	pclfail0
		MOVEA.L	D0,A5
		MOVE.L	_cmdlen,D4
		SUBQ.L	#1,D4
		MOVEA.L	_cmdstr,A4
		MOVEA.L	_vb,A6
pclloop0	MOVEA.L	A4,A0
		MOVE.L	D4,D0
		MOVEA.L	A5,A1
		JSR	_CopyArg(A6)
		SUB.L	D0,D4
		ADDA.L	D0,A4
		TST.B	(A5)
		BEQ	pcldone0
		MOVEA.L	A5,A0
		JSR	_CheckKeyword
		CMPI.L	#_dashn,D0
		BNE	pcldashn0
		TST.B	_cmdmode
		BEQ.S	pcldashn2
		CMPI.B	#MODE_CREATE,_cmdmode
		BEQ.S	pcldashn2
		LEA	_errambigmode0,A0
		LEA	_tcname,A1
		JSR	_PrintError(A6)
		BRA	pclfail1
pcldashn2	MOVE.B	#MODE_CREATE,_cmdmode
		TST.L	_optname
		BEQ.S	pcldashn1
		LEA	_errambigopt0,A0
		LEA	_tcname,A1
		JSR	_PrintError(A6)
		BRA.S	pclfail1
pcldashn1	MOVEA.L	A4,A0
		MOVE.L	D4,D0
		MOVEA.L	A5,A1
		JSR	_CopyArg(A6)
		SUB.L	D0,D4
		ADDA.L	D0,A4
		TST.B	(A5)
		BNE.S	pcldashn3
		LEA	_errmissname0,A0
		LEA	_tcname,A1
		JSR	_PrintError(A6)
		BRA.S	pclfail1
pcldashn3	MOVEA.L	A5,A0
		JSR	_StrDup(A6)
		MOVE.L	D0,_optname
		BNE.S	pcldashn4
		LEA	_errdupstr0,A0
		LEA	_tcname,A1
		JSR	_PrintError(A6)
		BRA.S	pclfail1
pcldashn4	BRA	pclloop0
pcldashn0	MOVEA.L	A5,A0
		JSR	_StackError(A6)
		LEA	_errunknownopt0,A0
		LEA	_tcname,A1
		JSR	_PrintError(A6)
		BRA.S	pclfail1
pcldone0	NOP

		LEA	_success,A0
		LEA	_tcname,A1
		JSR	_PrintError(A6)

pclfail1	TST.L	_optname
		BEQ.S	pclskip1
		MOVEA.L	_vb,A6
		MOVEA.L	_optname,A0
		JSR	_StrFree(A6)
pclskip1	MOVEA.L	_eb,A6
		MOVEA.L	A5,A1
		MOVE.L	_cmdlen,D0
		JSR	_FreeMem(A6)
		BRA.S	pclpass0
pclfail0	MOVEA.L	_vb,A6
		LEA	_errallocmem0,A0
		LEA	_tcname,A1
		JSR	_PrintError(A6)
pclpass0	MOVEM.L	(SP)+,D4/A4-A6
		RTS

_CheckKeyword	MOVEM.L	A2-A3/A6,-(SP)
		MOVEA.L	A0,A3
		MOVEA.L	_vb,A6
		LEA	_keywords,A2
ckloop0		MOVEA.L	(A2)+,A1
		MOVEA.L	A3,A0
		JSR	_StrCmp(A6)
		TST.L	D0
		BEQ.S	ckdone0
		TST.L	(A2)
		BNE.S	ckloop0
		MOVEQ	#0,D0
		BRA.S	ckpass0
ckdone0		MOVE.L	-4(A2),D0
ckpass0		MOVEM.L	(SP)+,A2-A3/A6
		RTS


		SECTION	_Const,DATA

_tcname		DC.B	"AI",0
_dbname		DC.B	"dos.library",0
_vbname		DC.B	"vic.library",0
_success	DC.B	"success",0
_vberr		DC.B	'Unable to open VIC library!  (Verify "LIBS:vic.library" exists.)',10,0
_errallocmem0	DC.B	"unable to allocate memory for command-line argument buffer",0
_errambigmode0	DC.B	"ambiguous command mode",0
_errambigopt0	DC.B	"ambiguous option specification",0
_errdupstr0	DC.B	"unable to duplicate name string",0
_errunknownopt0	DC.B	"unknown option",0
_errmissname0	DC.B	"missing name",0
_dashn		DC.B	"-n",0
_keywords	DC.L	_dashn
		DC.L	0


		SECTION	_Var,BSS

_tc		DS.L	1
_tcprev		DS.L	1
_cmdlen		DS.L	1
_cmdstr		DS.L	1
_wbmsg		DS.L	1
_db		DS.L	1
_vb		DS.L	1
_optname	DS.L	1
_cmdmode	DS.B	1


		END