/*
 *		PATCHES.C									vi:ts=4
 *
 *      Copyright (c) Eddy Carroll, September 1994.
 *
 *		Controls the patching of dos.library and other functions in a
 *		reliable manner.
 */

#define DEBUG_PATTERN		0

#define MONITOR_SEMAPHORE	0
#define STACK_CHECK			0		/* Not currently used */

#pragma libcall SysBase RawPutChar 204 001

#include "system.h"
#include "snoopdos.h"
#include "patches.h"

/*
 *		These four pointers are imported from PATCHCODE.S, which is
 *		also where the real code size is determined.
 */
#define CODESIZE		88	/* Must equal PatchCode_End - PatchCode_Start   */
#define STACKADJUST		14	/* Must equal pc_JumpOrigFunc - pc_NormalReturn */

#define PatchCode_Size		  (PatchCode_End - PatchCode_Start)
#define PatchCode_StackAdjust (PatchCode_JumpOrigFunc - PatchCode_NormalReturn)

extern char far PatchCode_Start[];
extern char far PatchCode_NormalReturn[];
extern char far PatchCode_JumpOrigFunc[];
extern char far PatchCode_End[];

#define ASM	__asm __saveds

#if MONITOR_SEMAPHORE
Task *LoadTask;				/* Indicates we're inside LoadSeg() */
#endif

/*
 *		Now some packet IDs not defined by Commodore in dos/dosextens.h
 */
#define ACTION_DOUBLE		  2000	/* Conman: create pipe filehandle	*/
#define ACTION_FORCE		  2001	/* Conman: Force input into handler	*/
#define ACTION_DROP			  2004	/* Conman: Discard all queued input	*/

#define ACTION_GET_DISK_FSSM  4201	/* Get disk startup message			*/
#define ACTION_FREE_DISK_FSSM 4202	/* Free disk startup message		*/

/*
 *		Next, our three message types for the background process. These
 *		correspond to pattern and pathname expansion messages.
 */
#define QUIT_MSG		0
#define PATTERN_MSG		1
#define PATHEXPAND_MSG	2

/*
 *		Some miscellaneous strings
 */
char LinkPointerString[] = " --> ";

/*
 *		I needed a quick way to hook into ReleaseSemaphore() to try and
 *		figure out why ramlib would sometimes crash on calling it. What
 *		better way to do this than by re-using one of our existing
 *		patched functions (one which takes a parameter in A0). OpenDevice
 *		fits the bill nicely (we just ignore the additional parameters).
 */
#if MONITOR_SEMAPHORE
#undef LVO_OpenDevice
#define LVO_OpenDevice		-570 // ReleaseSemaphore() LVO vector
#endif

/*
 *		This structure is used to communicate between the PutMsg() patch
 *		and the background SnoopDos process when doing path expansion
 *		for ACTION_MAKE_LINK or ShowFullPaths (when we're monitoring packets,
 *		we can't guarantee that the calling processes' message port is free).
 */
typedef struct NameFromLockMsg {
	struct Message  msg;		/* Standard exec message				*/
	int				type;		/* Type of this message					*/
	BPTR			lock;		/* Lock to calculate path relative to	*/
	char			*filename;	/* Filename relative to lock			*/
	char			*buf;		/* Buffer to store result in			*/
	int				maxlen;		/* Maximum length of buffer				*/
	char			*newbuf;	/* On return, points to path in buffer 	*/
	int				sigmask;	/* Signal number to signal when done	*/
	Task			*task;		/* Task to signal when done				*/
} NameFromLockMsg;

/*
 *		This structure is used to communicate between the patches and
 *		the background SnoopDos process when doing pattern matching.
 */
typedef struct PatternMsg {
	struct Message	msg;		/* Standard exec message				*/
	int				type;		/* Type of this message					*/
	char			*name;		/* Name of task to check				*/
	int				match;		/* Result of pattern match (1 == okay)	*/
	int				sigmask;	/* Signal number to signal when done	*/
	Task			*task;		/* Task to signal when done				*/
} PatternMsg;

/*
 *		This structure is used to cache the results of pattern comparisons
 *		for various tasks.
 */
typedef struct PatternCache {
	struct MinNode	node;		/* Used to link items together				*/
	Task			*task;		/* ID of task stored in this entry			*/
	char			name[PC_NAMELEN];/* Name of task stored in this entry	*/
	int				match;		/* True=monitor this task, false=don't		*/
} PatternCache;

PatternCache	PCacheEntries[NUM_PCACHE_ENTRIES];	/* Entries on list		*/
MsgPort			BackgroundPort;	/* Where to send pattern match requests to	*/
List			PatternList;	/* List of cached patterns					*/
Semaphore		PatternCacheSem;/* Sem to control access to pattern cache  	*/
Semaphore		PatternBufSem;	/* Sem to control access to pattern buffer	*/
Semaphore		TaskCacheSem;	/* Sem used to arbitrate cache access		*/
Semaphore		DosDeviceSem;	/* Sem used to control access to dev list	*/
Process			*BackgroundProc;/* Process used to do pattern matching		*/
ULONG			PatternEnabled;		/* If true, pattern matching is enabled	*/
ULONG			PatternWildcard;	/* If true, pattern contains a wildcard	*/
Task			*SnoopTask;			/* Pointer to our own task				*/
Task			*RamLibTask;		/* Pointer to RamLib process			*/
Task			*RealRamLibTask;	/* Pointer to Ramlib process (always!)	*/
Task			*InputTask;			/* Pointer to input.device's task		*/
ULONG			NewEventSig    = -1;/* Signal bit used when new event ready	*/
ULONG			ScanDosListSig = -1;/* Signal bit used to say rescan list	*/
ULONG			TaskCacheIndex = 1; /* Index into task cache array			*/

#define PAT_MAX_LEN	(MAX_STR_LEN   + 50)
#define PAT_BUF_LEN	(PAT_MAX_LEN*2 + 3)

char			PatternString[PAT_MAX_LEN];			/* Holds source pattern	*/
char			PatternBuf[PAT_BUF_LEN];			/* Holds parsed pattern	*/
Task			*CachedTask[NUM_CACHED_TASKS];		/* Holds cached tasks	*/
Task			*DeviceTaskList[MAX_DOS_DEVICES];	/* Holds device IDs		*/

/*
 *		This structure records outstanding packets (for direct packet i/o
 *		that bypasses AmigaDOS) which we are watching out for a reply to.
 *		We keep all the additional info because it's likely that any given
 *		task will re-use a packet structure when sending packets to different
 *		DOS processes, and we want to be able to distinguish these cases.
 */
typedef struct WaitPacket {
	struct	MinNode		node;			/* Used to link items together	*/
	struct	DosPacket	*dp;			/* Packet that was dispatched	*/
	struct	Task		*sendtask;		/* Task that sent it			*/
	struct	MsgPort		*destport;		/* Port it was sent to			*/
	LONG	eventnum;					/* Associated event number		*/
	Event	*event;						/* Associated event pointer		*/
	ULONG	arg1;						/* ARG1 of the packet			*/
	ULONG	arg2;						/* ARG2 of the packet			*/
	ULONG	arg3;						/* ARG3 of the packet			*/
	char	*resmsg;					/* Message to use for result	*/
	UWORD	flags;						/* Flags associated with result	*/
} WaitPacket;

Semaphore	PacketSem;							/* Arbitrates list access	*/
struct List PacketWaitList;						/* List of waiting packets	*/
struct List	PacketFreeList;						/* List of free nodes		*/
WaitPacket	PacketEntries[NUM_PACKET_ENTRIES];	/* The nodes on the list	*/

/*
 *		Prototypes for all our replacement patched functions
 */

void BackgroundProcCode(void);
char *MyNameFromLock(BPTR lock, char *filename, char *buf, int maxlen);

typedef unsigned long (*FuncPtr)();

#define FPROTO(name)	ULONG ASM New_##name(){return (0);}
#define DPROTO(name)	ULONG ASM New_##name();

DPROTO(AddDosEntry)
DPROTO(CurrentDir)
DPROTO(DeleteFile)
DPROTO(Execute)
DPROTO(GetVar)
DPROTO(FindVar)
DPROTO(LoadSeg)
DPROTO(NewLoadSeg)
DPROTO(Lock)
DPROTO(CreateDir)
DPROTO(MakeLink)
DPROTO(Open)
DPROTO(Rename)
DPROTO(RunCommand)
DPROTO(SetVar)
DPROTO(DeleteVar)
DPROTO(SystemTagList)

DPROTO(FindPort)
DPROTO(FindResident)
DPROTO(FindSemaphore)
DPROTO(FindTask)
DPROTO(OpenDevice)
DPROTO(OpenLibrary)
DPROTO(OpenResource)
DPROTO(PutMsg)

DPROTO(OpenFont)
DPROTO(LockPubScreen)
DPROTO(FindToolType)
DPROTO(MatchToolValue)

/*
 *		These two defines control whether or not a patch is enabled. It's
 *		vital that PATCH_ENABLED be -1 rather than simply any non-zero
 *		value -- see the comments in patchcode.s for more details.
 */
#define PATCH_ENABLED		((ULONG)(-1))
#define PATCH_DISABLED		0

/*
 *		MarkCallAddr
 *		CallAddr
 *
 *		These two macros allow us to determine what address we were called
 *		from in a patch function. MarkCallAddr should be the first
 *		declaration at the start of the patch function, and then CallAddr
 *		will correspond to the caller's address throughout that function.
 *		callmarker[0] is the place marker itself, callmarker[1] is the
 *		immediate return address, and callmarker[3] takes into the
 *		account the assembly language patch stub as well.
 */
#define MarkCallAddr		ULONG volatile callmarker[1]
#define CallAddr			(callmarker[6])

/*
 *		JumpOrigFunc(retval)
 *	
 *		This macro lets us return and call the original function from the
 *		resident patch code rather than from our C code. The passed
 *		parameter can be 0 in most cases, but if the function we're using
 *		this in had a formal parameter passed in reg_d0, then that
 *		formal parameter must be used as the return value.
 *
 *		We implement this by patching the stack directly to adjust the
 *		return address so that we arrive back at a second bit of code
 *		instead of the first bit.
 */
#define JumpOrigFunc(retval) \
	{ callmarker[1] += STACKADJUST; \
	  return (ULONG)(retval); }

/*
 *		The patch structure itself. Don't change the order of the first 
 *		four fields without updating the code in PATCHCODE.S as well!
 *
 *		Note that we use a library number rather than a pointer to an
 *		actual library, for convenience.
 */
typedef struct Patch {
	UWORD	code[CODESIZE/2];		/* Assembly code to handle patch	*/
	FuncPtr	origfunc;				/* Original function pointer		*/
	FuncPtr	newfunc;				/* Replacement function pointer		*/
	ULONG	enabled;				/* If -1, patch currently active	*/
	void	*sysbase;				/* Points to ExecBase				*/
	ULONG	stackneeded;			/* #bytes free stack required		*/
	UWORD	usecount;				/* No. of tasks currently in code	*/
	UWORD	library;				/* Library to patch					*/
	WORD	offset;					/* Offset into library				*/
	WORD	pad;					/* Keep structure longword-aligned	*/
} Patch;

#define PATCH_DEF(lib,func)	{ lib, LVO_##func, (FuncPtr)New_##func }
#define PATCH_END			{ 0, 0, 0 }

/*
 *		This structure is used to initialise the main Patch array in memory.
 *
 *		IMPORTANT -- these patches MUST be defined in exactly the same
 *		order as the first entries in the GID_* defines in snoopdos.h
 *		since those same GID_* values are used to toggle the patches
 *		on and off.
 */
struct PatchInitTable {
	UWORD	library;				/* Library number to patch			*/
	WORD	offset;					/* Offset in that library			*/
	FuncPtr	newfunc;				/* Replacement function to call		*/
} PatchInit[] = {
	PATCH_END,
	PATCH_DEF(	EXEC_LIB,		FindPort		),	/* GID_FINDPORT			*/
	PATCH_DEF(	EXEC_LIB,		FindResident	),	/* GID_FINDRESIDENT		*/
	PATCH_DEF(	EXEC_LIB,		FindSemaphore	),	/* GID_FINDSEMAPHORE	*/
	PATCH_DEF(	EXEC_LIB,		FindTask		),	/* GID_FINDTASK			*/
	PATCH_DEF(	INTUITION_LIB,	LockPubScreen	),	/* GID_LOCKSCREEN		*/
	PATCH_DEF(	EXEC_LIB,		OpenDevice		),	/* GID_OPENDEVICE		*/
	PATCH_DEF(	GRAPHICS_LIB,	OpenFont		),	/* GID_OPENFONT			*/
	PATCH_DEF(	EXEC_LIB,		OpenLibrary		),	/* GID_OPENLIBRARY		*/
	PATCH_DEF(	EXEC_LIB,		OpenResource	),	/* GID_OPENRESOURCE		*/
	PATCH_DEF(	ICON_LIB,		FindToolType	),	/* GID_READTOOLTYPES	*/
	PATCH_DEF(  EXEC_LIB,		PutMsg      	),	/* GID_SENDREXX        	*/

	PATCH_DEF(	DOS_LIB,		CurrentDir		),	/* GID_CHANGEDIR		*/
	PATCH_DEF(	DOS_LIB,		DeleteFile		),	/* GID_DELETE			*/
	PATCH_DEF(	DOS_LIB,		Execute			),	/* GID_EXECUTE			*/
	PATCH_DEF(	DOS_LIB,		GetVar			),	/* GID_GETVAR			*/
	PATCH_DEF(	DOS_LIB,		LoadSeg			),	/* GID_LOADSEG			*/
	PATCH_DEF(	DOS_LIB,		Lock			),	/* GID_LOCKFILE			*/
	PATCH_DEF(	DOS_LIB,		CreateDir		),	/* GID_MAKEDIR			*/
	PATCH_DEF(	DOS_LIB,		MakeLink		),	/* GID_MAKELINK			*/
	PATCH_DEF(	DOS_LIB,		Open			),	/* GID_OPENFILE			*/
	PATCH_DEF(	DOS_LIB,		Rename			),	/* GID_RENAME			*/
	PATCH_DEF(	DOS_LIB,		RunCommand		),	/* GID_RUNCOMMAND		*/
	PATCH_DEF(	DOS_LIB,		SetVar			),	/* GID_SETVAR			*/
	PATCH_DEF(	DOS_LIB,		SystemTagList	),	/* GID_SYSTEM			*/

	/*
	 *		Now the paired functions that track their partner's state
	 */
	PATCH_DEF(	ICON_LIB,		MatchToolValue	),	/* GID_READTOOLTYPES2	*/
	PATCH_DEF(	DOS_LIB,		NewLoadSeg		),	/* GID_LOADSEG2			*/
	PATCH_DEF(	DOS_LIB,		FindVar   		),	/* GID_GETVAR2 			*/
	PATCH_DEF(	DOS_LIB,		DeleteVar 		),	/* GID_SETVAR2  		*/
	PATCH_DEF(  DOS_LIB,		AddDosEntry	    ),	/* GID_ADDDOSENTRY		*/

	PATCH_END
};

#define PatchInitCount	(sizeof(PatchInit) / sizeof(PatchInit[0]))
#define PatchInitSize	(PatchInitCount * sizeof(Patch))

/*
 *		This anchor structure is used to let us locate the patches if we
 *		quit SnoopDos and then rerun it -- the patches themselves always
 *		stay in memory once loaded.
 */
typedef struct {
	Semaphore	sem;			/* Semaphore to arbitrate access to patches	*/
	char		name[30];		/* Somewhere permanent to store sem name	*/
	Patch		patchdata[1];	/* This is an open-ended array				*/
} PatchAnchorData;

PatchAnchorData *PatchAnchor;	/* Points to the current patch anchor		*/

/*
 *		If SAS/C allowed us to examine the value of Enum types from
 *		within the preprocessor, the following check would actually work
 */
#if SASC_ALLOWS_ENUM_CHECKS
#if (sizeof(PatchInit)/sizeof(PatchInit[0])) < (GID_NUMPATCHES+1)
#error "PatchInit[] table has too few entries for patches"
#else
#if (sizeof(PatchInit)/sizeof(PatchInit[0])) > (GID_NUMPATCHES+1)
#error "PatchInit[] table has too many entries for patches"
#endif
#endif
#endif

void *LibList[NUM_LIBS];

Patch *PatchList;

/*
 *		Now we have a big table of DOS packets that we recognise while
 *		monitoring. For each packet, we record the internal AmigaDOS ID
 *		we use to recognise it, the message ID of its name, the number
 *		of dp_Args to display, and the number of results to display.
 */

/*		The following constants define how many parameters are returned
 *		by the handler when it receives this packet. PK_0 means there is
 *		no return value, PK_1 means 1 return value, PK_2 means there are
 *		two return values (with the second being the error code if the
 *		first one indicates failure) and PK_2OK means there are two actual
 *		returned values, both of which are legitimate.
 */
#define PK_0			0			/* No return value from this packet		*/
#define PK_1			1			/* One return value from this packet	*/
#define PK_2			2			/* Two return values from this packet	*/
#define PK_2OK			3			/* Two return values even when okay		*/
#define PK_MASK			3			/* Used to extract PK_CODE				*/

/*
 *		How to detect when an event failed. Selecting none of these means
 *		that the packet can never fail.
 */
#define PKF_BOOL		0x08		/* True if zero result == fail			*/
#define PKF_NEG			0x10		/* True if -1 result == fail			*/

/*
 *		We want to completely ignore some packets, namely all those sent
 *		by the handler to lower level devices like timer.device or scsi.device
 *		for its own needs. We use PK_IGNORE to identify all such packets
 *		so we can discard them.
 *
 *		PK_COMMON packets are those which are monitored when the
 *		"Monitor Packets" option is enabled. These are connected
 *		with the associated DOS functions Open(), Lock() etc. and
 *		are output as such by SnoopDos, rather than as raw packets.
 *
 *		PK_RAW is used for our sentinel packet which indicates that
 *		even the packet type should be output since it's not recognised.
 */
