/*
**	patch.library
**
**	Copyright © 1993-1997 by Stefan Fuchs
**		Freely distributable.
*/

#ifndef _PATCH_INCLUDES_H
#include "patch_includes.h"
#endif


APTR FindTaskListObject( struct Patch *, APTR, ULONG);

#define LVOMatchPattern -846
#define LVOMatchPatternNoCase -972
#define LVOFindTask -294
#define LVOFreeMem -210
#define LVOAllocMem -198
#define LVOStackSwap -732

/****** patch.library/SetPatchA ***************************************
*
*   NAME
*        SetPatchA -- Changes certain attributes of a patch. (V3)
*        SetPatch -- varargs stub for SetPatchA(). (V3)
*
*   SYNOPSIS
*        Error = SetPatchA( patch, taglist )
*        D0                 A0     A1
*
*        ULONG Error SetPatchA( struct Patch *, struct TagItem *);
*
*        Error = SetPatch( patch, ...)
*
*        ULONG Error SetPatch( struct Patch *, ...);
*
*   FUNCTION
*        Changes certain attributes of a patch (see TAGS).
*
*   INPUTS
*        patch = pointer to a patch structure or NULL for no action
*        taglist = pointer to array of tags
*
*   TAGS
*        PATT_CreateTaskList (ULONG) - Create a TaskList of the given type:
*                TL_TYPE_INCLUDE:
*                       all specified tasks will use the patchroutine,
*                       all others will ignore it.
*                       So if no Task is specified via PATT_AddTask... tags,
*                       the patchroutine will be used for NO tasks!
*                TL_TYPE_EXCLUDE:
*                       all specified tasks will ignore the patchroutine,
*                       all others will use it.
*                       So if no Task is specified via PATT_AddTask... tags,
*                       the patchroutine will be used for ALL tasks!
*        PATT_DeleteTaskList (BOOL) - Delete any existing TaskList
*                This will return the patch to its default behaviour, which is
*                to call the patchroutine for any task.
*                It is not required to remove the TaskList, before you remove
*                a patch, (via RemovePatchTags()) because this will
*                automatically be done by patch.library.
*        PATT_AddTaskID (struct Task *) - Add a task address to the patch
*                                         TaskList.
*                Patch.library takes care that one TaskID appears only once
*                in the TaskList.
*                Unlike many tags, you may specify this tag more than once
*                in one taglist to add multiple tasks to the list.
*        PATT_AddTaskName (STRPTR) - Add a task of the given name to
*                                    the patch TaskList.
*                The string will be copied into an internal buffer.
*                Patch.library takes care that one TaskName appears only once
*                in the TaskList.
*                Unlike many tags, you may specify this tag more than once
*                in one taglist to add multiple tasks to the list.
*        PATT_RemTaskID (struct Task *) - Remove a task address from the
*                                         patch TaskList
*                Unlike many tags, you may specify this tag more than once
*                in one taglist to add multiple tasks to the list.
*        PATT_RemTaskName (APTR) - Remove a task of the given name from the
*                                  patch TaskList.
*                Unlike many tags, you may specify this tag more than once
*                in one taglist to add multiple tasks to the list.
*        PATT_Disabled (V4) (BOOL) - Enable (False) or disable (True) a patch.
*                For disabling a patch a nesting counter is provided.
*                In order to restore normal patch execution, the programmer
*                must provide exactly one call to 'enable'
*                for every 'disable'.
*                If a patch is disabled this does not mean, that no task
*                continues to use the patched code. It only makes sure,
*                that no further tasks enter the patch code.
*                If PATT_Disabled,TRUE is set with InstallPatchTags() the
*                patch will not be called until explicitly enabled
*                by a call to SetPatchTags().
*        PATT_AddRemoveHook (V4) (struct Hook *) - Add a Hook, which is called
*                by patch.library whenever a patch is removed from memory.
*                Neither the Hook structure nor the hookfunction are copied
*                into internal memory. So freeing these structures must be
*                accomplished by the hookfunction itself.
*                It must also preserve all registers.
*                The hookobject (Register A2) for this hook is the patch
*                structure.
*                For more information about hooks see utility/hooks.h,
*                utility.library/CallHook() or the RKMs.
*                Unlike many tags, you may specify this tag more than once
*                in one taglist to add multiple tasks to the list.
*        PATT_RemRemoveHook (V4) (struct Hook *) - Remove a Hook installed
*                with PATT_AddRemoveHook.
*                Unlike many tags, you may specify this tag more than once
*                in one taglist to add multiple tasks to the list.
*        PATT_UserData (V5) (ULONG) - Attach userdata to a patch.
*                Use this tag only, if you are the owner (creator) of the
*                patch.
*        PATT_AddTaskPattern (V5) (STRPTR) - Add a pattern of tasknames
*                                            to the patch TaskList.
*                The pattern may contain dos wildcards.
*                Patch.library takes care that one pattern appears only once
*                in the TaskList.
*                Unlike many tags, you may specify this tag more than once
*                in one taglist to add multiple tasks to the list.
*                This tag only works with kickstart V37+.
*                DO NOT USE this tag, if you patch one of the following
*                dos-functions: MatchPattern() or MatchPatternNocase()
*        PATT_RemTaskPattern (V5) (STRPTR) - Remove a taskpattern from the
*                                            patch TaskList.
*                Unlike many tags, you may specify this tag more than once
*                in one taglist to add multiple tasks to the list.
*        PATT_StackSize (V5) (ULONG) - Minimum stacksize (in bytes) your
*                                      function requires.
*                This value should be a multiple of 4 and more than 256.
*                If a taskpattern (see PATT_AddtaskPattern) is active,
*                at least 1500 Bytes should be specified.
*                DO NOT USE this tag, if you patch one of the following
*                exec-functions:
*                FindTask(), AllocMem(), FreeMem() or StackSwap()
*                You may specify zero to turn stack extension off.
*        PATT_Priority (V5) (BYTE) - Priority of the patch
*                valid range: -127...+126
*                It indicates the sequence of patches, if more than
*                one patch for a function is installed.
*                The original code has a priority of 0.
*                Priority:    Meaning:
*                >0: patch will be executed before the original
*                <0: patch will be executed after the original
*                =0: patch will be executed instead of the original (default)
*                Normally only the priorities +5, 0, -5 should be used.
*                Changing the priority of an active patch is a difficult
*                operation. It may fail with PATERR_PatchInUse.
*                You MUST NOT change the priority of a patch in respect
*                to the original function (see above).
*
*   RESULT
*        Error = errorcode as defined in patch.h.
*                PATERR_Ok
*                        Indicates success of the operation.
*                PATERR_OutOfMem
*                        Indicates that there was not enough memory to
*                        complete the operation.
*                PATERR_PatchInUse
*                        Indicates that some other task is using the
*                        installed function and the priority of the patch
*                        can't be changed right now.
*                        Your task may wait and try again later.
*                PATERR_InvalidHandle
*                        Indicates that the pointer to the patch passed
*                        to the function was not or is no longer valid.
*                        This might happen, if you pass a wrong pointer or
*                        you got the pointer via FindPatch() and another
*                        task has removed the patch before this task called
*                        RemovePatchTags().
*                PATERR_NoTaskList
*                        No valid TaskList is attached to the patch.
*                        The SetPatchA() function with the PATT_CreateTaskList
*                        tag specified, must be called to allocate a TaskList.
*                PATERR_TaskListExists
*                        PATT_CreateTaskList was specified, but there already
*                        exists a TaskList.
*                PATERR_InvalidTaskList
*                        PATT_CreateTaskList was specified with a wrong
*                        parameter.
*                PATERR_KickTooOld
*                        PATT_AddTaskPattern was used on a system with
*                        Kickstart V36or lower.
*                        (No longer possible with patch.library V6+, as
*                        Kickstart V37+ is required.)
*                PATERR_InvalidPattern
*                        PATT_AddTaskList was used with a string,
*                        which contained an invalid pattern.
*                        See dos.library ParsePattern() for more informations.
*                PATERR_Restricted
*                        PATT_AddTaskPattern or PATT_StackSize was used for a
*                        forbidden function (see TAGS).
*
*   NOTES
*        Using the TaskList feature provided by this function will
*        (by a huge amount in the case of PATT_AddTaskPattern) increase
*        the stackusage of the patched function.
*        See PATT_StackSize for a solution.
*
*   BUGS
*
*   SEE ALSO
*        InstallPatchTags(), patch.h, patchtags.h,
*        dos.library/ParsePatternNoCase(), dos.library/ParsePattern()
*
******************************************************************************
*
*/