#define PK_COMMON		0x20		/* Common packet (Open, Lock, etc.)		*/
#define PK_RAW			0x40		/* Completely raw packet				*/
#define PK_IGNORE		0x80		/* Ignore this packet completely		*/

/*
 *		Most packets return a value in dp_Res1 and if that value is
 *		zero or -1, then the error code is in dp_Res2. We define
 *		two constants to handle these common case.
 */
#define PK_BOOLEAN		(PKF_BOOL | PK_2)
#define PK_NEGATIVE		(PKF_NEG  | PK_2)

/*
 *		Warning: this table MUST be sorted by packet ID in ascending order,
 *		since a binary search is used to locate packet types
 */
#define LAST_PACK_MSG		0

struct PacketRef {
	UWORD	packetid;				/* The ID we watch out for				*/
	UWORD	msgid;					/* The name of the action to print		*/
	UBYTE	numparams;				/* Number of parameters to display		*/
	UBYTE	flags;					/* How to interpret the result			*/
} PacketTable[] = {
	ACTION_STARTUP,			MSG_ACT_STARTUP,		 1, PK_1,
	ACTION_GET_BLOCK,		MSG_ACT_GET_BLOCK,		 1, PK_IGNORE,
	ACTION_SET_MAP,			MSG_ACT_SET_MAP,		 1, PK_IGNORE,
	ACTION_DIE,				MSG_ACT_DIE,			 0, PK_BOOLEAN,
	ACTION_EVENT,			MSG_ACT_EVENT,			 1, PK_IGNORE,
	ACTION_CURRENT_VOLUME,	MSG_ACT_CURRENT_VOLUME,	 1, PK_2OK,
	ACTION_LOCATE_OBJECT,	MSG_ACT_LOCATE_OBJECT,	 3, PK_BOOLEAN | PK_COMMON,
	ACTION_RENAME_DISK,		MSG_ACT_RENAME_DISK,	 1, PK_BOOLEAN,
	ACTION_FREE_LOCK,		MSG_ACT_FREE_LOCK,		 1, PK_BOOLEAN,
	ACTION_DELETE_OBJECT,	MSG_ACT_DELETE_OBJECT,	 2, PK_BOOLEAN | PK_COMMON,
	ACTION_RENAME_OBJECT,	MSG_ACT_RENAME_OBJECT,	 4, PK_BOOLEAN | PK_COMMON,
	ACTION_MORE_CACHE,		MSG_ACT_MORE_CACHE,		 1, PKF_BOOL   | PK_2OK,
	ACTION_COPY_DIR,		MSG_ACT_COPY_DIR,		 1, PK_BOOLEAN,
	ACTION_WAIT_CHAR,		MSG_ACT_WAIT_CHAR,		 1, PKF_BOOL   | PK_2OK,
	ACTION_SET_PROTECT,		MSG_ACT_SET_PROTECT,	 4, PK_BOOLEAN,
	ACTION_CREATE_DIR,		MSG_ACT_CREATE_DIR,		 2, PK_BOOLEAN | PK_COMMON,
	ACTION_EXAMINE_OBJECT,	MSG_ACT_EXAMINE_OBJECT,	 2, PK_BOOLEAN,
	ACTION_EXAMINE_NEXT,	MSG_ACT_EXAMINE_NEXT,	 2, PK_BOOLEAN,
	ACTION_DISK_INFO,		MSG_ACT_DISK_INFO,		 1, PK_BOOLEAN,
	ACTION_INFO,			MSG_ACT_INFO,			 2, PK_BOOLEAN,
	ACTION_FLUSH,			MSG_ACT_FLUSH,			 0, PK_BOOLEAN,
	ACTION_SET_COMMENT,		MSG_ACT_SET_COMMENT,	 4, PK_BOOLEAN,
	ACTION_PARENT,			MSG_ACT_PARENT,			 1, PK_BOOLEAN,
	ACTION_TIMER,			MSG_ACT_TIMER,			 1, PK_IGNORE,
	ACTION_INHIBIT,			MSG_ACT_INHIBIT,		 1, PK_BOOLEAN,
	ACTION_DISK_TYPE,		MSG_ACT_DISK_TYPE,		 1, PK_IGNORE,
	ACTION_DISK_CHANGE,		MSG_ACT_DISK_CHANGE,	 1, PK_1,
	ACTION_SET_DATE,		MSG_ACT_SET_DATE,		 4, PK_BOOLEAN,
	ACTION_SAME_LOCK,		MSG_ACT_SAME_LOCK,		 2, PK_BOOLEAN,
	ACTION_READ,			MSG_ACT_READ,			 3, PK_NEGATIVE,
	ACTION_WRITE,			MSG_ACT_WRITE,			 3, PK_NEGATIVE,
	ACTION_SCREEN_MODE,		MSG_ACT_SCREEN_MODE,	 1, PK_BOOLEAN,
	ACTION_CHANGE_SIGNAL,	MSG_ACT_CHANGE_SIGNAL,	 3, PKF_BOOL   | PK_2OK,
	ACTION_READ_RETURN,		MSG_ACT_READ_RETURN,	 1, PK_IGNORE,
	ACTION_WRITE_RETURN,	MSG_ACT_WRITE_RETURN,	 1, PK_IGNORE,
	ACTION_FINDUPDATE,		MSG_ACT_FINDUPDATE,		 3, PK_BOOLEAN | PK_COMMON,
	ACTION_FINDINPUT,		MSG_ACT_FINDINPUT,		 3, PK_BOOLEAN | PK_COMMON,
	ACTION_FINDOUTPUT,		MSG_ACT_FINDOUTPUT,		 3, PK_BOOLEAN | PK_COMMON,
	ACTION_END,				MSG_ACT_END,			 1, PK_BOOLEAN,
	ACTION_SEEK,			MSG_ACT_SEEK,			 3, PK_NEGATIVE,
	ACTION_FORMAT,			MSG_ACT_FORMAT,			 2, PK_BOOLEAN,
	ACTION_MAKE_LINK,		MSG_ACT_MAKE_LINK,		 4, PK_BOOLEAN | PK_COMMON,
	ACTION_SET_FILE_SIZE,	MSG_ACT_SET_FILE_SIZE,	 3, PK_NEGATIVE,
	ACTION_WRITE_PROTECT,	MSG_ACT_WRITE_PROTECT,	 2, PK_BOOLEAN,
	ACTION_READ_LINK,		MSG_ACT_READ_LINK,		 4, PK_BOOLEAN,
	ACTION_FH_FROM_LOCK,	MSG_ACT_FH_FROM_LOCK,	 2, PK_BOOLEAN,
	ACTION_IS_FILESYSTEM,	MSG_ACT_IS_FILESYSTEM,	 0, PK_BOOLEAN,
	ACTION_CHANGE_MODE,		MSG_ACT_CHANGE_MODE,	 3, PK_BOOLEAN,
	ACTION_COPY_DIR_FH,		MSG_ACT_COPY_DIR_FH,	 1, PK_BOOLEAN,
	ACTION_PARENT_FH,		MSG_ACT_PARENT_FH,		 1, PK_BOOLEAN,
	ACTION_EXAMINE_ALL,		MSG_ACT_EXAMINE_ALL,	 5, PK_BOOLEAN,
	ACTION_EXAMINE_FH,		MSG_ACT_EXAMINE_FH,		 2, PK_BOOLEAN,
	ACTION_EXAMINE_ALL_END,	MSG_ACT_EXAMINE_ALL_END, 5, PK_BOOLEAN,
	ACTION_SET_OWNER,		MSG_ACT_SET_OWNER,		 4, PK_BOOLEAN,
	ACTION_DOUBLE,			MSG_ACT_DOUBLE,			 3, PK_BOOLEAN,
	ACTION_FORCE,			MSG_ACT_FORCE,			 3, PK_BOOLEAN,
	ACTION_STACK,			MSG_ACT_STACK,			 3, PK_BOOLEAN,
	ACTION_QUEUE,			MSG_ACT_QUEUE,			 3, PK_BOOLEAN,
	ACTION_DROP,			MSG_ACT_DROP,			 1, PK_BOOLEAN,
	ACTION_LOCK_RECORD,		MSG_ACT_LOCK_RECORD,	 5, PK_BOOLEAN,
	ACTION_FREE_RECORD,		MSG_ACT_FREE_RECORD,	 3, PK_BOOLEAN,
	ACTION_ADD_NOTIFY,		MSG_ACT_ADD_NOTIFY,		 1, PK_BOOLEAN,
	ACTION_REMOVE_NOTIFY,	MSG_ACT_REMOVE_NOTIFY,	 1, PK_BOOLEAN,
	ACTION_SERIALIZE_DISK,	MSG_ACT_SERIALIZE_DISK,	 0, PK_BOOLEAN,
	ACTION_GET_DISK_FSSM,	MSG_ACT_GET_DISK_FSSM,	 0, PK_BOOLEAN,
	ACTION_FREE_DISK_FSSM,	MSG_ACT_FREE_DISK_FSSM,	 0, PK_BOOLEAN,
	/*
 	 *		Our final table entry is used as a sentinel -- we key off the
	 *		final packet message as an indication that we're finished
	 *		searching the table.
 	 */
	0,						LAST_PACK_MSG,			 4, PK_2OK | PK_RAW
};

#define NUM_PACKETS		(sizeof(PacketTable) / sizeof(PacketTable[0]))

/*****************************************************************************
 *
 *		Start of functions!
 *
 *****************************************************************************/

/*
 *		InitPatches()
 *
 *		Sets up the library pointers and makes sure all the patch
 *		code vectors are initialised properly.
 *
 *		Also allocates memory for the patches (if necessary)
 *
 *		Returns TRUE for success, FALSE for failure.
 */
int InitPatches(void)
{
	struct PatchInitTable *pi;
	Patch *p;
	int i;

	/*
	 *		Before doing anything else, do a quick check to ensure that
	 *		the patch code in PATCHCODE.S agrees with the equivalent patch
	 *		structure defined in this file.
	 */
	if (CODESIZE != PatchCode_Size || STACKADJUST != PatchCode_StackAdjust) {
		Printf("In patches.c, adjust CODESIZE by %ld and STACKADJUST by %ld\n",
				PatchCode_Size-CODESIZE, PatchCode_StackAdjust-STACKADJUST);
		return (FALSE);
	}

	LibList[DOS_LIB]		= DOSBase;
	LibList[EXEC_LIB]		= SysBase;
	LibList[INTUITION_LIB]	= IntuitionBase;
	LibList[GRAPHICS_LIB]	= GfxBase;
	LibList[ICON_LIB]		= IconBase;

	/*
	 *		First, let's calculate our ROM start and end address.
	 *		We do this by getting the address pointed to by the
	 *		Open LVO vector in exec.library and rounding it down
	 *		to the nearest 512K
	 */
	RomStart = (*((ULONG *)(((char *)SysBase) + LIB_OPEN + 2))) & 0xFFF80000;
	RomEnd   = RomStart + 0x7FFFF;

	/*
	 *		Now see if the patches were already installed by an earlier
	 *		version of SnoopDos.
	 */
	Forbid();
	PatchAnchor = (PatchAnchorData *)FindSemaphore(PATCHES_NAME);
	if (!PatchAnchor) {
		/*
		 *		Patches weren't found, so allocate a new set and initialise
		 *		it with our patch code.
		 */
		PatchAnchor = AllocMem(sizeof(PatchAnchorData) + PatchInitSize,
							   MEMF_ANY | MEMF_CLEAR);
		if (!PatchAnchor) {
			Permit();
			return (FALSE);
		}
		strcpy(PatchAnchor->name, PATCHES_NAME);
		PatchAnchor->sem.ss_Link.ln_Name = PatchAnchor->name;
		PatchAnchor->sem.ss_Link.ln_Pri  = 0;
		AddSemaphore(&PatchAnchor->sem);
	}
	/*
	 *		Now PatchAnchor points to our patch list in memory -- copy over
	 *		our patch code and initialised data accordingly.
	 */
	PatchList = PatchAnchor->patchdata;
	for (pi = PatchInit+1, p = PatchList+1; pi->offset; p++, pi++) {
		memcpy(p->code, PatchCode_Start, CODESIZE);
		p->library		= pi->library;
		p->offset		= pi->offset;
		p->newfunc		= pi->newfunc;
		p->stackneeded	= CurSettings.StackLimit;
		p->sysbase		= SysBase;
	}
	CacheClearU();	/* Invalidate cache for code we just built */
	Permit();

	/*
	 *		Now initialise other variables associated with the patch code
	 */
	SnoopTask	= SysBase->ThisTask;
	InputTask	= FindTask("input.device");
	InitRamLibPatch();
	InitSemaphore(&PatternBufSem);
	InitSemaphore(&PatternCacheSem);
	InitSemaphore(&TaskCacheSem);
	InitSemaphore(&DosDeviceSem);
	InitSemaphore(&PauseSem);
	InitSemaphore(&PacketSem);

	UpdateDeviceList(SCANDEV_IMMEDIATE);

	NewList(&PatternList);
	for (i = 0; i < NUM_PCACHE_ENTRIES; i++)
		AddHead(&PatternList, (Node *)&PCacheEntries[i]);
	
	NewList(&PacketWaitList);
	NewList(&PacketFreeList);
	for (i = 0; i < NUM_PACKET_ENTRIES; i++)
		AddHead(&PacketFreeList, (Node *)&PacketEntries[i]);

	NewEventSig = AllocSignal(-1);
	if (NewEventSig == -1)
		return (FALSE);
	NewEventMask = 1 << NewEventSig;

	/*
	 *		Signal bit used to tell main task to rescan device list because
	 *		a device was added (or removed?)
	 */
	ScanDosListSig = AllocSignal(-1);
	if (ScanDosListSig == -1)
		return (FALSE);
	ScanDosListMask = 1 << ScanDosListSig;

	/*
	 *		Now create a background process to handle process matching.
	 *		We run this at a high priority to ensure quick response.
	 *
	 *		We first of all initialise our pattern port, and set it
	 *		to NOT signal our task when a message arrives. This ensures
	 *		that if, for some reason, our new process doesn't run
	 *		until after someone tries to send it a message, nothing
	 *		bad will happen (e.g. signalling a task at 0x0000)
	 *		
	 *		The background process itself will replace this with
	 *		PA_SIGNAL to make things work normally again.
	 */
	NewList(&BackgroundPort.mp_MsgList);
	BackgroundPort.mp_Flags = PA_IGNORE;
	BackgroundProc = CreateNewProcTags(NP_Entry,     BackgroundProcCode,
					   				   NP_Name, 	 BACKGROUND_NAME,
									   NP_Priority,  5,
					   				   TAG_DONE);

	return (BackgroundProc != NULL);
}

/*
 *		UpdatePatches()
 *
 *		Scans the list of patches, updating our behaviour as follows.
 *		This means:
 *
 *			- Any patch that's currently active and which has been flagged
 *			  as inactive should be disabled if possible (i.e. origfunc
 *			  set to NULL).
 *
 *			- Any patch that's currently inactive and which has been flagged
 *			  as active should be enabled if possible (i.e. origfunc set
 *			  to point to the original function).
 *
 *		If we are currently Disabled, then all patches are made inactive.
 *		
 */
void UpdatePatches(void)
{
	Patch *p;

	if (!PatchList)
		return;

	if (MonPackets || ShowAllPackets)
		UpdateDeviceList(SCANDEV_IMMEDIATE); /* Keep device list up to date */

	Forbid();			/* Don't even think about letting anyone else run! */

	/*
	 *		Update status of pair functions to reflect the master
	 *		function's status
	 */
	PatchList[GID_READTOOLTYPES2].enabled	=
							PatchList[GID_READTOOLTYPES].enabled;
	PatchList[GID_LOADSEG2].enabled			=
							PatchList[GID_LOADSEG].enabled;
	PatchList[GID_GETVAR2].enabled			=
							PatchList[GID_GETVAR].enabled;
	PatchList[GID_SETVAR2].enabled			=
							PatchList[GID_SETVAR].enabled;

	for (p = PatchList+1; p->offset; p++) {
		void *lib        = LibList[p->library];
		int func_enabled = (Disabled ? PATCH_DISABLED : p->enabled);

		p->stackneeded	 = CurSettings.StackLimit;	/* Update this */

		if (!lib)			/* Skip over any libraries we couldn't open */
			continue;

		if (p->origfunc == NULL && func_enabled != PATCH_DISABLED) {
			/*
			 *		Okay, we want to enable this function.
			 */
			p->origfunc = (FuncPtr)SetFunction(lib, p->offset, (FuncPtr)p);
		} else if (p->origfunc && func_enabled == PATCH_DISABLED) {
			/*
			 *		Okay, we want to disable this function. However, we
			 *		may not be able to do this cleanly, if it's been
			 *		since patched by someone else. Thus, if our attempt
			 *		to unpatch it doesn't return a pointer to our replacement
			 *		code, we have to leave the patch installed. However, it
			 *		will be re-tried automatically the next time someone
			 *		calls this function.
			 */
			FuncPtr curfunc = (FuncPtr)SetFunction(lib, p->offset, p->origfunc);

			if (curfunc != (FuncPtr)p) {
				/*
				 *		Uhoh -- someone else patched us!
				 */
				SetFunction(lib, p->offset, (FuncPtr)curfunc);
			} else {
				/*
				 *		We unpatched successfully, so zero pointer
				 */
				p->origfunc = NULL;
			}
		}
	}
	/*
	 *		We disable stack monitoring for the PutMsg() patch because
	 *		we need to catch the return packet from small-stack processes
	 *		like RAM: etc. Instead, that patch does its own stack checking.
	 */
	PatchList[GID_SENDREXX].stackneeded = 0;

	CacheClearU();	/* Just to be safe */
	Permit();
	/*
	 *		Finally, check if we've disabled packet monitoring and
	 *		there are some outstanding packets that have not yet had
	 *		return values assigned. Free them all as appropriate
	 */
	if (!MonPackets && !ShowAllPackets)
	{
		for (;;) {
			WaitPacket *wp;
			Event *ev;
			LONG  seqnum;

			ObtainSemaphore(&PacketSem);
			if (IsListEmpty(&PacketWaitList)) {
				ReleaseSemaphore(&PacketSem);
				break;
			}
			wp 	   = (WaitPacket *)RemHead(&PacketWaitList);
			AddHead(&PacketFreeList, (Node *)wp);
			ev	   = wp->event;
			seqnum = wp->eventnum;
			ReleaseSemaphore(&PacketSem);

			ObtainSemaphore(&BufSem);
			if (seqnum >= RealFirstSeq) {
				ev->status = ES_READY;
				Signal(SnoopTask, NewEventMask);
			}
			ReleaseSemaphore(&BufSem);
		}
	}
}

/*
 *		CleanupPatches()
 *
 *		Frees any resources allocated when initialising this module. Also
 *		disable any patches that are still active.
 */
void CleanupPatches(void)
{
	static int unpatchtries = 0;

	if (PatchList) {
		struct PatchInitTable *pi;
		Patch *p;

		for (p = PatchList+1; p->offset; p++)
			p->enabled = PATCH_DISABLED;
		UpdatePatches();

		/*
		 *		Now our patches have been disabled, and it's time to
		 *		ensure nobody got left inside our code. We do this by
		 *		checking that all the usecounts are set to zero, and if
		 *		they're not, giving the user the option of continuing
		 *		to try, or exiting.
		 *
		 *		Before we do this, however, we record all the functions
		 *		which still have a usecount > 0 -- this way, if someone
		 *		else runs SnoopDos while we are checking and installs
		 *		a new set of patches in the patchtable, and enables
		 *		some additional functions, we don't have to check that
		 *		ALL usage counts are zero, only those which were non-zero
		 *		at the start of the loop.
		 *
		 *		(That is, we ignore any usage counts which are results of
		 *		functions being re-enabled by the new SnoopDos -- typically,
		 *		there will only be one or two functions at most to be
		 *		checked.)
		 *
		 *		For convenience, we re-use the PatchInit list to hold the
		 *		exit "usage" states for each function -- we won't be needing
		 *		it any more.
		 */
		for (p = (PatchList+1), pi = (PatchInit+1); p->offset; p++, pi++)
			pi->offset = p->usecount;

		for (;;) {
			for (p = (PatchList+1), pi = (PatchInit+1); p->offset; p++, pi++) {
				if (pi->offset > 0) {
					if (p->usecount > 0)
						break;
					pi->offset = 0;	/* No longer active, so mark as done */
				}
			}
			if (p->offset) {
				/*
				 *		Couldn't unpatch, so go around the loop and try
				 *		again. After about 8-10 seconds, we warn the user
				 *		that we're still trying.
				 */
				unpatchtries++;
				if (unpatchtries == 5) {
					char errorbuf[400];
					
					mysprintf(errorbuf, MSG(MSG_ERROR_UNPATCH),
							  GetFuncName(p - PatchList));
					ShowError(errorbuf);
				}
				Delay(2*50);		/* Wait 2 seconds before next try */
			} else
				break;
		}
	}
	if (BackgroundProc) {
		/*
		 *		Send a terminate message to the pattern process and wait
		 *		for it to respond
		 */
		PatternMsg pmsg;

		pmsg.type                = QUIT_MSG;
		pmsg.sigmask			 = SIGF_SINGLE;
		pmsg.task				 = SysBase->ThisTask;
		pmsg.msg.mn_Node.ln_Name = NULL;

		SetSignal(0, SIGF_SINGLE);		/* Ensure signal is clear first */
		PutMsg(&BackgroundPort, &pmsg);	/* Send quit message to process	*/
		Wait(SIGF_SINGLE);				/* Wait for acknowledgement		*/
	}
	if (NewEventSig    != -1) FreeSignal(NewEventSig),    NewEventSig    = -1;
	if (ScanDosListSig != -1) FreeSignal(ScanDosListSig), ScanDosListSig = -1;
}

/*
 *		InitRamLibPatch()
 *
 *		Attempts to patch the ramlib process so that it no longer uses
 *		a message port with the signal bit set to SIGB_SINGLE (which
 *		is asking for trouble if you call any functions that use
 *		semaphores, since a SIGB_SINGLE can be generated by a message
 *		arriving at the message port while waiting for the semaphore,
 *		causing all hell to break loose -- the semaphore appears to be
 *		obtained while in fact it's still in use by someone else).
 */
void InitRamLibPatch(void)
{
	unsigned char *ramdata;
	int i;
	
	RealRamLibTask = FindTask("ramlib");
	if (NoPatchRamLib || !RealRamLibTask) {
		/*
		 *		Not patching ramlib, so instead, install our bypass patch
		 *		that stops us from monitoring any ramlib tasks so that
		 *		we won't get crashed.
		 */
		RamLibTask = RealRamLibTask;
	} else {
		/*
		 *		Patch ramlib itself so that it doesn't use SIGF_SINGLE
		 *		any more.
		 */
		ramdata = (void *)SysBase->ex_RamLibPrivate;
		if (!ramdata) {
			/*
			 *		You never know ... this field may no longer exist. If
			 *		it doesn't, then assume the bug no longer exists either
			 *		and exit.
			 */
			return;
		}
		for (i = 0x20; i < 0x50; i += 2) {
			struct MsgPort *ramport = (struct MsgPort *)(ramdata + i);

			/*
			 *		Depending on the Kickstart version (V37 through V40)
			 *		the ramlib message port lies somewhere in the range
			 *		0x30 to 0x50 of the ramlib's private area. We do
			 *		some integrity checks on the port to make sure it's
			 *		not just a lucky hit. If some future SetPatch fixes
			 *		this, then we will never find a match, which suits
			 *		us fine.
			 */
			if (ramport->mp_Flags   == 0			&&
				ramport->mp_SigBit  == SIGB_SINGLE	&&
				ramport->mp_SigTask == RealRamLibTask)
			{
				/*
				 *		Now patch ramlib's message port so it won't use
				 *		SIGB_SINGLE any more. This is a little tricky, since
				 *		ramlib process will be in the middle of WaitPort()
				 *		and WaitPort() won't return until a message arrives at
				 *		the port. Essentially, we can only change the port's
				 *		message bit while we are sure that the ramlib process
				 *		is NOT in a WaitPort(). We do this by bumping our
				 *		process priority up to 127 temporarily, making the
				 *		call, and then restoring it.
				 *
				 *		Since we're at a higher priority than ramlib, then
				 *		as soon as ramlib responds to the message, it will
				 *		be switched out (in the Ready state) before it has a
				 *		chance to call WaitPort() and go back to sleep again.
				 */
				ULONG oldpri;

				Forbid();
				oldpri = SetTaskPri(SysBase->ThisTask, 127);
				OpenLibrary("ramlib-patch.library", 0);	/* Wake-up ramlib */
				ramport->mp_SigBit = SIGBREAKB_CTRL_E;
				SetTaskPri(SysBase->ThisTask, oldpri);
				Permit();
				break;
			}
		}
	}
}

/*
 *		UpdateDeviceList(method)
 *
 *		Updates the DOS device list. This is used to keep an internal list
 *		of all the task IDs of DOS handlers so we can quickly determine
 *		if a given ID belongs to a handler or not.
 *
 *		This should be called at least once, and ideally every now and
 *		again, so that new devices added to the DOS list can be detected
 *		and old ones ignored.
 *
 *		The method can be SCANDEV_IMMEDIATE, to immediately scan the device
 *		list, or SCANDEV_DELAY to wait for a second before updating. The
 *		delay is because the patch that signals us to rescan (AddDosEntry)
 *		happens before the device is actually fully initialised, so we need
 *		to give it a little extra time to get going.
 *
 *		It's not critical if we don't give it enough time, it just means
 *		that the new device won't appear in the list after all -- a bit
 *		annoying for people using the packet debugger to help debug a
 *		device they keep mounting interactively.
 */
void UpdateDeviceList(int method)
{
	struct DosList *dlist;
	int i = 0;

	if (method == SCANDEV_DELAY)
		Delay(50);

	ObtainSemaphore(&DosDeviceSem);
	dlist = LockDosList(LDF_DEVICES | LDF_READ);
	while ((dlist = NextDosEntry(dlist, LDF_DEVICES))
			 && i < (MAX_DOS_DEVICES-1))
	{
		if (dlist->dol_Task)
			DeviceTaskList[i++] = dlist->dol_Task->mp_SigTask;
	}
	UnLockDosList(LDF_DEVICES | LDF_READ);
	DeviceTaskList[i] = 0;
#if 0
	Printf("New device list:\n");
	for (i = 0; DeviceTaskList[i]; i++)
		Printf("    %2ld = %s\n", i, DeviceTaskList[i]->tc_Node.ln_Name);
#endif
	ReleaseSemaphore(&DosDeviceSem);
}

/*
 *		SetPattern(string, ignorewb)
 *
 *		Sets the current pattern to the given string (possibly containing
 *		wildcards). If ignorewb is true, then the pattern is modified to
 *		also exclude any processes with the names Workbench, Shell Process
 *		or Background CLI.
 */
void SetPattern(char *pattern, int ignorewb)
{
	PatternCache *pc;
	char *p;

	if (*pattern == '\0' && !ignorewb) {
		PatternEnabled = 0;
		return;
	}
	ObtainSemaphore(&PatternCacheSem);
	ObtainSemaphore(&PatternBufSem);
	PatternEnabled = 0;

	/*
	 *		Now, update our pattern string. If the pattern string is empty,
	 *		we disable patterns. If the pattern string contains no wildcards,
	 *		we enable patterns but disable wildcard patching (this is much
	 *		faster, since it can be done in the patch's task, rather than
	 *		having to call back to the main routine).
	 *
	 *		We allocate both pattern semaphores: the Cache semaphore to
	 *		ensure that no pattern code can execute when PatternEnabled
	 *		is zero, and the buffer semaphore to ensure that the pattern
	 *		process doesn't try to do pattern matching while we are
	 *		building the new pattern string.
	 *
	 *		We need to be careful to get the cache semaphore BEFORE we get
	 *		the pattern buffer semaphore, otherwise we could deadlock if
	 *		a patch decided to check a pattern at just the wrong time.
	 */
	if (ignorewb) {
		if (*pattern == '\0')
			strcpy(PatternString, PAT_EASY_EXCLUDE);
		else
			mysprintf(PatternString, PAT_COMPLEX_EXCLUDE, pattern);
	} else
		strcpy(PatternString, pattern);

	/*
	 *		Now, due to a bug in AmigaDOS's ParsePatternNoCase, we
	 *		have to convert the string to upper case first. (The bug
	 *		is that character classes like [a-z] will never match anything,
	 *		only [A-Z] will work. Since it's case insensitive for
	 *		everything else, we simply convert everything to upper case.)
	 */
	for (p = PatternString; *p; p++) {
		if (*p >= 'a' && *p <= 'z')
			*p &= 0x5F;
	}
	PatternWildcard = ParsePatternNoCase(PatternString,
										 PatternBuf, PAT_BUF_LEN);

	if (PatternWildcard != -1)	/* Only enable patterns if successful */
		PatternEnabled = 1;
	
	/*
	 *		Regardless of what happened, invalidate the pattern cache
	 *		to force a new match for each task name.
	 */
	FORLIST(&PatternList, pc)
		pc->task = NULL;

	ReleaseSemaphore(&PatternBufSem);
	ReleaseSemaphore(&PatternCacheSem);
}

/*
 *		FlushWaitingPackets(void)
 *
 *		Flushes any packets currently in the waiting packets queue
 *		(marking them as missed). Usually called after packet monitoring
 *		has been disabled, in case there were any half-completed packets
 *		in the queue.
 */
void FlushWaitingPackets(void)
{
	WaitPacket *wp;
	int flushed = 0;

	ObtainSemaphore(&PacketSem);
	wp = HeadNode(&PacketWaitList);
	while (NextNode(wp)) {
		ULONG seqnum = wp->eventnum;
		Event *ev    = wp->event;

		Remove((Node *)wp);
		AddHead(&PacketFreeList, (Node *)wp);
		ReleaseSemaphore(&PacketSem);	/* Never lock two sem's at once! */
		ObtainSemaphore(&BufSem);
		if (seqnum >= RealFirstSeq) {
			ev->result = MSG(MSG_RES_MISSED);
			ev->status = ES_READY;
			flushed    = 1;
		}
		ReleaseSemaphore(&BufSem);
		ObtainSemaphore(&PacketSem);
		wp = HeadNode(&PacketWaitList);
	}
	ReleaseSemaphore(&PacketSem);

	if (flushed)
		Signal(SnoopTask, NewEventMask);
}

/*
 *		LoadFuncSettings(FuncSets)
 *
 *		Downloads the values in the passed-in FuncSettings array to the
 *		various local structures that require them.
 */
void LoadFuncSettings(FuncSettings *func)
{
	Patch *p;
	int i;

	if (!PatchList)
		return;

	Forbid();
	for (p = PatchList+1, i = 1; p->offset; p++, i++)
		p->enabled = ((func->Opts[i] && !Disabled) ?
							PATCH_ENABLED : PATCH_DISABLED);
	/*
	 *		A bit of a hack -- the PutMsg() function is needed to monitor
	 *		three individual patches: SendRexx, Monitor Packets, and
	 *		Packet Debugger. We do the check for the last two here.
	 */
	if ((func->Opts[GID_MONPACKETS] || func->Opts[GID_MONALLPACKETS]) &&
	     !Disabled)
	{
		PatchList[GID_SENDREXX].enabled    = PATCH_ENABLED;
		PatchList[GID_ADDDOSENTRY].enabled = PATCH_ENABLED;
	} else {
		PatchList[GID_ADDDOSENTRY].enabled = PATCH_DISABLED;
		FlushWaitingPackets();
	}
	UpdatePatches();
	Permit();
	SetPattern(func->Pattern, func->Opts[GID_IGNOREWB]);
}

/*
 *		BackgroundProcCode()
 *
 *		This function is called as a separate process. It is used
 *		to service requests for pattern matching by patched code.
 *		We do this in a separate process to ensure we have enough
 *		stack to perform the pattern matching (pattern matching can
 *		be very stack-intensive.)
 *
 *		This process is also used to do pathname expansion when we
 *		are monitoring packets -- this is needed for both the
 *		ShowAllPaths option and monitoring of the ACTION_MAKE_LINK packet.
 *
 *		We keep going until we get a QUIT_MSG from the main task,
 *		at which point we exit.
 */
void __saveds BackgroundProcCode(void)
{
#if DEBUG_PATTERN
	BPTR dbfile = Open("CON:100/100/400/100/Pattern/WAIT/AUTO/CLOSE",
					   MODE_OLDFILE);
#define DBP(s)	FPrintf s
#else
#define DBP(s)
#endif

	Task  *quittask;
	ULONG  quitsig;
	int    done = 0;

	/*
	 *		Our first task is to re-route the message port allocated
	 *		on our behalf by the main process so that it points to us.
	 *		We also re-enable signals on incoming messages to ensure
	 *		we are correctly interrupted.
	 */		
	BackgroundPort.mp_SigTask = SysBase->ThisTask;
	BackgroundPort.mp_SigBit  = AllocSignal(-1);	/* Better not fail!		*/
	BackgroundPort.mp_Flags   = PA_SIGNAL;			/* Allow signalling now	*/
	DBP((dbfile, "Started pattern debug code\n"));

	while (!done) {
		/*		Now handle all pattern messages queue on the pattern port.
		 *		Unlike normal messages, we don't simply reply to these
		 *		with ReplyMsg(); instead, we signal the indicated task
		 *		using the signal bit specified in the message.
		 *
		 *		This is because the sender may not have an available
		 *		signal bit to use to receive the reply so it uses a
		 *		system bit which is guaranteed to be unused.
		 *
		 *		Note that we grab the pattern semapore while we process
		 *		the messages; this stops the mainline code from updating
		 *		the middle of the buffer while we are doing a comparison.
		 */
		PatternMsg      *pmsg;
		NameFromLockMsg *lmsg;

		WaitPort(&BackgroundPort);

		ObtainSemaphore(&PatternBufSem);
		while ((pmsg = (void *)GetMsg(&BackgroundPort)) != NULL) {
			DBP((dbfile, "Got a message, type = %ld!\n", pmsg->type));
			switch (pmsg->type) {

				case QUIT_MSG:
					/*
					 *		We've been told to quit by the main task, so
					 *		set some variables appropriately. We flush
					 *		the remaining messages in the queue first
					 *		just to be safe (we wouldn't want to leave
					 *		anyone hanging, now, would we?)
					 */
					PatternEnabled	= 0;	/* Safety first */
					done	 		= 1;
					quittask		= pmsg->task;
					quitsig  		= pmsg->sigmask;
					break;

				case PATTERN_MSG:
					DBP((dbfile, "Got a pmatch from %s\n", pmsg->name));
					if (PatternEnabled)
						pmsg->match = MatchPatternNoCase(PatternBuf,
														 pmsg->name);
					else
						pmsg->match = 1; /* If disabled, everything matches */

					Signal(pmsg->task, pmsg->sigmask);
					break;

				case PATHEXPAND_MSG:
					lmsg         = (NameFromLockMsg *)pmsg;
					lmsg->newbuf = MyNameFromLock(lmsg->lock, lmsg->filename,
												  lmsg->buf,  lmsg->maxlen);
					Signal(lmsg->task, lmsg->sigmask);
					break;
			}
		}
		ReleaseSemaphore(&PatternBufSem);
	}

#if DEBUG_PATTERN
	DBP((dbfile, "Quitting...\n"));
	Close(dbfile);
#endif

	/*
	 *		Now cleanup and exit from this task. We need to Forbid()
	 *		before we signal the main task that we're ready to quit,
	 *		since otherwise the main task might go ahead and unload
	 *		us before we have a chance to execute the last few lines
	 *		in the function (e.g. if the main task is running at a
	 *		higher priority than we are).
	 */		
	Forbid();
	FreeSignal(BackgroundPort.mp_SigBit);
	Signal(quittask, quitsig);
}