ULONG LIBFUNC SetPatchA( REGA0 struct Patch *patch GNUC_REGA0, REGA1 struct TagItem *taglist GNUC_REGA1)
{
ULONG Result = PATERR_Ok;
struct TagItem *tlist;
struct TLHeader *tasklistheader;
struct Node *pointer;
struct MasterPatch *master;
ULONG bufsize;
APTR pattern;

	if( patch)
	{
		Result = SAVEObtainSemaphore();
		if (Result == 0L)
		{
			if( patch->PS_Node.ln_Type != PS_TYPE_MASTER)
			{
				Result = TestPatchHandle(patch);
				if (Result == 0L)
				{



					if( tlist = FindTagItem(PATT_CreateTaskList, taglist))
					{
						if((tlist->ti_Data == TL_TYPE_EXCLUDE) || (tlist->ti_Data == TL_TYPE_INCLUDE))
						{
							if(patch->PS_PatchCode->PC_TaskList)
							{
								Result = PATERR_TaskListExists;
							}
							else
							{
								if( tasklistheader = BAllocmem( sizeof( struct TLHeader), MEMF_CLEAR | MEMF_PUBLIC))
								{
									NewList( (struct List *)tasklistheader);
									tasklistheader->TL_List.lh_Type = tlist->ti_Data;
									if (tlist->ti_Data == TL_TYPE_INCLUDE)
									{
										tasklistheader->TL_Result1 = 0L;
										tasklistheader->TL_Result2 = 1L;
									}
									else
									{
										tasklistheader->TL_Result1 = 1L;
										tasklistheader->TL_Result2 = 0L;
									}
									patch->PS_PatchCode->PC_TaskList = tasklistheader;
								}
								else
								{
									Result = PATERR_OutOfMem;
								}
							}
						}
						else Result = PATERR_InvalidTaskList;
						if( Result != PATERR_Ok) goto Ende;
					}



					if( GetTagData(PATT_DeleteTaskList, 0L, taglist))
					{
						if(tasklistheader = patch->PS_PatchCode->PC_TaskList)
						{
							patch->PS_PatchCode->PC_TaskList = 0L;
							for (pointer = (struct Node *) tasklistheader->TL_List.lh_Head;
							     pointer->ln_Succ;
							     pointer = (struct Node *) tasklistheader->TL_List.lh_Head)
							{
								if( pointer->ln_Type == TLI_TYPE_TASKPATTERN)
									AFreemem( ((struct TLNode *)pointer)->TN_Pattern);
								ADeleteMyListNode(pointer);
							}
							AFreemem(tasklistheader);
						}
					}



					if( tlist = FindTagItem(PATT_Disabled, taglist))
					{
						if( tlist->ti_Data)
						{
							if( (patch->PS_Flags & PSF_InternalCall) == 0)
								patch->PS_PatchCode->PC_Disable++;
						}
						else
						{
							patch->PS_PatchCode->PC_Disable--;
						}
					}



					tlist = taglist;
					while( tlist = FindTagItem(PATT_AddTaskID, tlist))
					{
						if( patch->PS_PatchCode->PC_TaskList)
						{
							if( FindTaskListObject( patch, (APTR)tlist->ti_Data, TLI_TYPE_TASKID) == NULL)
							{
								if( pointer = ACreateMyListNode( (struct List *) patch->PS_PatchCode->PC_TaskList, sizeof( struct Node)))
								{
									pointer->ln_Name = (STRPTR)tlist->ti_Data;
									pointer->ln_Type = TLI_TYPE_TASKID; /* This must be set, if everything else is initialized ok */
								}
								else
								{
									Result = PATERR_OutOfMem;
									break;
								}
							}
						}
						else
						{
							Result = PATERR_NoTaskList;
							break;
						}
						tlist++;
					}
					if( Result != PATERR_Ok) goto Ende;



					tlist = taglist;
					while( tlist = FindTagItem(PATT_AddTaskName, tlist))
					{
						if( patch->PS_PatchCode->PC_TaskList)
						{
							if( FindTaskListObject( patch, (APTR)tlist->ti_Data, TLI_TYPE_TASKNAME) == NULL)
							{
								if( pointer = ACreateMyListNodeNamed( (struct List *)patch->PS_PatchCode->PC_TaskList, sizeof( struct Node), (STRPTR)tlist->ti_Data))
								{
									pointer->ln_Type = TLI_TYPE_TASKNAME; /* This must be set, if everything else is initialized ok */
								}
								else
								{
									Result = PATERR_OutOfMem;
									break;
								}
							}
						}
						else
						{
							Result = PATERR_NoTaskList;
							break;
						}
						tlist++;
					}
					if( Result != PATERR_Ok) goto Ende;



					tlist = taglist;
					while( tlist = FindTagItem(PATT_AddTaskPattern, tlist))
					{
						if( patch->PS_PatchCode->PC_TaskList)
						{
							master = GetMasterPatch( patch);
							if (DOSBase == (struct DosLibrary *)(master->MPS_PatchedLibraryBase))
							{
								if (master->MPS_PatchedLVO == LVOMatchPattern)
								{
									Result = PATERR_Restricted;
									break;
								}
								if (master->MPS_PatchedLVO == LVOMatchPatternNoCase)
								{
									Result = PATERR_Restricted;
									break;
								}
							}
							if( FindTaskListObject( patch, (APTR)tlist->ti_Data, TLI_TYPE_TASKPATTERN) == NULL)
							{
								bufsize = strlen( (STRPTR)tlist->ti_Data) * 2 + 2;
								pattern = BAllocmem( bufsize, MEMF_CLEAR | MEMF_PUBLIC);
								if( pattern)
								{
									if( ParsePatternNoCase( (STRPTR)tlist->ti_Data, pattern, bufsize) != -1)
									{
										if( pointer = ACreateMyListNodeNamed( (struct List *)patch->PS_PatchCode->PC_TaskList, sizeof( struct Node) + 4, (STRPTR)tlist->ti_Data))
										{
											((struct TLNode *)pointer)->TN_Pattern = pattern;
											pointer->ln_Type = TLI_TYPE_TASKPATTERN; /* This must be set, if everything else is initialized ok */
										}
										else
										{
											AFreemem( pattern);
											Result = PATERR_OutOfMem;
											break;
										}
									}
									else
									{
										AFreemem( pattern);
										Result = PATERR_InvalidPattern;
										break;
									}
								}
								else
								{
									Result = PATERR_OutOfMem;
									break;
								}
							}
						}
						else
						{
							Result = PATERR_NoTaskList;
							break;
						}
						tlist++;
					}
					if( Result != PATERR_Ok) goto Ende;



					tlist = taglist;
					while( tlist = FindTagItem(PATT_RemTaskID, tlist))
					{
						ADeleteMyListNode( FindTaskListObject( patch, (APTR)tlist->ti_Data, TLI_TYPE_TASKID));
						tlist++;
					}



					tlist = taglist;
					while( tlist = FindTagItem(PATT_RemTaskName, tlist))
					{
						pointer = FindTaskListObject( patch, (APTR)tlist->ti_Data, TLI_TYPE_TASKNAME);
						ADeleteMyListNode( pointer);
						tlist++;
					}



					tlist = taglist;
					while( tlist = FindTagItem(PATT_RemTaskPattern, tlist))
					{
						if( pointer = FindTaskListObject( patch, (APTR)tlist->ti_Data, TLI_TYPE_TASKPATTERN))
						{
							AFreemem( ((struct TLNode *)pointer)->TN_Pattern);
							ADeleteMyListNode( pointer);
						}
						tlist++;
					}



					tlist = taglist;
					while( tlist = FindTagItem(PATT_AddRemoveHook, tlist))
					{
						AddTail(&(patch->PS_RemoveHookList), (struct Node *)tlist->ti_Data);
						tlist++;
					}



					tlist = taglist;
					while( tlist = FindTagItem(PATT_RemRemoveHook, tlist))
					{
						Remove((struct Node *)tlist->ti_Data);
						tlist++;
					}



					if( tlist = FindTagItem(PATT_UserData, taglist))
					{
						patch->PS_UserData = tlist->ti_Data;
					}



					if( tlist = FindTagItem(PATT_StackSize, taglist))
					{
						master = GetMasterPatch( patch);
						if ((struct Library *)SysBase == master->MPS_PatchedLibraryBase)
						{
							switch( master->MPS_PatchedLVO)
							{
								case LVOStackSwap:
								case LVOAllocMem:
								case LVOFreeMem:
								case LVOFindTask:
									Result = PATERR_Restricted;
									goto Ende;
							}
						}
						patch->PS_StackSize = tlist->ti_Data;
						patch->PS_PatchCode->PC_StackSize = tlist->ti_Data;
						ReCalcStackSize( master);
					}



					/* This shouldn't be called, when installing a patch! */
					if( (patch->PS_Flags & PSF_InternalCall) == 0)
					{
						if( tlist = FindTagItem(PATT_Priority, taglist))
						{
							if( tlist->ti_Data)
							{
								Disable();
								if (TestUsage( patch))
								{
									Enable();
									Result = PATERR_PatchInUse;
								}
								else
								{
									patch->PS_Node.ln_Pri = tlist->ti_Data;
									master = GetMasterPatch( patch);
									Remove( (struct Node *)patch);
									Enqueue( (struct List *)&(master->MPS_PatchHeader), (struct Node *)patch);
									LinkPatch( master);
									CacheClearU();
									Enable();
								}
							}
						}
					}
				}
			}
			else
			{



				master = (struct MasterPatch *)patch;
				if( tlist = FindTagItem(PATT_Attach, taglist))
				{
					master->MPS_PatchedLibraryBase = (struct Library *)tlist->ti_Data;
					Result = ConnectStartType( master);
				}



				if( GetTagData(PATT_Detach, 0L, taglist))
				{
					master->MPS_Flags &= ~MPSF_Attached;
					master->MPS_PatchedLibraryBase = 0L;
				}



			}
Ende:
			ReleaseSemaphore(&(PatchBase->PB_Semaphore));

			if( patch->PS_Node.ln_Type != PS_TYPE_MASTER)
				if( (Result == PATERR_Ok) && ((patch->PS_Flags & PSF_InternalCall) == 0))
					SendNotify( PATCOD_PatchChanged, patch);
		}
		if( patch->PS_Node.ln_Type != PS_TYPE_MASTER)
			patch->PS_Flags &= ~PSF_InternalCall;
	}
	return(Result);
}

/*--------------------------------------------------------------------------*/
APTR FindTaskListObject( struct Patch *patch, APTR Object, ULONG code)
{
struct Node *pointer;

	if( patch->PS_PatchCode->PC_TaskList)
	{
		for (pointer = (struct Node *) patch->PS_PatchCode->PC_TaskList->TL_List.lh_Head;
		     pointer->ln_Succ;
		     pointer = (struct Node *)pointer->ln_Succ)
		{
			switch( pointer->ln_Type)
			{
				case TLI_TYPE_TASKID:
					if (code == TLI_TYPE_TASKID && Object == pointer->ln_Name)
						return( pointer);
				case TLI_TYPE_TASKNAME:
					if (code == TLI_TYPE_TASKNAME && strcmp(pointer->ln_Name, Object) == 0L)
						return( pointer);
				case TLI_TYPE_TASKPATTERN:
					if (code == TLI_TYPE_TASKPATTERN && strcmp(pointer->ln_Name, Object) == 0L)
						return( pointer);
			}
		}
	}
	return(NULL);
}