/*
 *		CheckPattern(name, namelen)
 *
 *		Checks if our task's name has been masked out by the
 *		user, using our current AmigaDOS pattern string.
 *
 *		We can't call the DOS pattern match function directly, due to
 *		stack limitations (the caller may not have a very big stack).
 *		So, we maintain a cache of recently used tasks -- the first
 *		task on the list is always the most recently monitored task.
 *
 *		If a task doesn't appear on the list, or if it appears on the
 *		list but its name has changed, then we send a message to the
 *		main SnoopDos task asking it to check the pattern. We also
 *		add the result of this check to our cache for future calls.
 *
 *		Sending a message to another task from within a patch is tricky;
 *		more specifically, receiving the acknowledgement back from the
 *		main task is tricky. This is because we don't know what signal
 *		bits the caller might be using, and so we can't just grab one.
 *		We could use AllocSignal(), but then what if the caller has no
 *		free signal bits?
 *
 *		Our solution (not necessarily the best) is to use the reserved
 *		exec signal bit SIGB_SINGLE. This is used for semaphore operations,
 *		among others, and is only ever used while waiting for a single
 *		event to happen. Thus, when our code is reached, we know this
 *		bit is not in use and thus we can use it ourselves.
 *
 *		Returns true if no pattern is set, or if the pattern matches
 *		the current task name. Returns false otherwise.
 *
 */
int CheckPattern(char *taskname, int namelen)
{
	PatternCache *pc;
	Task *thistask = SysBase->ThisTask;
	PatternMsg pmsg;

	if (!PatternEnabled)
		return (TRUE);

	if (!PatternWildcard)
		return (stricmp(taskname, PatternString) == 0);

	namelen = MIN(namelen, PC_NAMELEN-1);

	/*
	 *		Got a pattern, so search our cache list
	 */
	ObtainSemaphore(&PatternCacheSem);
	FORLIST(&PatternList, pc)
		if (pc->task == thistask)
			break;
	
	/*
	 *		Okay, now pc is either the matching task, or else a pointer
	 *		to the end of our list.
	 */
	if (NextNode(pc) && pc->task == thistask) {
		/*
		 *		We broke out of the loop since we matched the task.
		 *		Now check if the name matches too.
		 */
		if (strncmp(pc->name, taskname, namelen) == 0) {
			/*
			 *		We're the same. Move this node to the start of
			 *		the list, and return the appropriate state.
			 */
			Remove((Node *)pc);
			AddHead(&PatternList, (Node *)pc);
			ReleaseSemaphore(&PatternCacheSem);
			return (pc->match);
		}
		/*
		 *		The name is different but the Task ID is the same --
		 *		most likely, someone ran a new CLI command. Thus, use
		 *		this cache entry for our new task name and fall through.
		 */
	} else {
		/*
		 *		We didn't match a task so grab one from the end of the
		 *		list and use that instead.
		 */
		pc		 = TailNode(&PatternList);
		pc->task = thistask;
	}

	/*
	 *		Move the node to the start of the list to stop it being flushed
	 *		any time soon, and update the name to match our own name.
	 */
	Remove((Node *)pc);
	AddHead(&PatternList, (Node *)pc);
	strncpy(pc->name, taskname, namelen);
	pc->name[namelen] = 0;
	ReleaseSemaphore(&PatternCacheSem);

	/*
	 *		Now send a message to the main task asking it to perform a name
	 *		match for us. We need to be careful to make sure our pmsg won't
	 *		be confused with a DOS packet by our PutMsg() patch -- we do
	 *		this by NULLing out the name field.
	 */
	pmsg.type                = PATTERN_MSG;
	pmsg.name				 = taskname;
	pmsg.sigmask			 = SIGF_SINGLE;
	pmsg.task				 = thistask;
	pmsg.msg.mn_Node.ln_Name = NULL;

	SetSignal(0, SIGF_SINGLE);		/* Ensure signal is clear first		*/
	PutMsg(&BackgroundPort, &pmsg);	/* Send request to background task	*/
	Wait(SIGF_SINGLE);				/* Wait for acknowledgement			*/
	pc->match = pmsg.match;			/* And save result in cache			*/

	return (pmsg.match);
}

/*
 *		GetVolName(lock, buf, maxlen)
 *
 *		Copies the volume name associated with lock into the buffer,
 *		with terminating ':'. If lock is NULL, the volume address is
 *		taken directly from volume.
 *
 *		If UseDevNames is true, the device list is searched looking
 *		for the device node associated with the volume node (i.e. two
 *		nodes sharing the same task address).
 *
 *		WARNING: This function must not be called from within a DOS
 *				 device handler due to potential deadlock errors!
 *
 */
void GetVolName(BPTR lock, char *buf, int maxlen)
{
	struct DeviceList *vol;
	struct DosList *dl;
	int gotdev = 0;

	if (lock == NULL) {
		NameFromLock(lock, buf, maxlen);
		return;
	}
	vol = BTOC(((struct FileLock *)BTOC(lock))->fl_Volume);

	if (UseDevNames == 0 || vol->dl_Task == NULL) {
		/*
		 *		Use volume name, especially if the volume isn't currently
		 *		mounted!
		 */
		UBYTE *volname = BTOC(vol->dl_Name); 
		int len = MIN(maxlen-2, *volname);

		memcpy(buf, volname+1, len);
		buf[len++] = ':';
		buf[len] = '\0';
		return;
	}

	/*
	 *		The user wants the device name. The only way to obtain this
	 *		is to search the device list looking for the device node with
	 *		the same task address as this volume.
	 */
	dl = LockDosList(LDF_DEVICES | LDF_READ);
	while (dl = NextDosEntry(dl, LDF_DEVICES)) {
		if (dl->dol_Task == vol->dl_Task) {
			/*
			 *		Found our task, so now copy device name
			 */
			UBYTE *devname = BTOC(dl->dol_Name);
			int len = MIN(maxlen-2, *devname);

			memcpy(buf, devname+1, len);
			buf[len++] = ':';
			buf[len] = '\0';
			gotdev = 1;
			break;
		}
	}
	UnLockDosList(LDF_DEVICES | LDF_READ);
	if (!gotdev)
		strcpy(buf, "???:");
}

/*
 *		MyNameFromLock(lock, filename, buf, maxlen)
 *
 *		This is a custom version of the DOS function NameFromLock()
 *		which expands a disk lock into a full path.
 *
 *		Our version adds the following features. The device name will be
 *		given as either the physical device (like DH0:) if UseDevNames
 *		is true, or the volume name (like System3.0:) if UseDevNames is
 *		false.
 *
 *		If filename is non-NULL, then it will be appended to the lock
 *		path. If filename contains path info, then this will be taken
 *		into account when generating the lock, so that an absolute path
 *		in filename will not have any effect, and a relative filename
 *		(like //directory) will cause the original lock to be ParentDir()'d
 *		twice before being resolved.
 *
 *		This function can't fail. In the event of an error (string too
 *		long, or something like that), the buffer will be filled accordingly.
 *		It is assumed that the buffer will always be big enough to hold short
 *		error messages or a volume name.
 *
 *		Returns a pointer to the path (which will not necessarily be at
 *		the start of the buffer, but is guaranteed null-terminated.)
 *		Note that it's even possible that the pointer returned will be
 *		to the original filename if no path expansion was required.
 *
 *		New: We now preserve the IoErr() that was present on entry, since
 *		it may have been set by the calling function if OnlyShowFails is
 *		true. Otherwise, IoErr() will be screwed up by the operations we
 *		do here (e.g. SAS/C deleting a non-existent file when OnlyShowFails
 *		is true).
 *
 *		WARNING: This function must not be called from within a DOS
 *				 device handler due to potential deadlock errors!
 */		
char *MyNameFromLock(BPTR lock, char *filename, char *buf, int maxlen)
{
	Process *myproc = (Process *)SysBase->ThisTask;
	int pos = maxlen - 1;
	D_S(fib, struct FileInfoBlock);
	LONG savedioerr = IoErr();
	BPTR curlock;
	BPTR newlock;
	void *savewinptr;
	char *p;
	int len;
	int skipfirstslash = 0;	/* If true, skip first slash when building name */
	int err = 0;

	/*
	 *		Check for special magic filehandle
	 */
	if (filename && *filename) {
		if (strcmp(filename, "*") == 0)
			return (filename);

		/*
		 *		First determine if we have any work to do.
		 */
		if (*filename == ':') {
			/*
			 *		Got a reference relative to the root directory. Simply
			 *		grab the volume (or device) name from the lock and go
			 *		with that.
			 */
			int len;

			GetVolName(lock, buf, maxlen);
			len = strlen(buf);
			strncat(buf+len, filename+1, maxlen-len);
			buf[maxlen-1] = '\0';
			SetIoErr(savedioerr);
			return (buf);
		}
		for (p = filename; *p; p++) {
			if (*p == ':')			/* If absolute path name, leave it alone */
				return (filename);
		}
	} else {
		/*
		 *		Filename is null, so indicate we want to skip the first
		 *		slash when building the directory path
		 */
		skipfirstslash = 1;
	}

	savewinptr = myproc->pr_WindowPtr;
	myproc->pr_WindowPtr = (APTR)-1;		/* Disable error requesters	  */

	newlock = DupLock(lock);
	if (lock && !newlock) {
		GetVolName(lock, buf, 20);
		if (filename) {
			strcat(buf, ".../");
			strcat(buf, filename);
		}
		myproc->pr_WindowPtr = savewinptr;	/* Re-enable error requesters */
		SetIoErr(savedioerr);
		return (buf);
	}
	buf[pos] = '\0';
	curlock = newlock;
	if (filename) {
		while (newlock && *filename == '/') {
			/*
			 *		Handle leading /'s by moving back a directory level
			 *		but nothing else
			 */
			newlock = ParentDir(curlock);
			if (newlock) {
				UnLock(curlock);
				curlock = newlock;
				filename++;
			}
		}
		len = strlen(filename);
		if (len > (pos-2)) {
			memcpy(buf+2, filename+len-pos, pos-2);
			buf[0] = buf[1] = '.';
			pos = 0;
			UnLock(curlock);
		} else {
			pos -= len;
			memcpy(buf+pos, filename, len);
		}
	}

	/*
	 *		At this point, we have buf containing the filename (minus any
	 *		leading /'s), starting at the index given by pos. If filename
	 *		was NULL or empty, then pos indexes to a \0 terminator.
	 *
	 *		Next, we want to pre-pend directory names to the front of
	 *		the filename (assuming there _is_ a filename) until we get
	 *		to the device root.
	 */
	newlock = curlock;
	while (newlock) {

		if (!Examine(curlock, fib)) {
			err++;
			break;
		}
		len = strlen(fib->fib_FileName);
		if (len > (pos-3)) {
			/*
			 *		Not enough room: prefix dots at start to indicate
			 *		an overrun. We use pos-3 since we need one char
			 *		for a possible slash and two more to accomodate a
			 *		leading ".."
			 */
			memcpy(buf+2, fib->fib_FileName+len-pos+3, pos-2);
			buf[0] = buf[1] = '.';
			buf[pos-1] = '/';
			pos = 0;
			break;
		}
		newlock = ParentDir(curlock);
		if (newlock) {
			UnLock(curlock);
			curlock = newlock;
			pos -= len + 1;
			memcpy(buf + pos, fib->fib_FileName, len);
			if (skipfirstslash) {
				skipfirstslash = 0;
				buf[pos+len] = '\0';
			} else
				buf[pos+len] = '/';
		}
	}
	/*
	 *		Now we've built the path components; add the volume node
	 *		to the beginning if possible.
	 */
	if (err) {
		/*
		 *		If an error occurred, the volume is probably not mounted,
		 *		so we include a ".../" component in the path to show
		 *		we couldn't get all the info
		 */
		pos -= 4;
		memcpy(buf + pos, ".../", 4);
	}
	if (pos > 0) {
		char volname[20];
		int len;
		char *p;

		GetVolName(curlock, volname, 20);
		len = strlen(volname);
		if (len > pos) {
			p = volname + len - pos;
			len = pos;
		} else {
			p = volname;
		}
		pos -= len;
		memcpy(buf + pos, p, len);
	}
	if (curlock)
		UnLock(curlock);

	myproc->pr_WindowPtr = savewinptr;		/* Re-enable error requesters */
	SetIoErr(savedioerr);
	return (buf+pos);
}

/*
 *		AsyncNameFromLock(volname, lock, filename, dest, maxlen)
 *
 *		This is identical to the MyNameFromLock() function except that
 *		it gets the background SnoopDos process to do the path expansion
 *		instead of doinng it from within the caller's process. This is
 *		necessary if we need to expand paths and we are not sure if the
 *		caller's process mesage port is free.
 *
 *		Volname is the volume on which the lock resides. If the lock itself
 *		is NULL, then the root of the volume will be assumed and this
 *		string will be copied into the buffer as the volume name.
 *
 *		WARNING: This function must not be called from within a DOS
 *				 device handler due to potential deadlock errors!
 */
char *AsyncNameFromLock(char *volname, BPTR lock, char *filename,
						char *buf, int maxlen)
{
	NameFromLockMsg lmsg;

	if (lock == NULL) {
		/*
		 *		Do a quick scan on the filename itself to see
		 *		if it contains a colon prefixed by some text;
		 *		if it does, then we already have a full path.
		 *		If it's just a colon, prefix it with the
		 *		volume name. If it has no colon, prefix it
		 *		with the volume name and a colon.
		 */
		char *p;

		strcpy(buf, volname);
		if (filename) {
			if (*filename == ':') {
				strcat(buf, filename);
			} else {
				for (p = filename; *p && *p != ':'; p++)
					;
				if (*p)
					strcpy(buf, filename);
				else
					mysprintf(buf, "%s:%s", volname, filename);
			}
		}
		return (buf);
	}
	lmsg.type		= PATHEXPAND_MSG;
	lmsg.lock		= lock;
	lmsg.filename	= filename;
	lmsg.buf		= buf;
	lmsg.maxlen		= maxlen;
	lmsg.sigmask	= SIGF_SINGLE;
	lmsg.task		= SysBase->ThisTask;
	lmsg.msg.mn_Node.ln_Name = NULL;

	SetSignal(0, SIGF_SINGLE);		/* Ensure signal is clear first		*/
	PutMsg(&BackgroundPort, &lmsg);	/* Send request to background task	*/
	Wait(SIGF_SINGLE);				/* Wait for acknowledgement			*/
	return (lmsg.newbuf);
}

#if STACK_CHECK
/*
 *		GetFreeStack()
 *
 *		Returns the amount of free space available on the calling task's
 *		stack (thanks to Ralph Babel for this).
 */
ULONG GetFreeStack(void)
{
	Process *pr = (Process *)SysBase->ThisTask;
	char *upper;
	char *lower;
	ULONG total;
	ULONG avail;

	if (pr->pr_Task.tc_Node.ln_Type == NT_PROCESS && pr->pr_CLI != NULL) {
		upper = (char *)pr->pr_ReturnAddr + sizeof(ULONG);
		total = *(ULONG *)pr->pr_ReturnAddr;
		lower = upper - total;
	} else {
		upper = (char *)pr->pr_Task.tc_SPUpper;
		lower = (char *)pr->pr_Task.tc_SPLower;
		total = upper - lower;
	}
	avail = (char *)getreg(REG_A7) - lower;
	return (avail);
}
#endif

/*
 *		CreateEvent(calladdr, seqnum, action, filename,
 *					options, expandname [, lock] )
 *
 *		Allocates a new event for the current task and initialises
 *		almost all the fields in it accordingly.
 *
 *		seqnum is initialised to the sequence number of the new event. This
 *		is used to allow later detection of events which have scrolled off
 *		the top of the buffer.
 *
 *		filename and options are text strings, and actionid is a message ID.
 *		The storage for these will be allocated in the event buffer.
 *
 *		If expandname is EXPAND_NAME, then 'filename' is assumed to represent
 *		a real disk filename, and it will be expanded to a full path if that
 *		option is set. If expandname is NO_EXPAND, then the name is passed
 *		unchanged.
 *
 *		If expandname is neither EXPAND_NAME or NO_EXPAND, then it is
 *		interpreted as a volume name, and one additional parameter
 *		following it will be expected -- this additional parameter will
 *		be a lock on that volume. The filename will then be expanded
 *		to a full pathname on that volume. If the lock is null, then it
 *		is assumed to represent the root of the volume. The filename
 *		expansion is done by the background process, making it safe to
 *		call from within the PutMsg() patch which monitors raw packets.
 *		
 *		If you pass in a NULL value for any of the three pointers, it will
 *		be made to point to a blank string.
 *
 *		If for some reason the event could not be created, returns NULL,
 *		else returns a pointer to the initialised event.
 *
 *		Possible reasons for failure include:
 *
 *			- Calling task is main SnoopDos task (!)
 *			- Calling task is not included in the match pattern
 *			- Calling task was called by a ROM function
 *			- Disk error occurred expanding filename
 *			- Out of memory allocating new event
 */
struct Event *CreateEvent(ULONG calladdr, LONG *seqnum, ULONG actionid,
						  char *filename, char *options, int expandname, ...)
{
	Task *thistask = SysBase->ThisTask;
	struct CommandLineInterface *cli;
	Event *ev;
	char *procname;
	char *strptr;
	char *action;
	char namebuf[MAX_SHORT_LEN+1];
	char pathbuf[MAX_STR_LEN+1];
	int plen = 0;
	int flen = 0;
	int olen = 0;
	int alen = 0;
	int slen = 0;
	int clinum;

	/*
	 *		We ignore all calls from the main SnoopDOS process and
	 *		background process, and also any nested calls made while
	 *		we have the semaphore locked (which might otherwise lead
	 *		to nasty trashing of the list!)
	 *
	 *		Also, any calls from input.device are ignored since these
	 *		typically call us with layers locked, and the slightest
	 *		delay could be fatal.
	 */
	if (thistask == SnoopTask || thistask == (Task *)BackgroundProc
							  || thistask == BufSem.ss_Owner
							  || thistask == RamLibTask
							  || thistask == InputTask)
		return (NULL);

	/*
	 *		We also ignore any calls made from ROM addresses (if this
	 *		option is enabled), since this cuts down the amount of
	 *		superfluos output considerably.
	 *
	 *		We specifically DO allow calls from within ramlib, since
	 *		it's very easy to disable ramlib directly if need be (by
	 *		putting ~ramlib in the match name) and ramlib does
	 *		many useful operations that are convenient to monitor.
	 */
	if (thistask != RealRamLibTask && !MonROMCalls &&
							  calladdr >= RomStart && calladdr <= RomEnd)
		return (NULL);

	/*
	 *		Work out the correct name for our task. This will either be
	 *		the task name, the process name, or the current CLI command
	 *		(if the process is a CLI, _and_ is currently running a command.)
	 *
	 *		In addition, for CLIs, we set clinum to the CLI number. For
	 *		tasks and non-CLI processes, clinum becomes zero.
	 */
	procname = thistask->tc_Node.ln_Name;
	plen	 = strlen(procname);
	cli		 = BTOC(((Process *)thistask)->pr_CLI);
	clinum   = 0;

	if (thistask->tc_Node.ln_Type == NT_PROCESS && cli) {
		/*
		 *		If we have a CLI command, we have to convert the name
		 *		from a BSTR into a C string
		 */
		clinum = ((Process *)thistask)->pr_TaskNum;
		if (cli->cli_Module) {
			UBYTE *cliname = BTOC(cli->cli_CommandName);
			plen = *cliname;

			if (plen) {
				if (plen > MAX_SHORT_LEN)
					plen = MAX_SHORT_LEN;
				memcpy(namebuf, cliname+1, plen);
				namebuf[plen] = 0;
			} else {
				strcpy(namebuf, "<Unknown>");
				plen = strlen(namebuf);
			}
			procname = namebuf;
		}
	}

#if 0
	if (stricmp(procname, "ramlib") == 0) {
		KPrintF("Ramlib: Stacksize is %ld\n", GetFreeStack());
	}
#endif

	if (!CheckPattern(procname, plen))		/* Perform pattern check */
		return (FALSE);

	/*
	 *		Now deal with our three strings
	 */
	if (ShowPaths && filename != NULL) {
		if (expandname == EXPAND_NAME) {
			/*
			 *		Expand filename to full path
			 */
			filename = MyNameFromLock(((Process *)thistask)->pr_CurrentDir,
									  filename, pathbuf, MAX_STR_LEN);
		} else if (expandname != NO_EXPAND) {
			/*
			 *		Expand filename to full path using background process
			 *		and the lock specified in expandname.
			 */
			BPTR lock = *(ULONG *)(&expandname+1);

			filename = AsyncNameFromLock((char *)expandname, lock, filename, 
									     pathbuf, MAX_STR_LEN);
		}
	}
	plen++;
	if (ShowCLINum && clinum)
		plen += 8;						/* Leave room for CLI display */

	action = MSG(actionid);
	alen   = strlen(action) + 1;

	if (filename)	flen = strlen(filename)	+ 1;
	if (options)	olen = strlen(options)	+ 1;

	if (SegTrackerActive)
		slen = MAX_SEGTRACKER_LEN;

	/*
	 *		If the calling task has locked the layers of the SnoopDos
	 *		screen, then we ignore all calls by that task. This prevents
	 *		us deadlocking when we wait for the semaphore (while meanwhile,
	 *		the SnoopDos mainline code could own the semaphore, and be
	 *		waiting for the layers to be unlocked by our caller so it can
	 *		render some output).
	 *
	 *		We Forbid() to make sure the window doesn't get closed from
	 *		under our feet while we're checking it. We have to try and
	 *		allocate the event while still Forbid'd since otherwise, there
	 *		is a small window where the main window could open, lock the
	 *		layers, and lock the semaphore, in between our check. It's
	 *		unlikely, but not _that_ unlikely (especially since the main
	 *		window always does an immediate redraw when it opens)
	 */		
	Forbid();
	if (MainWindow &&
			(MainWindow->RPort->Layer->Lock.ss_Owner   == thistask ||
			 MainWindow->WScreen->LayerInfo.Lock.ss_Owner == thistask)) {
		Permit();
		return (NULL);
	}
	ev = GetNewEvent(plen + alen + flen + olen + slen);
	Permit();

	if (!ev)
		return (NULL);
	
	ev->action = ev->filename = ev->options = ev->result = "";

	strptr = (char *)(ev+1);

	if (ShowCLINum && clinum)
		mysprintf(strptr, "[%ld] %s", clinum, procname);
	else
		strcpy(strptr, procname);
	ev->procname  = strptr;
	strptr       += plen;

//	KPrintF("New event: %ld, %s, %s\n", ev->seqnum, procname, action); /* DB */
	if (alen) {
		strcpy(strptr, action);
		ev->action   = strptr;
		strptr	  	+= alen;
	}
	if (flen) {
		strcpy(strptr, filename);
		ev->filename  = strptr;
		strptr   	 += flen;
	}
	if (olen) {
		strcpy(strptr, options);
		ev->options   = strptr;
		strptr       += olen;
	}
	if (slen) {
		ev->segname	  = strptr;
		strptr       += slen;
	} else {
		ev->segname   = NULL;	/* Say no segtracker storage allocated */
	}
	// DateStamp(&ev->datestamp);
	ev->calladdr	= calladdr;
	ev->processid	= (ULONG)thistask;
	// ev->processid	= GetFreeStack();

	if (seqnum)
		*seqnum = ev->seqnum;
	return (ev);
}

/*
 *		CheckTaskRecursion()
 *
 *		Checks if the current task is already listed as being on the
 *		recursion list. If so, returns FALSE, indicating that the
 *		caller should immediately pass control back to the original
 *		function and not try and do anything else.
 *
 *		Otherwise, records the task's address in a recursion list
 *		and returns a handle. This handle should be passed back to
 *		EndTaskRecursion() when the function is complete, which will
 *		cause it to be removed from the list.
 *
 *		OpenLibrary() needs to surround its call to CreateEvent() with
 *		CheckTaskRecursion()/EndTaskRecursion. Also, GetMsg()/PutMsg() need 
 *		this to check if any of the DOS packets it monitors have been
 *		generated by AmigaDOS functions we monitor.
 */
ULONG CheckTaskRecursion(void)
{
	Task *taskid = SysBase->ThisTask;
	ULONG taskhandle;
	int i;

	for (i = 1; i < NUM_CACHED_TASKS; i++)
		if (CachedTask[i] == taskid)
			return (0);
	
	ObtainSemaphore(&TaskCacheSem);
	taskhandle = TaskCacheIndex++;
	if (TaskCacheIndex >= NUM_CACHED_TASKS)
		TaskCacheIndex = 1;
	CachedTask[taskhandle] = taskid;
	ReleaseSemaphore(&TaskCacheSem);

	return (taskhandle);
}

/*
 *		EndTaskRecursion(taskhandle)
 *
 *		Called to remove a task from the recursion list.
 *		See CheckTaskRecursion() above.
 */
void EndTaskRecursion(ULONG taskhandle)
{
	CachedTask[taskhandle] = 0;
}

#if 0
/*
 *		TestPatch()
 *
 *		Patches a single function to try it out
 */
void TestPatch(void)
{
	extern List			EventList;			/* List of events		*/
	static EventFormat evformat[50];
	static char outstr[500];
	char *formatstr = "%d %t %c %n %s %h %a %40f %o %r";
	int formatlen;

	OnlyShowFails    = 0;
	SegTrackerActive = 1;
	formatlen = ParseFormatString(formatstr, evformat, 50);

	if (!InitPatches()) {
		printf("Patch initialisation failed!\n");
		return;
	}

	PatchList[GID_OPENFILE].enabled = 1;
	UpdatePatches();
	printf("Patch now enabled!\n\n");

	FormatEvent(evformat, NULL, outstr, 0, formatlen-1);
	printf("%s\n", outstr);
	printf("%s\n", UnderlineTitles(evformat, outstr, '-'));

	for (;;) {
		ULONG mask;

		mask = Wait(SIGBREAKF_CTRL_C | NewEventMask);
		if (mask & SIGBREAKF_CTRL_C)
			break;

		if (mask & NewEventMask) {
			Event *ev;

			SetSignal(0, NewEventMask);	/* Clear signal for next time */
			FORLIST(&EventList, ev) {
				int stat = ev->status;

				if (stat == ES_UPDATING || stat == ES_READY) {
					FormatEvent(evformat, ev, outstr, 0, formatlen-1);
					printf("%s\n", outstr);
					if (stat == ES_READY)
						ev->status = ES_ACCEPTED;
				}
			}
		}
	}
	PatchList[GID_OPENFILE].enabled = 0;
	UpdatePatches();
	printf("Patch disabled\n");
	CleanupPatches();
}
#endif

/*
 *		HandlePaused(event, seqnum)
 *
 *		Should be called when pausing is enabled -- it sets the
 *		event Result code to "...." to indicate the event is about
 *		to be executed, then waits for the pausing to be turned off.
 *		If you pass in NULL as the event code, then this isn't done.
 *
 *		seqnum is the actual event number remembered by the caller,
 *		not (necessarily) the event number stored in the event. This
 *		is so that the code can check if the event has rolled off the
 *		top of the buffer or not before modifying it.
 *
 *		There are some circumstances where it is dangerous for
 *		us to pause because we are being called by a system task
 *		that is needed by the main SnoopDos program.
 *
 *		We work around this as follows. Firstly, if the caller happens
 *		to own the semaphore associated with the layer info for the
 *		window or screen containing the SnoopDos window, then we
 *		don't try and pause it since otherwise we might deadlock
 *		(the user wouldn't be able to unpause SnoopDos because that
 *		requires clicking on a button.)
 *
 *		Secondly, whenever SnoopDos itself does something that might
 *		stop it processing the main messages (e.g. show a requester,
 *		open a file, check for ASL, etc.) then pausing is temporarily
 *		turned off since we have no way of knowing whether a third
 *		party patch might be spinning off a background task to do it
 *		and the background task might try and wait on this. Bummer, eh?
 */
void HandlePaused(Event *ev, LONG seqnum)
{
	if (Paused) {
		/*
		 *		Need to forbid when checking layers since otherwise the
		 *		main window might close while we're reading its pointers.
		 */
		int gotlock = 0;
		char *oldresmsg;

		Forbid();
		if (MainWindow) {
			struct Layer *layer;
			struct Layer_Info *li;
			Task *thistask = SysBase->ThisTask;

			layer = MainWindow->RPort->Layer;
			li    = &MainWindow->WScreen->LayerInfo;

			gotlock = (li->Lock.ss_Owner    == thistask) ||
					  (layer->Lock.ss_Owner == thistask);
		}
		Permit();
		if (gotlock)
			return;

		/*
		 *		Okay, safe to proceed with pausing. We simply set the
		 *		result message to indicate we're paused, signal
		 *		the main task, wait for the pausing to finish, restore
		 *		the result message, and return.
		 */
		if (ev) {
			ObtainSemaphore(&BufSem);
			if (seqnum >= RealFirstSeq) {
				oldresmsg  = ev->result;
				ev->result = MSG(MSG_RES_PAUSE);
			}
			ReleaseSemaphore(&BufSem);
		}
		Signal(SnoopTask, NewEventMask);	/* Make sure main task updates */
		ObtainSemaphore(&PauseSem);
		ReleaseSemaphore(&PauseSem);
		if (ev) {
			ObtainSemaphore(&BufSem);
			if (seqnum >= RealFirstSeq)
				ev->result = oldresmsg;
			ReleaseSemaphore(&BufSem);
		}
	}
}

#define CheckForPause(ev,seqnum)	if (Paused)	{ HandlePaused(ev, seqnum); }

/*****************************************************************************
 *
 *		Now here are all the patches for the 25 or so functions that we
 *		patch into. Most of the patches follow a similar template, and
 *		share similar lower-level functions, but are different enough to
 *		prevent us from using the same patch for several functions.
 *
 *		We always signal both before and after a particular call, so that
 *		in the event of a call causing a crash, it will still show up in
 *		the SnoopDos window.
 *
 *		The OnlyShowFails flag is used to say whether we want to monitor
 *		all calls to a function, or only those which return a failure code.
 *		If the latter, we need to call the function first before deciding
 *		whether or not to create an event to record it -- in this case, the
 *		result code remains cached for the remainder of the function. The
 *		rest of the time, we create the event and signal the main task,
 *		then call the function to get the result, and finally update the
 *		event with the result and signal the main task again.
 *
 *		Each patch that calls a function and then updates the buffer on its
 *		return must ensure that the buffer entry is has a pointer to is still
 *		valid before it updates it. It does this by remembering the sequence
 *		number of the buffer entry, and then checking that the sequence
 *		number of the first buffer entry is still <= that (i.e. the entry
 *		is still somewhere in the buffer). This is vital to ensure that
 *		innocent buffer memory isn't trashed by old events.
 *
 *****************************************************************************/

/*
 *		New_FindPort(name)
 *
 *		Searches the system port list looking for a port name
 */
ULONG ASM New_FindPort(reg_a1 char *name, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_FindPort origfunc = (FP_FindPort)PatchList[GID_FINDPORT].origfunc;
	int onlyfails = OnlyShowFails;
	ULONG result;
	Event *ev;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_FINDPORT,
					 name, NULL, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_FindResident(name)
 *
 *		Searches the system resident list looking for a resident module
 */
ULONG ASM New_FindResident(reg_a1 char *name, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_FindResident origfunc = (FP_FindResident)
								PatchList[GID_FINDRESIDENT].origfunc;
	int onlyfails = OnlyShowFails;
	ULONG result;
	Event *ev;
	LONG  seqnum;

	if (SysBase->ThisTask == RealRamLibTask)
		JumpOrigFunc(0);

	if (onlyfails) {
		result = origfunc(name, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_FINDRESIDENT,
					 name, NULL, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_FindSemaphore(name)
 *
 *		Searches the system semaphore list looking for the named semaphore
 */
ULONG ASM New_FindSemaphore(reg_a1 char *name, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_FindSemaphore origfunc = (FP_FindSemaphore)
							 	 PatchList[GID_FINDSEMAPHORE].origfunc;
	int onlyfails = OnlyShowFails;
	ULONG result;
	Event *ev;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_FINDSEM,
					 name, NULL, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}
	
/*
 *		New_FindTask(name)
 *
 *		Searches the system task list looking for the named task.
 *		We ignore calls to FindTask(0) since this is just
 *		looking for a pointer to the current task.
 */
ULONG ASM New_FindTask(reg_a1 char *name, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_FindTask origfunc = (FP_FindTask)PatchList[GID_FINDTASK].origfunc;
	int onlyfails = OnlyShowFails;
	ULONG result;
	Event *ev;
	LONG  seqnum;

	if (name == NULL)
		return (origfunc(name, libbase));

	if (onlyfails) {
		result = origfunc(name, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_FINDTASK,
					 name, NULL, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_LockPubScreen(name)
 *
 *		Obtains a lock on the named public screen. If the name is NULL,
 *		then the caller is looking for a Lock() on the default screen,
 *		and we ignore it (otherwise we'd get loads of output).
 */
ULONG ASM New_LockPubScreen(reg_a0 char *name, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_LockPubScreen origfunc = (FP_LockPubScreen)
								PatchList[GID_LOCKSCREEN].origfunc;
	int onlyfails = OnlyShowFails;
	ULONG result;
	Event *ev;
	LONG  seqnum;

	if (!name)
		JumpOrigFunc(0);

	if (onlyfails) {
		result = origfunc(name, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_LOCKSCREEN,
					 name, NULL, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		Signal(SnoopTask, NewEventMask);
		CheckForPause(ev, seqnum);
		result = origfunc(name, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_OpenDevice(name, unit, ioreq, flags)
 *
 *		Opens a new device. We need to be careful with the error return
 *		for this function, since success is indicated by a return of 0!
 *
 *		N.b. Kickstart 3.1 has a space-saving hack which allows a NULL
 *		device name to be interpreted as a magic cookie indicating a
 *		particular ROM devices. We ignore such calls (especially from CON,
 *		which does it a lot!) but flag other occurrances of NULL device
 *		names as an error.
 */
ULONG ASM New_OpenDevice(reg_a0 char *name, reg_d0 long unit,
					     reg_a1 struct IORequest *ioreq,
						 reg_d1 long flags, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_OpenDevice origfunc = (FP_OpenDevice)PatchList[GID_OPENDEVICE].origfunc;
	int onlyfails = OnlyShowFails;
	ULONG result;
	Event *ev;
	char unitstr[20];
	char *devname = name;
	LONG  seqnum;

/*
 *		Release Semaphore replacement code for debugging patch
 */
#if MONITOR_SEMAPHORE
	Semaphore *sem = (Semaphore *)name;

	if (LoadTask == SysBase->ThisTask)
		KPrintF("ReleaseSemaphore in LoadSeg: retaddr = %08lx\n", CallAddr);
	return origfunc(name, unit, ioreq, flags, libbase);
#endif

	if (onlyfails) {
		result = origfunc(name, unit, ioreq, flags, libbase);
		if (!result)
			return (result);
	}
	/*
	 *		Now a workaround for a bug in CON that makes it call
	 *		OpenDevice("timer.device" | NULL) very frequently.
	 *		(The NULL is a space-saving shorthand which the 3.1 Kickstart
	 *		interprets as a magic cookie for "timer.device" to save some
	 *		ROM space).
	 */
	if ((CallAddr >= RomStart && CallAddr <= RomEnd) ||
					strcmp(SysBase->ThisTask->tc_Node.ln_Name, "CON") == 0) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(unit);
	}
	if (!devname)
		devname = MSG(MSG_NULLSTR);

	mysprintf(unitstr, MSG(MSG_OPT_DEVUNIT), unit);
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_OPENDEV,
					 devname, unitstr, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(unit);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, unit, ioreq, flags, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (!result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_OpenFont(textattr)
 *
 *		Opens a new font that's currently in memory. OpenDiskFont will
 *		call this function first before checking the disk, so patching
 *		this function catches all accesses.
 */
ULONG ASM New_OpenFont(reg_a0 struct TextAttr *textattr, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_OpenFont origfunc = (FP_OpenFont)PatchList[GID_OPENFONT].origfunc;
	int onlyfails = OnlyShowFails;
	ULONG result;
	Event *ev;
	char *name;
	char sizestr[20];
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(textattr, libbase);
		if (result)
			return (result);
	}
	if (textattr) {
		mysprintf(sizestr, MSG(MSG_OPT_FONTSIZE), textattr->ta_YSize);
		name = textattr->ta_Name;
	} else {
		*sizestr = '\0';
		name = MSG(MSG_NULLSTR);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_OPENFONT,
					 name, sizestr, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(textattr, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_OpenLibrary(name, version)
 *
 *		Opens the named library with the specified version number.
 *
 *		We have a nasty potential for deadlock here -- CreateEvent()
 *		which we call, in turns calls DateStamp(), and DateStamp()
 *		itself calls OpenLibrary(....). So, we need to protect
 *		ourselves against a recursive call.
 *
 *		We do this by ignoring any call to open dos.library made
 *		by a call originating in ROM.
 */
ULONG ASM New_OpenLibrary(reg_a1 char *name, reg_d0 long version,
						  reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_OpenLibrary origfunc = (FP_OpenLibrary)
							   PatchList[GID_OPENLIBRARY].origfunc;
	int onlyfails = OnlyShowFails;
	ULONG result;
	Event *ev;
	char verstr[20];
	ULONG taskhandle;
	LONG  seqnum;

	/*
	 *		Check for re-entrant calling (usually caused by the
	 *		DateStamp call in the GetNewEvent() code).
	 *
	 *		In theory, we should only need to check if the semaphore
	 *		is owned by the current task. In practise, we need
	 *		the additional checks to be really sure (otherwise
	 *		we seem to get unexplained crashes very frequently --
	 *		very mysterious and worrying).
	 *
	 *		We also ignore calls to open dos.library -- this is partly
	 *		because it gets called by DateStamp() inside dos.library
	 *		itself (so things that call DateStamp once a second, like
	 *		Enforcer(), will generate loads of calls to it) and partly
	 *		to avoid problems with our own CreateEvent() code calling it.
	 */
	if (BufSem.ss_Owner == SysBase->ThisTask ||
		strcmp(name, "dos.library") == 0)
	{
		JumpOrigFunc(version);
	}

	if (onlyfails) {
		result = origfunc(name, version, libbase);
		if (result)
			return (result);
	}
	taskhandle = CheckTaskRecursion();
	if (!taskhandle) {
		if (!onlyfails)
			result = origfunc(name, version, libbase);
		return (result);
	}
	mysprintf(verstr, MSG(MSG_OPT_LIBVER), version);
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_OPENLIB,
					 name, verstr, NO_EXPAND);
	EndTaskRecursion(taskhandle);

	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(version);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, version, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_OpenResource(name)
 *
 *		Opens the named resource
 */
ULONG ASM New_OpenResource(reg_a1 char *name, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_OpenResource origfunc = (FP_OpenResource)
							    PatchList[GID_OPENRESOURCE].origfunc;
	int onlyfails = OnlyShowFails;
	ULONG result;
	Event *ev;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_OPENRES,
					 name, NULL, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_FindToolType(array, tooltype)
 *
 *		Searches the tooltype array for a particular tooltype
 */
ULONG ASM New_FindToolType(reg_a0 char **array, reg_a1 char *tooltype,
						   reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_FindToolType origfunc = (FP_FindToolType)
							    PatchList[GID_READTOOLTYPES].origfunc;
	int onlyfails = OnlyShowFails;
	ULONG result;
	Event *ev;
	char *toolname = tooltype;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(array, tooltype, libbase);
		if (result)
			return (result);
	}
	if (toolname == NULL)
		toolname = MSG(MSG_NULLSTR);

	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_FINDTOOL,
					 toolname, NULL, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(array, tooltype, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_MatchToolValue(tooltype, value)
 *
 *		Checks if a specified tooltype contains a given value
 */
ULONG ASM New_MatchToolValue(reg_a0 char *tooltype, reg_a1 char *value,
						     reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_MatchToolValue origfunc = (FP_MatchToolValue)
							      PatchList[GID_READTOOLTYPES2].origfunc;
	int onlyfails = OnlyShowFails;
	ULONG result;
	Event *ev;
	char *toolname = tooltype;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(tooltype, value, libbase);
		if (result)
			return (result);
	}
	if (toolname == NULL)
		toolname = MSG(MSG_NULLSTR);

	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_MATCHTOOL,
					 toolname, value, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(tooltype, value, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_CurrentDir()
 *
 *		Changes current directory to somewhere else
 */
ULONG ASM New_CurrentDir(reg_d1 BPTR lock, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_CurrentDir origfunc = (FP_CurrentDir)PatchList[GID_CHANGEDIR].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	char lockbuf[MAX_LOCK_LEN+1];
	char *lockpath;
	LONG  seqnum;

	if (onlyfails)
		return origfunc(lock, libbase);		/* CurrentDir never fails */

	lockpath = MyNameFromLock(lock, NULL, lockbuf, MAX_LOCK_LEN);
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_CHANGEDIR,
					 lockpath, NULL, NO_EXPAND);
	if (!ev)
		JumpOrigFunc(0);
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	ev->status = ES_UPDATING;
	CheckForPause(ev, seqnum);
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return origfunc(lock, libbase);
}

/*
 *		New_DeleteFile()
 *
 *		Deletes a file from disk
 */
ULONG ASM New_DeleteFile(reg_d1 char *name, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_DeleteFile origfunc = (FP_DeleteFile)PatchList[GID_DELETE].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_DELETE,
					 name, NULL, EXPAND_NAME);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_Execute()
 *
 *		Executes a command from disk. Now superceded by System and RunCommand
 */
ULONG ASM New_Execute(reg_d1 char *cmdline, reg_d2 BPTR fin, reg_d3 BPTR fout,
					  reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_Execute origfunc = (FP_Execute)PatchList[GID_EXECUTE].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	char *optstr;
	LONG  seqnum;

	/*
	 *		For functions that may potentially take a long time to complete,
	 *		we check if they're being called from ROM straight away, so
	 *		that we can exit and not hang around waiting for them if they
	 *		are, even in the OnlyShowFails case.
	 */
	if (!MonROMCalls && CallAddr >= RomStart && CallAddr <= RomEnd)
		JumpOrigFunc(0);

	if (onlyfails) {
		result = origfunc(cmdline, fin, fout, libbase);
		if (result)
			return (result);
	}
	if (fin == NULL)
		optstr = MSG(MSG_OPT_EXECSINGLE);
	else
		optstr = MSG(MSG_OPT_EXECBATCH);

	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_EXECUTE,
					 cmdline, optstr, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(cmdline, fin, fout, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_GetVar()
 *
 *		Inquires about the value of an environment variable
 */
ULONG ASM New_GetVar(reg_d1 char *name, reg_d2 char *buffer,
					 reg_d3 size, reg_d4 flags, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_GetVar origfunc = (FP_GetVar)PatchList[GID_GETVAR].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	char optstr[20];
	ULONG optid;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, buffer, size, flags, libbase);
		if (result != -1)
			return (result);
	}
	/*
	 *		Now determine our flags. We recognise global, local
	 *		or either types of variable. Binary variables are
	 *		flagged with a trailing asterisk.
	 */
	if		(flags & GVF_GLOBAL_ONLY)	optid = MSG_OPT_GLOBAL;
	else if ((flags & 7) == LV_ALIAS)	optid = MSG_OPT_ALIAS;
	else if (flags & GVF_LOCAL_ONLY)	optid = MSG_OPT_LOCAL;
	else								optid = MSG_OPT_ANY;
	strcpy(optstr, MSG(optid));

	if (flags & GVF_BINARY_VAR)			strcat(optstr, "*");

	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_GETVAR,
					 name, optstr, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, buffer, size, flags, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result != -1)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_FindVar()
 *
 *		Inquires about the value of a local environment variable
 */
ULONG ASM New_FindVar(reg_d1 char *name, reg_d2 ULONG type,
					  reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_FindVar origfunc = (FP_FindVar)PatchList[GID_GETVAR2].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	ULONG optid;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, type, libbase);
		if (result)
			return (result);
	}

	if		((type & 7) == LV_VAR)		optid = MSG_OPT_LOCAL;
	else if ((type & 7) == LV_ALIAS)	optid = MSG_OPT_ALIAS;
	else								optid = MSG_OPT_UNKNOWN;

	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_FINDVAR,
					 name, MSG(optid), NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		Signal(SnoopTask, NewEventMask);
		CheckForPause(ev, seqnum);
		result = origfunc(name, type, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_SetVar()
 *
 *		Sets a (possibly new) variable to a particular value
 */
ULONG ASM New_SetVar(reg_d1 char *name, reg_d2 char *buffer,
					 reg_d3 size, reg_d4 flags, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_SetVar origfunc = (FP_SetVar)PatchList[GID_SETVAR].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	char varstr[MAX_STR_LEN+1];
	int vlen;
	ULONG optid;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, buffer, size, flags, libbase);
		if (result)
			return (result);
	}
	/*
	 *		Now determine our flags. We recognise global, local
	 *		or alias variables.
	 */
	if		(flags & GVF_GLOBAL_ONLY)	optid = MSG_OPT_GLOBAL;
	else if ((flags & 7) == LV_VAR)		optid = MSG_OPT_LOCAL;
	else if ((flags & 7) == LV_ALIAS)	optid = MSG_OPT_ALIAS;
	else								optid = MSG_OPT_UNKNOWN;

	/*
	 *		Now create a string that looks like "Variable=Value"
	 *
	 *		We go to some pains to ensure we don't overwrite our
	 *		string length
	 */
	vlen = strlen(name);
	if (vlen > (MAX_STR_LEN-1)) {
		strncpy(varstr, name, MAX_STR_LEN);
		varstr[MAX_STR_LEN] = 0;
	} else {
		strcpy(varstr, name);
		strcat(varstr, "=");
		vlen = 98 - vlen;
		if (size != -1)
			vlen = MIN(vlen, size);

		strncat(varstr, buffer, vlen);
		varstr[MAX_STR_LEN] = 0;
	}

	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_SETVAR,
					 varstr, MSG(optid), NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, buffer, size, flags, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_DeleteVar()
 *
 *		Deletes an environment variable from the environment
 */
ULONG ASM New_DeleteVar(reg_d1 char *name, reg_d2 ULONG flags,
					 	reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_DeleteVar origfunc = (FP_DeleteVar)PatchList[GID_SETVAR2].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	ULONG optid;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, flags, libbase);
		if (result)
			return (result);
	}
	/*
	 *		Now determine our flags. We recognise global, local
	 *		or alias variables.
	 */
	if		(flags & GVF_GLOBAL_ONLY)	optid = MSG_OPT_GLOBAL;
	else if ((flags & 7) == LV_VAR)		optid = MSG_OPT_LOCAL;
	else if ((flags & 7) == LV_ALIAS)	optid = MSG_OPT_ALIAS;
	else								optid = MSG_OPT_UNKNOWN;

	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_KILLVAR,
					 name, MSG(optid), NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, flags, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_LoadSeg()
 *
 *		Tries to load in a module from disk
 */
ULONG ASM New_LoadSeg(reg_d1 char *name, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_LoadSeg origfunc = (FP_LoadSeg)PatchList[GID_LOADSEG].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_LOADSEG,
					 name, NULL, EXPAND_NAME);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		
#if MONITOR_SEMAPHORE
		LoadTask = SysBase->ThisTask;
		result = origfunc(name, libbase);
		LoadTask = 0;
#else
		result = origfunc(name, libbase);
#endif
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_NewLoadSeg()
 *
 *		Tries to load in a module from disk
 */
ULONG ASM New_NewLoadSeg(reg_d1 char *name, reg_d2 TAGPTR tags,
						 reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_NewLoadSeg origfunc = (FP_NewLoadSeg)PatchList[GID_LOADSEG2].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, tags, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_NEWLOADSEG,
					 name, NULL, EXPAND_NAME);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, tags, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_NewLoadSeg()
 *
 *		Tries to load in a module from disk
 */
ULONG ASM New_Lock(reg_d1 char *name, reg_d2 LONG mode, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_Lock origfunc = (FP_Lock)PatchList[GID_LOCKFILE].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	LONG optid;
	char *curname;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, mode, libbase);
		if (result)
			return (result);
	}
	if		(mode == ACCESS_READ)		optid = MSG_OPT_READ;
	else if (mode == ACCESS_WRITE)		optid = MSG_OPT_WRITE;
	else								optid = MSG_OPT_READBAD;

	curname = name;
	if (!ShowPaths && *curname == '\0')
		curname = "\"\"";

	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_LOCK,
					 curname, MSG(optid), EXPAND_NAME);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, mode, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_CreateDir()
 *
 *		Creates a new directory on disk
 */
ULONG ASM New_CreateDir(reg_d1 char *name, reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_CreateDir origfunc = (FP_CreateDir)PatchList[GID_MAKEDIR].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(name, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_MAKEDIR,
					 name, NULL, EXPAND_NAME);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_MakeLink()
 *
 *		Creates a new hard or soft link
 */
ULONG ASM New_MakeLink(reg_d1 char *name, reg_d2 LONG dest, reg_d3 LONG soft,
 					   reg_a6 void *libbase)
{
	MarkCallAddr;
	struct Process *myproc = (struct Process *)SysBase->ThisTask;
	FP_MakeLink origfunc   = (FP_MakeLink)PatchList[GID_MAKELINK].origfunc;
	int onlyfails          = OnlyShowFails;
	Event *ev;
	ULONG result;
	LONG optid;
	char namestr[MAX_STR_LEN+1];
	LONG seqnum;
	int  len;

	if (onlyfails) {
		result = origfunc(name, dest, soft, libbase);
		if (result)
			return (result);
	}
	if (soft)	optid = MSG_OPT_SOFTLINK;
	else		optid = MSG_OPT_HARDLINK;

	/*
	 *		Now build a string that looks like "name --> <dest>" to display
	 *		as our link. For soft links, we just concatenate the two
	 *		strings. For hard links, we have to generate a filename
	 *		from the lock also.
	 */
	len = strlen(name);
	if (len >= MAX_STR_LEN) {
		strncpy(namestr, name, MAX_STR_LEN);
		namestr[MAX_STR_LEN] = 0;
	} else {
		if (ShowPaths) {
			strcpy(namestr, MyNameFromLock(myproc->pr_CurrentDir,
										   name, namestr, MAX_STR_LEN-2));
			len = strlen(namestr);
		} else
			strcpy(namestr, name);

		strcat(namestr, LinkPointerString);		/* Usually ' --> ' */
		if (soft) {
			strncat(namestr, (char *)dest, MAX_STR_LEN - len - 1);
			namestr[MAX_STR_LEN] = 0;
		} else {
			strcat(namestr, MyNameFromLock(dest, NULL, namestr+len+1,
												 MAX_STR_LEN-len-1));
		}
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_MAKELINK,
					 namestr, MSG(optid), NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, dest, soft, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_Open()
 *
 *		Opens a new file on disk
 */
ULONG ASM New_Open(reg_d1 char *name, reg_d2 LONG accessMode,
				  reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_Open origfunc = (FP_Open)PatchList[GID_OPENFILE].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	char *optstr;
	LONG  seqnum;

	if		(accessMode == MODE_OLDFILE)	optstr = MSG(MSG_OPT_READ);
	else if (accessMode == MODE_NEWFILE)	optstr = MSG(MSG_OPT_WRITE);
	else if (accessMode == MODE_READWRITE)	optstr = MSG(MSG_OPT_MODIFY);
	else									optstr = MSG(MSG_OPT_UNKNOWN);

	if (onlyfails) {
		result = origfunc(name, accessMode, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_OPEN,
					 name, optstr, EXPAND_NAME);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(name, accessMode, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_Rename()
 *
 *		Renames a file on disk. This is a little tricky, since we have
 *		to generate two events: one containing the source name and
 *		one containing the destination name.
 */
ULONG ASM New_Rename(reg_d1 char *oldname, reg_d2 char *newname,
				     reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_Rename origfunc = (FP_Rename)PatchList[GID_RENAME].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	LONG  seqnum;

	if (onlyfails) {
		result = origfunc(oldname, newname, libbase);
		if (result)
			return (result);
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_RENAME,
					 oldname, NULL, EXPAND_NAME);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	ev->status = ES_READY;
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_RENAME2,
					 newname, NULL, EXPAND_NAME);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(oldname, newname, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result)
			ev->result = MSG(MSG_RES_OKAY);
		else
			ev->result = MSG(MSG_RES_FAIL);
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_RunCommand()
 *
 *		Runs a command from disk. Like Execute() only with more control.
 */
ULONG ASM New_RunCommand(reg_d1 BPTR seglist,  reg_d2 ULONG stack,
						 reg_d3 char *cmdline, reg_d4 cmdlen,
				     	 reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_RunCommand origfunc = (FP_RunCommand)PatchList[GID_RUNCOMMAND].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	char  stacksize[40];	/* Reserve additional space for return code! */
	LONG  seqnum;
	int	  stklen;
	char  *p;

	/*
	 *		For functions that may potentially take a long time to complete,
	 *		we check if they're being called from ROM straight away, so
	 *		that we can exit and not hang around waiting for them if they
	 *		are, even in the OnlyShowFails case.
	 */
	if (!MonROMCalls && CallAddr >= RomStart && CallAddr <= RomEnd)
		JumpOrigFunc(0);

	if (onlyfails) {
		result = origfunc(seglist, stack, cmdline, cmdlen, libbase);
		if (result != -1)
			return (result);
	}

	/*
	 *		This is kind of nasty but it works. We need somewhere to store
	 *		the return code string, but when we call CreateEvent(), we
	 *		don't know what it will be yet. So, we allocate additional
	 *		space at the end of our stacksize to hold this info, and
	 *		overwrite it later on.
	 */
	mysprintf(stacksize, "%ld", stack);
	stklen = strlen(stacksize);
	strcat(stacksize, "................");	/* Storage for return code */

	/*
	 *		Next, a quick hack to get rid of that nasty CR at the end
	 *		of the string (purely for visual purposes -- we put it back
	 *		afterwards, of course!)
	 */
	for (p = cmdline; *p; p++) {
		if (*p == '\n') {
			*p = ' ';
			break;
		}
	}
	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_RUNCOMMAND,
					 cmdline, stacksize, NO_EXPAND);
	if (*p)
		*p = '\n';

	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	ev->options[stklen] = 0;
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->result = "----";
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(seglist, stack, cmdline, cmdlen, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result != -1) {
			ev->result = ev->options + stklen + 1;
			mysprintf(ev->result, "%ld", result);
		} else {
			ev->result = MSG(MSG_RES_FAIL);
		}
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*
 *		New_SystemTagList()
 *
 *		Executes a command line. Like Execute() only more powerful.
 */
ULONG ASM New_SystemTagList(reg_d1 char *cmdline, reg_d2 TAGPTR tags,
					     	reg_a6 void *libbase)
{
	MarkCallAddr;
	FP_SystemTagList origfunc = (FP_SystemTagList)
								 PatchList[GID_SYSTEM].origfunc;
	int onlyfails = OnlyShowFails;
	Event *ev;
	ULONG result;
	char  optstr[20];	/* Reserve additional space for return code! */
	LONG  seqnum;

	/*
	 *		For functions that may potentially take a long time to complete,
	 *		we check if they're being called from ROM straight away, so
	 *		that we can exit and not hang around waiting for them if they
	 *		are, even in the OnlyShowFails case.
	 */
	if (!MonROMCalls && CallAddr >= RomStart && CallAddr <= RomEnd)
		JumpOrigFunc(0);

	if (onlyfails) {
		result = origfunc(cmdline, tags, libbase);
		if (result != -1)
			return (result);
	}
	/*
	 *		This is kind of nasty but it works. We need somewhere to store
	 *		the return code string, but when we call CreateEvent(), we
	 *		don't know what it will be yet. So, we allocate space for our
	 *		option string instead and then replace it with our result
	 *		string later on.
	 */
	strcpy(optstr, "............");	/* Storage for result string */

	ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_SYSTEM, cmdline,
					 optstr, NO_EXPAND);
	if (!ev) {
		if (onlyfails)
			return (result);
		JumpOrigFunc(0);
	}
	*ev->options = '\0';
	
	/*
	 *		Allocated event safely, now tell SnoopDos task to wake up
	 */
	if (onlyfails) {
		CheckForPause(NULL, 0);
	} else {
		ev->result = "----";
		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
		result = origfunc(cmdline, tags, libbase);
	}
	ObtainSemaphore(&BufSem);
	if (seqnum >= RealFirstSeq) {
		if (result != -1) {
			ev->result  = ev->options;
			ev->options = "";
			mysprintf(ev->result, "%ld", result);
		} else {
			ev->result = MSG(MSG_RES_FAIL);
		}
		ev->status = ES_READY;
		Signal(SnoopTask, NewEventMask);
	}
	ReleaseSemaphore(&BufSem);
	return (result);
}

/*****************************************************************************
 *
 *	  Now the Mother of all patches -- the PutMsg() packet monitor/Rexx patch
 *
 *	  (There are quite a few sub-functions associated with this.)
 *
 *****************************************************************************/

/*
 *		New_AddDosEntry(dlist)
 *
 *		This is a very simple patch which is only enabled when we are
 *		monitoring DOS packets. It simply signals the main task to update
 *		the device list whenever a new device is added to the DOS device
 *		list.
 */
ULONG ASM New_AddDosEntry(reg_d1 struct DosList *dlist, reg_a6 void *libbase)
{
	FP_AddDosEntry origfunc = (FP_AddDosEntry)
							   PatchList[GID_ADDDOSENTRY].origfunc;
	ULONG result;

	/*
	 *		Note that we better remember to actually add the new device
	 *		before telling SnoopDos to update its list, otherwise it
	 *		might miss it if it's too quick (e.g. priority > 0).
	 *
	 *		New: Even though we DO add it before signalling SnoopDos, the
	 *		device itself won't be fully initialised for a little bit longer.
	 *		Therefore, the main SnoopDos code 
	 */
	result = origfunc(dlist, libbase);

	if (dlist && dlist->dol_Type == DLT_DEVICE)
		Signal(SnoopTask, ScanDosListMask);
	
	return (result);
}

/*
 *		AddWaitingPacket(ev, seqnum, failmsg, dp, port, pt)
 *
 *		Adds a new entry to the list of waiting packets containing enough
 *		information to allow us to identify the reply to the packet when
 *		it is processed later on (in the main PutMsg() patch).
 *
 *		If there isn't a spare waiting packet, we will steal one and mark
 *		the event it represented as "Miss" to indicate the return code
 *		was missed.
 */
void AddWaitingPacket(Event *ev, LONG seqnum, char *failmsg,
					  struct DosPacket *dp, struct MsgPort *port,
					  struct PacketRef *pt)
{
	WaitPacket *wp;
	Event *oldev;
	ULONG oldseq;

	/*
	 *		Add details about this packet to our list of waiting
	 *		packets, so that when we spot the packet being returned
	 *		by PutMsg() later on, we can fill in the result code.
	 *
	 *		Note that we must not obtain both the buffer and packet
	 *		semaphores simultaneously, or a deadlock could occur.
	 */
	ObtainSemaphore(&PacketSem);
	if (IsListEmpty(&PacketFreeList)) {
		/*
		 *		Removing an existing node which is already in use. We need
		 *		to signal our main task to tell it not to continue waiting
		 *		for this event to complete. We have to wait until we unlock
		 *		the packetsem so we just note the details for now.
		 */
		wp     = (WaitPacket *)RemTail(&PacketWaitList);
		oldseq = wp->eventnum;
		oldev  = wp->event;
		//	if (!wp)
		//		KPrintF("Warning!! No packet available on WaitList!\n");
	} else {
		wp     = (WaitPacket *)RemHead(&PacketFreeList);
		oldseq = 0;
	}
	wp->dp   	 = dp;
	wp->sendtask = SysBase->ThisTask;
	wp->destport = port;
	wp->event    = ev;
	wp->eventnum = seqnum;
	wp->arg1 	 = dp->dp_Arg1;
	wp->arg2 	 = dp->dp_Arg2;
	wp->arg3 	 = dp->dp_Arg3;
	wp->resmsg	 = failmsg;
	wp->flags    = pt->flags;
	AddHead(&PacketWaitList, (Node *)wp);
	ReleaseSemaphore(&PacketSem);

	/*
	 *		Finally, check if we had to flush out an old packet from
	 *		our list of waiting packets when we allocated the new
	 *		packet. If so, mark the packet as missed (if it hasn't
	 *		already scrolled off the top of the buffer)
	 */
	if (oldseq) {
		ObtainSemaphore(&BufSem);
		if (oldseq >= RealFirstSeq) {
			oldev->result = MSG(MSG_RES_MISSED);
			oldev->status = ES_READY;
			Signal(SnoopTask, NewEventMask);
		}
		ReleaseSemaphore(&BufSem);
	}
}

/*
 *		HandleSimplePacket(calladdr, packet, port, packetref)
 *
 *		Handles the given packet by creating an event that matches the
 *		packet type (if we support it) and creating a new entry on the
 *		waiting packet list so we can match it up later.
 *
 *		This function handles only simple packets (i.e. those with the
 *		PK_COMMON flag set in the packet table). If the corresponding
 *		AmigaDOS option (Open, Lock, MakeDir etc.) is not currently
 *		enabled, then no entry is made for this packet.
 */
void HandleSimplePacket(ULONG calladdr, struct DosPacket *dp,
				     	struct MsgPort *port, struct PacketRef *pt)
{
	/*
	 *		Now create our new simple event to represent the packet.
	 *		The contents of this will vary depending on the packet
	 *		itself, but in general, is an exact match with the format
	 *		of the AmigaDOS function associated with the packet.
	 *
	 *		(If that function is not being monitored, then we do nothing.)
	 */
	Event *ev;
	LONG  seqnum;
	ULONG gadid;
	ULONG actid = 0;
	char  *volname = ((Task *)(port->mp_SigTask))->tc_Node.ln_Name;
	BPTR  lock = NULL;
	BSTR  name;
	char  *optstr;
	char  namebuf[MAX_STR_LEN+1];
	char  optbuf[20];

	namebuf[0] = '\0';

	/*
	 *		First, determine the format to use, based on the packet type
	 */
	switch (dp->dp_Type) {
		case ACTION_FINDINPUT:
			gadid	= GID_OPENFILE;
			actid	= MSG_ACT_POPEN;
			lock	= dp->dp_Arg2;
			name	= dp->dp_Arg3;
			optstr	= MSG(MSG_OPT_READ);
			break;

		case ACTION_FINDOUTPUT:
			gadid	= GID_OPENFILE;
			actid	= MSG_ACT_POPEN;
			lock	= dp->dp_Arg2;
			name	= dp->dp_Arg3;
			optstr	= MSG(MSG_OPT_WRITE);
			break;

		case ACTION_FINDUPDATE:
			gadid	= GID_OPENFILE;
			actid	= MSG_ACT_POPEN;
			lock	= dp->dp_Arg2;
			name	= dp->dp_Arg3;
			optstr	= MSG(MSG_OPT_MODIFY);
			break;

		case ACTION_DELETE_OBJECT:
			gadid	= GID_DELETE;
			actid	= MSG_ACT_PDELETE;
			lock    = dp->dp_Arg1;
			name    = dp->dp_Arg2;
			optstr  = NULL;
			break;

		case ACTION_CREATE_DIR:
			gadid	= GID_MAKEDIR;
			actid	= MSG_ACT_PMAKEDIR;
			lock	= dp->dp_Arg1;
			name	= dp->dp_Arg2;
			optstr  = NULL;
			break;

		case ACTION_LOCATE_OBJECT:
			gadid	= GID_LOCKFILE;
			actid	= MSG_ACT_PLOCK;
			lock	= dp->dp_Arg1;
			name	= dp->dp_Arg2;
			switch (dp->dp_Arg3) {
				case ACCESS_READ:	optstr = MSG(MSG_OPT_READ);		break;
				case ACCESS_WRITE:	optstr = MSG(MSG_OPT_WRITE);	break;
				default:			optstr = MSG(MSG_OPT_READBAD);	break;
			}
			break;

		case ACTION_MAKE_LINK:
		{
			int len;
			char *cname;

			gadid	= GID_MAKELINK;
			actid	= MSG_ACT_PMAKELINK;
			lock	= dp->dp_Arg1;
			cname	= BTOC(dp->dp_Arg2);

			/*
			 *		Now build a string that looks like "name --> <dest>" to
			 *		display as our link. For soft links, we just concatenate
			 *		the two strings. For hard links, we have to generate a
			 *		filename from the lock also.
			 */
			len = *cname;
			if (len >= MAX_STR_LEN) {
				strncpy(namebuf, cname+1, MAX_STR_LEN);
				namebuf[MAX_STR_LEN] = 0;
			} else {
				if (ShowPaths) {
					char *tempbuf = namebuf + MAX_STR_LEN-9 - len;

					memcpy(tempbuf, cname+1, len);
					tempbuf[len] = '\0';
					strcpy(namebuf, AsyncNameFromLock(volname, lock, tempbuf,
											          namebuf, MAX_STR_LEN-8));
					len = strlen(namebuf);
				} else {
					memcpy(namebuf, cname+1, len);
					namebuf[len] = '\0';
				}
				strcat(namebuf, LinkPointerString);		/* Usually ' --> ' */
				len += strlen(LinkPointerString);

				if (dp->dp_Arg4 == LINK_HARD) {
					/*
					 *		Hard link: interpret dp->dp_Arg3 as the lock
					 *		being linked to
					 */
					strcat(namebuf, AsyncNameFromLock(volname, dp->dp_Arg3,
													  NULL, namebuf+len+1,
													  MAX_STR_LEN-len-1));
					optstr = MSG(MSG_OPT_HARDLINK);
				} else {
					/*
					 *		Soft link: interpret dp->dp_Arg3 as the name of
					 *		the path to the link
					 */
					strncat(namebuf, (char *)dp->dp_Arg3, MAX_STR_LEN-len-1);
					namebuf[MAX_STR_LEN] = 0;
					optstr = MSG(MSG_OPT_SOFTLINK);
				}
			}
			volname = (char *)NO_EXPAND; /* Don't try and expand filename */
			name    = NULL;
			break;
		}

		case ACTION_RENAME_OBJECT:
			lock	= dp->dp_Arg1;
			name	= dp->dp_Arg2;
			optstr	= NULL;

			/*
			 *		This is a bit cheeky. The Rename action comes in
			 *		two parts, the first line with the original name and
			 *		the second part with the new name. We create the
			 *		first part here, and then fall through to let the
			 *		rest of the code handle the second part.
			 */
			if (CurSettings.Func.Opts[GID_RENAME]) {
				char *cname = BTOC(name);

				lock	  	= dp->dp_Arg3;
				name	  	= dp->dp_Arg4;
				if (*cname) {
					memcpy(namebuf, cname+1, *cname);
					namebuf[*cname] = '\0';
				} else {
					/*
					 *		If we have an empty string, we replace it with ""
					 *		since we may be renaming the current directory (?)
					 */
					strcpy(namebuf, "\"\"");
				}
				ev = CreateEvent(calladdr, &seqnum, MSG_ACT_PRENAME,
								 namebuf, NULL, (int)volname, lock);
				if (!ev)
					return;

				ev->status	= ES_READY;
				gadid		= GID_RENAME;
				actid	 	= MSG_ACT_PRENAME2;
			}
			break;
	}

	/*
	 *		Now, if we recognised the packet type, create a new event
	 *		with the info stored accordingly. We ignore any packets
	 *		for which monitoring is disabled.
	 */
	if (actid && CurSettings.Func.Opts[gadid]) {
		/*
		 *		We reserve 8 bytes at the start of the options string to
		 *		hold the result message (Ok or Fail) that is filled in
		 *		by the PutMsg() code when the packet is returned.
		 */
#define RES_LEN		8

		strcpy(optbuf, "........");
		if (optstr)
			strcat(optbuf, optstr);

		if (name) {
			char *cname = BTOC(name);

			namebuf[0] = '\0';
			if (*cname) {
				memcpy(namebuf, cname+1, *cname);
				namebuf[*cname] = '\0';
			} else {
				/*
				 *		If we have an empty string, we replace it with ""
				 *		since it will typically be a Lock("").
				 */
				strcpy(namebuf, "\"\"");
			}
		}
		ev = CreateEvent(calladdr, &seqnum, actid, namebuf, optbuf,
						 (int)volname, lock);
		if (!ev)
			return;

		ev->result    = ev->options;
		ev->options  += RES_LEN;
		*ev->result   = '\0';
		ev->status    = ES_UPDATING;
		/*
		 *		We add the event to the waiting list BEFORE checking for Pause
		 *		so that if while paused, the user decides to disable packet
		 *		monitoring, the packet will be in the wait queue already and
		 *		thus will be safely flushed; otherwise, it would hang around
		 *		for ever (not doing any damage but wasting resources).
		 */
		AddWaitingPacket(ev, seqnum, "%s", dp, port, pt);
		CheckForPause(ev, seqnum);
		Signal(SnoopTask, NewEventMask);
	}
#undef RES_LEN
}

/*
 *		HandleRawPacket(calladdr, packet, port, packetref)
 *
 *		Handles the given packet by creating an event that matches the
 *		packet type (if we support it) and creating a new entry on the
 *		waiting packet list so we can match it up later.
 *
 *		This function handles all packets and produces a raw hex dump
 *		(with the name of the packet) rather than the more english-like
 *		"Open", "Lock", etc.
 */
void HandleRawPacket(ULONG calladdr, struct DosPacket *dp,
				     struct MsgPort *port, struct PacketRef *pt)
{
	/*
	 *		Building this event is a little tricky, since the result
	 *		string will not be a static string but will contain the
	 *		actual return value. Thus, we allocate enough room in the
	 *		filename portion to hold the result string as well, and
	 *		adjust our variables accordingly after the event has
	 *		been created.
	 *
	 *		The same is true for raw packet types (i.e. those that we
	 *		don't recognise at all).
	 */
#define RAW_LEN		20		/* Length of possible raw packet type	*/
#define RES_LEN		26		/* Length of longest result code		*/ 
#define ARG_LEN		50		/* Length of longest argument list		*/

	static char *fmt[] = {
		"(None)", "%8lx", "%8lx, %8lx", "%8lx, %8lx, %8lx",
		"%8lx, %8lx, %8lx, %8lx", "%8lx, %8lx, %8lx, %8lx, %8lx"
	};
	static char *failmsg[] = {
		"%s", "%-4s %8lx", "%-4s %8lx", "%-4s %8lx, %8lx"
	};
	char args[RAW_LEN+RES_LEN+ARG_LEN];
	char *tname = args;
	Event *ev;
	LONG  seqnum;

	if (pt->flags & PK_RAW)
		tname += RAW_LEN;   

	tname += RES_LEN;
	memset(args, ' ', tname - args);
	mysprintf(tname, fmt[pt->numparams], dp->dp_Arg1, dp->dp_Arg2,
							dp->dp_Arg3, dp->dp_Arg4, dp->dp_Arg5);

	ev = CreateEvent(calladdr, &seqnum, pt->msgid, args,
					 ((Task *)(port->mp_SigTask))->tc_Node.ln_Name,
					 NO_EXPAND);
	if (!ev)
		return;

	if (pt->flags & PK_RAW) {
		/*
		 *		Now fix up action name and target name
		 */
		ev->action    = ev->filename;
		ev->filename += RAW_LEN;
		mysprintf(ev->action, MSG(MSG_ACT_RAWPACKET), dp->dp_Type);
	}
	ev->result    = ev->filename;	/* Point to storage for later result */
	ev->filename += RES_LEN;
	*ev->result   = 0;				/* Make result field empty initially */
	ev->status	  = ES_UPDATING;
	/*
	 *		We add the event to the waiting list BEFORE checking for Pause
	 *		so that if while paused, the user decides to disable packet
	 *		monitoring, the packet will be in the wait queue already and
	 *		thus will be safely flushed; otherwise, it would hang around
	 *		for ever (not doing any damage but wasting resources).
	 */
	AddWaitingPacket(ev, seqnum, failmsg[pt->flags & PK_MASK], dp, port, pt);
	CheckForPause(ev, seqnum);
	Signal(SnoopTask, NewEventMask);
}

/*
 *		New_PutMsg()
 *
 *		This function is used frequently by the system to send a message
 *		from one task to another. It is also used by applications wishing
 *		to send ARexx messages, which is what we want to monitor.
 *
 *		The problem is: how do we distinguish system messages (of which
 *		there are lots!) from ARexx messages (which are relatively
 *		infrequent).
 *
 *		It's almost impossible to get a perfect solution to this, but we
 *		can get fairly close by checking the following:
 *
 *		  - The destination port must have a name
 *		  - The host name associated with the supposed Rexx message must
 *			point to valid memory
 *        - The action type for the Rexx message is RXCOMM
 *        - The Rexx system library must be up and running
 *        - The library base must be word aligned (else we fault on a 68000)
 *        - The Close() vector in the Rexx private system library must
 *			match the Close() vector in the Rexx library base that we
 *			opened ourselves (the library bases themselves can be different).
 *
 *		If all these hold true, then we can assume it is a valid ARexx
 *		message and display it accordingly.
 *
 *		For DOS packets, things are a little simpler since DOS packets contain
 *		cross-links that can be easily checked for validity. To keep things
 *		moving at a reasonable speed, we only consider StandardPackets, where
 *		the DosPacket structure immediately follows the exec message structure.
 *		This is not universally true, but it covers all the cases we want to
 *		monitor.
 *
 *		We need to intercept DOS packets twice -- once on the initial PutMsg
 *		to the target device, and once when the target device PutMsg's the
 *		reply. Thus, we maintain a list of outstanding packets so we can
 *		detect which is which.
 *
 *		To identify DOS packets, we use the following criterion:
 *
 *			- Task is a process
 *			- Message node's name points to valid longword-aligned memory
 *			- DOS packet (from name ptr) points back to message header
 *			- Either current task is on the list of devices we're monitoring
 *			  or the destination message port's owner is on the list of
 *			  devices we're monitoring.
 *		
 *		If any of these conditions fail, we ignore the message.
 *
 */		
ULONG ASM New_PutMsg(reg_a0 struct MsgPort *port, reg_a1 struct Message *msg,
					 reg_a6 void *libbase)
{
	MarkCallAddr;

	FP_PutMsg origfunc   = (FP_PutMsg)PatchList[GID_SENDREXX].origfunc;
	Task *thistask		 = SysBase->ThisTask;

/*
 *		Save a little stack space and time by making these non-local
 */
#define rmsg			((struct RexxMsg *)msg)
#define rexxclosevect 	(ULONG *)(((ULONG)RexxSysBase) - 10)
#define thisclosevect	(ULONG *)(((ULONG)rmsg->rm_LibBase) - 10)

	struct DosPacket *dp;
	Task  *desttask;
	Event *ev;
	LONG  seqnum;
	ULONG taskhandle;

	/*
	 *		Before doing anything else, check if we're being called from
	 *		within an interrupt. If so, get the heck out of here since
	 *		trying to obtain a semaphore from within an interrupt is
	 *		definitely a bad idea!
	 *
	 *		To quickly check if we're within an interrupt, we simply see
	 *		if our stack pointer is within the range of the supervisor stack.
	 *		If it is, then we're probably in an interrupt, or at least in
	 *		Supervisor mode, and either way we don't want to continue.
	 *
	 *		We also ignore ramlib, for the usual reasons, and our own
	 *		background task (since it is used to expand locks from within
	 *		of this patch and an infinite loop would be easy!)
	 */
	if (thistask == RamLibTask || thistask == (Task *)BackgroundProc ||
		(getreg(REG_A7) >= (ULONG)SysBase->SysStkLower &&
		 getreg(REG_A7) <= (ULONG)SysBase->SysStkUpper))
	{
		JumpOrigFunc(0);
	}

	/*
	 *		See if we have a valid ARexx message. See the function
	 *		header for an explanation of all the checks
	 */
	if (CurSettings.Func.Opts[GID_SENDREXX]			&&
		port->mp_Node.ln_Name						&&
		*port->mp_Node.ln_Name						&&
		rmsg->rm_CommAddr							&&
		TypeOfMem(rmsg->rm_CommAddr)				&&
		(rmsg->rm_Action & RXCODEMASK) == RXCOMM	&&
		RexxSysBase									&&
		TypeOfMem(thisclosevect)					&&
		((ULONG)thisclosevect & 1) == 0             &&
		*thisclosevect == *rexxclosevect)
	{
		/*
		 *		Okay, we know we've got a valid Rexx message so now enter
		 *		it into the event buffer. First, however, check that we
		 *		are within our stack bounds (since the normal check done
		 *		in the assembly patch code is bypassed for this function
		 *		to accomodate RAM returning DOS packets).
		 */
		if (getreg(REG_A7) >= (ULONG)thistask->tc_SPLower &&
		 	getreg(REG_A7) <= (ULONG)thistask->tc_SPUpper &&
		 	getreg(REG_A7) <=
			 			((ULONG)thistask->tc_SPLower+CurSettings.StackLimit))
		{
			JumpOrigFunc(0);
		}
		ev = CreateEvent(CallAddr, &seqnum, MSG_ACT_SENDREXX, rmsg->rm_Args[0],
						 port->mp_Node.ln_Name, NO_EXPAND);
		if (!ev)
			return origfunc(port, msg, libbase);

		ev->status = ES_UPDATING;
		CheckForPause(ev, seqnum);
		ObtainSemaphore(&BufSem);
		if (seqnum >= RealFirstSeq) {
			ev->status = ES_READY;
			Signal(SnoopTask, NewEventMask);
		}
		ReleaseSemaphore(&BufSem);
		return origfunc(port, msg, libbase);
	}

	/*
	 *		Not an ARexx message, now check if it's a DOS packet.
	 *		First though, we must check to see if we're already in
	 *		the middle of a SnoopDos patch -- if so, we could end up
	 *		recursing forever unless we don't try and monitor this one.
	 *
	 *		See above for the criteria we use to spot packet messages.
	 */
	taskhandle = CheckTaskRecursion();
	if (!taskhandle)
		return origfunc(port, msg, libbase);

	dp		 = (struct DosPacket *)(msg->mn_Node.ln_Name);
	desttask = port->mp_SigTask;

	if ((ShowAllPackets | MonPackets)							&&
		thistask->tc_Node.ln_Type == NT_PROCESS					&&
		((ULONG)dp & 1) == 0									&&
		TypeOfMem(dp)											&&
		dp->dp_Link == msg										&&
		((ULONG)(dp->dp_Port) & 3) == 0							&&
		TypeOfMem(dp->dp_Port))
	{
		/*
		 *		Looks like we're going to monitor the packet.
		 *
		 *		First, check if the packet is already on our list of
		 *		outstanding packets. If so, we can handle it immediately.
		 */
		struct PacketRef *pt;
		WaitPacket *wp;
		Task **ptask;
		char *p = NULL;

		/*
		 *		Now some slightly tricky stuff. We can have up to TWO
		 *		entries on the waiting packet list which match the current
		 *		packet (one for the Packet Debugger and one for the
		 *		Monitor Packets option) and we need to acknowledge them
		 *		both. However, we also need to ensure we never try and
		 *		lock both the PacketSem and BufferSem at the same time
		 *		(since otherwise we could deadlock).
		 *
		 *		This effectively means we have to restart our list scan
		 *		after each packet has been acknowledged, until we have
		 *		no more left -- a small speed hit, but actually not too
		 *		bad since most times, we will be the only packet on the
		 *		wait list anyway. (We use 'p' as a flag to indicate
		 *		whether or not we found a packet, so we know to return
		 *		at the end of it all.)
		 */
		ObtainSemaphore(&PacketSem);
		wp = HeadNode(&PacketWaitList);
		while (NextNode(wp)) {
			if (wp->dp		 == dp					&&
				wp->sendtask != thistask			&& 
				wp->destport != port				&&
				wp->arg1	 == dp->dp_Arg1			&&
				wp->arg2	 == dp->dp_Arg2			&&
				wp->arg3	 == dp->dp_Arg3)
			{
				/*
				 *		Found the packet on our list, so go ahead and fill in
				 *		the result code for the associated event (assuming it
				 *		hasn't scrolled off the buffer, of course!)
				 */
				Event *ev 	  = wp->event;
				LONG   seqnum = wp->eventnum;
				char  *fmsg   = wp->resmsg;
				char   flags  = wp->flags;

				Remove((Node *)wp);
				AddHead(&PacketFreeList, (Node *)wp);
				ReleaseSemaphore(&PacketSem);
				ObtainSemaphore(&BufSem);
				if (seqnum >= RealFirstSeq) {
					/*
					 *		Okay, now fill in the result string. The space for
					 *		this was allocated when the event was defined.
					 *
					 *		For events that succeed, we can print "Okay",
					 *		"Okay <result>", and "Okay <result1> <result2>".
					 *
					 *		For events which fail, we can print
					 *		"Fail <result2>" or "Fail <result1> <result2>"
					 */
					UWORD fail;
					ULONG param;
					char  *q, *r;

#define					  evresmsg	MSG(fail ? MSG_RES_FAIL : MSG_RES_OKAY)

				 	fail = ((flags & PKF_BOOL) && dp->dp_Res1 == 0)	 ||
						   ((flags & PKF_NEG)  && dp->dp_Res1 == (ULONG)-1);

#if NOT_WORRIED_ABOUT_STACK_SIZE
					/*
					 *		Unfortunately, we can't use this nice simple
					 *		code because calling mysprintf() uses too much
					 *		stack space if we're being called from certain
					 *		handlers with minimal stack space (e.g. RAM).
					 *		Instead, we must do the same effect ourselves.
					 *		This code is left here so you can see what the
					 *		original intention was.
					 */
					if ((flags & PK_MASK) == PK_2OK) {
						mysprintf(ev->result, fmsg, evresmsg,
								  dp->dp_Res1, dp->dp_Res2);
					} else {
						mysprintf(ev->result, fmsg, evresmsg, 
								  (fail ? dp->dp_Res2 : dp->dp_Res1));
					}
					p = (char *)-1;	/* Non-zero means we did something */

#else /* Extremely worried about stack size */

					/*
					 *		Here we roll our own sprintf() (inline!) to keep
					 *		stack usage to an absolute minimum. It's a rather
					 *		small subset and somewhat hacked, you'll notice.
					 *
					 *		We make P non-null to indicate we did some work
					 */
					p = fmsg;
					q = ev->result;

					/*
					 *		First, skip over string formatter at start which
					 *		always holds either 'Ok' or 'Fail'
					 */
					strcpy(q, evresmsg);
					q += strlen(q);
					while (q < (ev->result + 4))
						*q++ = ' ';
					while (*p && *p != 's')
						p++;
					if (*p)
						p++;

					/*
					 *		Now parse remainder of string. The first parameter
					 *		encountered gets replaced with either dp_Res1 or
					 *		dp_Res2, depending on circumstances. The second
					 *		parameter always gets replaced with dp_Res2.
					 */
					if ((flags & PK_MASK) == PK_2OK || !fail)
						param = dp->dp_Res1;
					else
						param = dp->dp_Res2;

					while (*p) {
						if (*p != '%') {
							*q++ = *p++;
							continue;
						}
						while (*p && *p != 'x' && *p != 'X')
							p++;
						p++;
						if (param == 0) {
							strcpy(q, "       0");
						} else if (param == (ULONG)-1) {
							strcpy(q, "      -1");
						} else {
							/*
							 *		Expand param to 8 hex digits, right-aligned
							 */
							r = q + 7;
							while (param > 0) {
								*r-- = "0123456789ABCDEF"[param & 15];
								param >>= 4;
							}
							while (r >= q)
								*r-- = ' ';
						}
						q += 8;
						param = dp->dp_Res2; /* Second parm always dp_Res2 */
					}
					*q = '\0';
#endif
					/*
					 *		Unlike most times, we don't signal SnoopDos that
					 *		an event has been updated (yet) since there may
					 *		be another event to update too, and it's best
					 *		to do a single signal at the end (fewer context
					 *		switches are required.) So, we just mark the
					 *		event as ready.
					 */
					ev->status = ES_READY;
				}
				ReleaseSemaphore(&BufSem);
				ObtainSemaphore(&PacketSem);
				/*
				 *		Now restart the search from the beginning of the
				 *		list (since wp is no longer valid)
				 */
				wp = HeadNode(&PacketWaitList);
			} else {
				/*
				 *		Just advance to next element on list
				 */
				wp = NextNode(wp);
			}
		}
		ReleaseSemaphore(&PacketSem);
		if (p) {
			/*
			 *		Found a matching packet, so wake up the main task
			 *		and finish up. (We only signal the main snoop task
			 *		now, so that both events get printed simultaneously,
			 *		rather than one after another, which would be a little
			 *		messy). This also avoids us getting pre-empted inside
			 *		our loop above if SnoopDos is running at a higher priority
			 *		than this task, which is convenient (though not essential).
			 */
			Signal(SnoopTask, NewEventMask);
			goto done_putmsg;
		}

		/*
		 *		If we didn't find a match on our list of waiting packets,
		 *		then check to see if we're ignoring ROM calls. If so,
		 *		and if we were called from ROM, then immediately abort
		 *		(this would be done later anyway, but by checking now,
		 *		we save quite a bit of time.)
		 *
		 *		(We can't do this checking any earlier, or we'd miss
		 *		returned packets from ROM filesystems like RAM: and FFS).
		 *
		 *		We also exit if we're under the stack limit. (We can't
		 *		check this earlier because otherwise we'd miss the
		 *		return packet from RAM which only has a tiny stack.)
		 */
		if (((!MonROMCalls && CallAddr >= RomStart && CallAddr <= RomEnd)) ||
			(getreg(REG_A7) >= (ULONG)thistask->tc_SPLower &&
			 getreg(REG_A7) <= (ULONG)thistask->tc_SPUpper &&
			 getreg(REG_A7) <=
			 			((ULONG)thistask->tc_SPLower+CurSettings.StackLimit)))
		{
			goto done_putmsg;
		}

		/*
		 *		Okay, didn't match the packet with a previous outgoing one.
		 *		Now search the device list to see if we can match either
		 *		the owner of the port (a new outgoing packet) or the task ID
		 *		of the sender (a returning packet).
		 *
		 *		In the case of a returning packet, we just ignore it since
		 *		we missed it on the way out.
		 */
		for (ptask = DeviceTaskList; *ptask && *ptask != desttask; ptask++) {
			if (*ptask == thistask)
				goto done_putmsg;	/* Sender was a DOS device so ignore */
		}
		if (!*ptask) {
			/*
			 *		Couldn't find dest task on device list so it must
			 *		be a message to somewhere else -- hence, ignore it.
			 */
			goto done_putmsg;
		}

		/*
		 *		Okay, got ourselves a bona fida packet. Next, look it up
		 *		in our packet table to decide what to do with it.
		 *		Unrecognised packets come out as LAST_PACK_MSG which is
		 *		specifically initialised to handle them properly.
		 */
		for (pt = &PacketTable[0]; pt->msgid != LAST_PACK_MSG; pt++)
			if (pt->packetid == dp->dp_Type)
				break;
		
		if (pt->flags & PK_IGNORE)	/* Certain packet types are ignored */
			goto done_putmsg;

		/*
		 *		Okay, we finally (finally!) get to create a new entry
		 *		in our event buffer. Actually, we get to create two
		 *		entries, since if we have both the Packet Debugger
		 *		AND the Monitor Packets option turned on, we want to
		 *		see the output from both of them (even if they're both
		 *		the same event!).
		 *
		 *		Why? Well, if you're debugging a device driver and you
		 *		have Open packets etc. coming back and forth, then 
		 *		as well as the raw packet data, it's also kind of nice
		 *		to see actual filenames etc. in english instead of hex.
		 */
		if (MonPackets && (pt->flags & PK_COMMON))
			HandleSimplePacket(CallAddr, dp, port, pt);

		if (ShowAllPackets)
			HandleRawPacket(CallAddr, dp, port, pt);
	}

done_putmsg:
	EndTaskRecursion(taskhandle);
	return origfunc(port, msg, libbase);
}
