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

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


struct libObject
{
	struct Library *lo_Object;
	struct IORequest *lo_IORequest;
	STRPTR lo_Name;
	UWORD lo_Flags;
};

/* internal functions: */
struct MasterPatch *AddSystemNode( struct libObject *, WORD);
ULONG OpenLibObject( struct libObject *, struct TagItem *);


/****** patch.library/InstallPatchTagsA ***************************************
*
*   NAME
*        InstallPatchTagsA -- Installs a patchcode for library functions. (V2)
*        InstallPatchTags -- varargs stub for InstallPatchTagsA(). (V2)
*
*   SYNOPSIS
*        patch = InstallPatchTagsA( funcEntry, funcOffset, taglist )
*        D0                         A0         D0          A1
*
*        struct Patch *InstallPatchTagsA( APTR, UWORD, struct TagItem * );
*
*        patch = InstallPatchTags( funcEntry, funcOffset, ...)
*
*        struct Patch *InstallPatchTags( APTR, UWORD, ...);
*
*   FUNCTION
*        Adds a user provided routine to a library function.
*
*        NEW for V3: An internal call to SetPatch() will be made.
*        So all tags which are valid with SetPatch() may be specified in the
*        taglist.
*
*   INPUTS
*        funcEntry = pointer to the entry of the function to add
*        funcOffset = Library Offset Vector (LVO) of the function to patch
*        taglist = pointer to array of tags
*
*   TAGS
*        PATT_LibraryName (STRPTR) - Specifies that you want to patch a
*                library of the given name
*                ( as in exec.library/OpenLibrary() ).
*                Either PATT_LibraryName, PATT_DeviceName or PATT_LibraryBase
*                MUST be specified.
*        PATT_DeviceName (STRPTR) - Specifies that you want to patch a device
*                of the given name ( as in exec.library/OpenDevice() ).
*                Either PATT_LibraryName, PATT_DeviceName or PATT_LibraryBase
*                MUST be specified.
*        PATT_LibraryBase (struct Library *) Specifies that you want to patch
*                a library, device or resource with the given base.
*                You may get such a pointer by calling one of the following
*                exec functions: OpenLibrary(), OpenDevice(), OpenResource()
*                You may safely close this library, if InstallPatchTags()
*                returns, because patch.library will increase the OpenCount
*                to make sure, that the library won't be removed from the
*                system as long as there are patches installed.
*                V4: The OpenCount will not be increased, if the
*                    PatchSupervisor support program is active.
*                Either PATT_LibraryName, PATT_DeviceName or PATT_LibraryBase
*                MUST be specified.
*        PATT_LibVersion (ULONG) - Versionnumber for exec.library/OpenLibrary.
*                Optional in conjunction with PATT_LibraryName.
*                Should be specified, if a function is available only
*                from a certain library version on.
*                Default is NULL (any version).
*        PATT_DevFlags (ULONG) - Flags for exec.library/OpenDevice().
*                Optional in conjunction with PATT_DeviceName.
*                Default is NULL.
*        PATT_DevUnit (ULONG) - Unit for exec.library/OpenDevice().
*                Optional in conjunction with PATT_DeviceName.
*                Default is NULL.
*        PATT_PatchName (STRPTR) - An optional pointer to a string, which can
*                be used to search for a patch with FindPatch() or
*                FindPatchTags().
*                The null-terminated string will be copied, so you may free
*                its memory when InstallPatchTags() returns. The string should
*                identify the program that installed the patch.
*                Even though this tag is optional, the use of it is strongly
*                encouraged, because without it certain future enhancements
*                of the patch.library may not work
*                (e.g.: saving of user-settings).
*        PATT_Priority (BYTE) - Priority of the patch
*                valid range: -127...+126
*                It indicates the sequence of patches, if more than one patch
*                for a function is to be 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.
*        PATT_NewCodeSize (ULONG) - Optional length of the patch code to be
*                installed.
*                If this field is set to the correct value the PC-RELATIVE
*                patch code starting at funcEntry will be copied into an
*                internal buffer. So it is possible to deallocate the
*                patch code after InstallPatchTags() returned.
*        PATT_Result2 (ULONG *) - An optional pointer to a longword,
*                which will contain an errorcode as defined patch.h, when
*                the function returns.
*                Assembler programmers can get the same value from d1.
*        PATT_ProjectID (APTR) (V4) - Optional pointer to a PatchProject as
*                created by a CreatePatchProject() function call, indicating
*                that the newly installed patch belongs to one project or
*                program.
*                If used together with the RemovePatchProject() function
*                many patches can be removed with one function call.
*        PATT_UseXResult (BOOL) (V5) - If this tag is set to TRUE,
*                your patchcode MUST return a PatchXResult structure in d0.
*                See PatchAlloc for more informations.
*        PATT_Original *((* )()) (V6) - Pointer to a longword or NULL.
*                If you specify this tag you indicate, that you want to
*                call the original function from within your patchcode.
*                You may get this pointer by specifying a pointer
*                to a longword, which will be used in your patchcode to
*                call the original function (e.g.: jsr LongWord).
*                If you specify NULL with this tag you can get the
*                pointer to the original function by a later call to
*                GetPatch().
*                WARNING: Your patchcode must be ready to be called
*                immediately, unless you also specify PATT_Disable,TRUE.
*                So PATT_Original,NULL should always be paired with
*                PATT_Disable,TRUE.
*                Also note, that PATT_Priority MUST be > 0, if you specify this tag.
*
*
*        For more tags see the description of SetPatch().
*
*   RESULT
*        patch = pointer to a patch structure or NULL on failure
*
*        Errorcodes returned with PATT_Result2, NPAT_Result2 or in 
*        register d1:
*        Note that from V3 on errorcodes may be returned, even if patch is a
*        valid pointer, indicating a failure in the internal SetPatch() call.
*        Previous versions always set PATERR_Ok, if patch was a valid pointer.
*                PATERR_Ok
*                        Indicates success of the operation.
*                PATERR_OutOfMem
*                        Indicates that there was not enough memory to
*                        complete the operation.
*                PATERR_OpenLib
*                        The operation failed, because the exec.library
*                        function OpenLibrary() failed. Check the Autodocs
*                        for more informations about OpenLibrary().
*                PATERR_OpenDev
*                        The operation failed, because the exec.library
*                        function OpenDevice() failed. Check the Autodocs
*                        for more informations about OpenDevice().
*                PATERR_FuncNotStd
*                        The library offset vector of the function to patch
*                        was not in format that patch-library can accept.
*                        Patch.library can handle the following formats:
*                        jmp xxxxxx
*                        moveq.l #xx,Dx    bra.l xxxx (up to V5 only)
*                        moveq.l #xx,Dx    bra.s xx   (up to V5 only)
*                        Note that this is the type of error returned,
*                        if you are trying to patch a function that uses
*                        in-line code such as exec.library/GetCC()
*                PATERR_InvalidTags
*                        An error occurred while parsing the specified Tags
*                        (e.g.: EITHER PATT_LibraryName, PATT_DeviceName or
*                         PATT_LibraryBase MUST be used)
*
*        For more errorcodes see the description of SetPatch().
*
*   NOTES
*        This function may implicitly call dos.library functions, so do not
*        call it from tasks.
*
*   BUGS
*        Up to V4 it was not possible to pass pathnames with PATT_LibraryName
*        or PATT_DeviceName (e.g.: "Work:libs/foo.library").
*        This has been fixed in V5.
*
*   SEE ALSO
*        RemovePatchTags(), RemovePatchProject(), SetPatch(), PatchAlloc(),
*        patch.h, patchtags.h, exec.library/OpenLibrary(),
*        exec.library/OpenDevice()
*
******************************************************************************
*
*/

struct Patch * LIBFUNC InstallPatchTagsA( REGA0 APTR newcode GNUC_REGA0, REGD0 WORD LVO GNUC_REGD0, REGA1 struct TagItem *taglist GNUC_REGA1)
{
ULONG Result;
struct libObject libobject;
struct MasterPatch *master;
struct Patch *patch = NULL;
struct PatchProject *project;
STRPTR patchname;
BYTE pri;
APTR mem;
ULONG *Result2;
struct TagItem *tlist;
struct TagItem tarray[3];

	RemovePatchHandler();

	Result = SAVEObtainSemaphore();
	if (Result == 0L)
	{
		Result = OpenLibObject( &libobject, taglist);
		if( Result == 0L)
		{
			tarray[0].ti_Tag = PATT_LibraryName;
			tarray[0].ti_Data = (ULONG) libobject.lo_Name;
			tarray[1].ti_Tag = PATT_LVO;
			tarray[1].ti_Data = (ULONG) LVO;
			tarray[2].ti_Tag = TAG_DONE;
			master = FindPatchTagsA( tarray);

			if( master)
			{
				CloseLibObject( libobject.lo_Object, libobject.lo_IORequest, libobject.lo_Flags);
			}
			else
			{
				master = AddSystemNode( &libobject, LVO);
				if( master == NULL)
				{
					CloseLibObject( libobject.lo_Object, libobject.lo_IORequest, libobject.lo_Flags);
					Result = PATERR_OutOfMem;
				}
			}

			if( Result == PATERR_Ok)
			{
				patchname = (STRPTR) GetTagData(PATT_PatchName, 0L, taglist);
				pri = GetTagData(PATT_Priority, 0L, taglist);

				if( GetTagData(PATT_SYSTEM, 0L, taglist))
				{
					patch = AddStruct( (struct List *)(&(master->MPS_PatchHeader)), patchname, PS_TYPE_SYSTEM, pri);
					SetPCode( patch, taglist);
					patch = CreatePatchCode( patch);

				}
				else
				{
					patch = AddStruct( (struct List *)(&(master->MPS_PatchHeader)), patchname, PS_TYPE_USER, pri);
					SetPCode( patch, taglist);
					patch = CreatePatchCode( patch);
				}
				if( patch)
				{
					if( project = (struct PatchProject *)GetTagData(PATT_ProjectID, 0L, taglist))
					{
						AddTail( &(project->PPR_PatchListHeader), (struct Node *)(&(patch->PS_ProjectNode)));
					}

					if( tlist = FindTagItem(PATT_Original, taglist))
					{
						patch->PS_Flags |= PSF_OriginalType;
						if( tlist->ti_Data)
						{
							Result2 = (ULONG *)tlist->ti_Data;
							Result2[0] = (ULONG)patch->PS_SystemEntry;
						}
					}

					patch->PS_UserPatchCodeSize = GetTagData(PATT_NewCodeSize, 0L, taglist);
					if( patch->PS_UserPatchCodeSize)
					{
						mem = BAllocmem( patch->PS_UserPatchCodeSize, MEMF_PUBLIC);
						if( mem)
						{
							CopyMem( newcode, mem, patch->PS_UserPatchCodeSize);
							newcode = mem;
						}
						else Result = PATERR_OutOfMem;
					}
					if( Result == PATERR_Ok)
					{
						SetJSRInstr( patch->PS_Jsr, newcode);
						if( GetTagData(PATT_Disabled, 0L, taglist))
							patch->PS_PatchCode->PC_Disable = 1L;
						patch->PS_PatchCode->PC_PatchXResult = GetTagData(PATT_UseXResult, 0L, taglist);

						Forbid();
						Disable();
						LinkPatch( master);
						CacheClearU();
						Enable();

						Result = ConnectStartType( master);
						if( Result == PATERR_Ok)
						{
							SendNotify( PATCOD_PatchInstalled, patch);
							patch->PS_Flags |= PSF_InternalCall;
							Result = SetPatchA( patch, taglist);
							Permit();
							goto ende;
						}
						Permit();
					}
					RemStruct( patch);
				}
			}
		}
ende:		ReleaseSemaphore(&(PatchBase->PB_Semaphore));
	}

	Result2 = (ULONG *)GetTagData(PATT_Result2, 0L, taglist);
	if(Result2) Result2[0] = Result;
	ReturnD1(Result);
	return( patch);
}


/****i* patch.library/OpenLibObject ***************************************
*
*   NAME
*        OpenLibObject -- Get access to library type object.
*
*   SYNOPSIS
*        error = OpenLibObject( libobject, taglist )
*
*        ULONG OpenLibObject( struct libObject *, struct TagItem *);
*
*   FUNCTION
*        Get access to an library type object (Library, Device, or Resource)
*        depending on the specified tags and fill in a libObject structure.
*        Every successful call to OpenLibObject() must be paired, with a
*        call to CloseLibObject(), when you are done with the object.
*
*   INPUTS
*        libobject = pointer to a libobject structure for results
*        taglist = pointer to a taglist containing PATT_xxx tags
*
*   RESULT
*        error = Errorcode as defined in patch.h.
*
*   NOTES
*
*   BUGS
*
*   SEE ALSO
*        CloseLibObject(), patch.h
*
******************************************************************************
*
*/
ULONG OpenLibObject( struct libObject *libobject, struct TagItem *taglist)
{
ULONG Result = NULL;
LONG version;
STRPTR path;
ULONG devunit;
ULONG devflags;

	libobject->lo_Flags = 0;
	libobject->lo_IORequest = 0L;
	libobject->lo_Name = 0;
	libobject->lo_Object = (struct Library *)GetTagData(PATT_LibraryBase, 0L, taglist);
	if( libobject->lo_Object)
	{
		libobject->lo_Name = libobject->lo_Object->lib_Node.ln_Name;
		if( PatchBase->PB_PatchSVActive == 0)
		{
			libobject->lo_Object->lib_OpenCnt++;	/* ATOMIC operation */
			libobject->lo_Flags |= MPSF_IncLibUsage;
		}
		return( PATERR_Ok);
	}


	if( path = (STRPTR)GetTagData(PATT_LibraryName, 0L, taglist))
	{
		libobject->lo_Name = FilePart(path);
		version = GetTagData(PATT_LibVersion, 0L, taglist);

		if( PatchBase->PB_PatchSVActive)
		{
			libobject->lo_Object = (struct Library *)FindName( &(PatchBase->PB_SysBase->LibList), libobject->lo_Name);
		}
		else
		{
			libobject->lo_Object = OpenLibrary( path, version);
			if( libobject->lo_Object)
				libobject->lo_Flags |= MPSF_CloseLib;
			else
				Result = PATERR_OpenLib;
		}
		return( Result);
	}

	if( path = (STRPTR)GetTagData(PATT_DeviceName, 0L, taglist))
	{
		libobject->lo_Name = FilePart(path);

		if( PatchBase->PB_PatchSVActive)
		{
			libobject->lo_Object = (struct Library *)FindName( &(PatchBase->PB_SysBase->DeviceList), libobject->lo_Name);
		}
		else
		{
			devflags = GetTagData(PATT_DevFlags, 0L, taglist);
			devunit = GetTagData(PATT_DevUnit, 0L, taglist);
			libobject->lo_IORequest = BAllocmem( sizeof( struct IORequest), MEMF_PUBLIC | MEMF_CLEAR);
			if( libobject->lo_IORequest)
			{
				if( OpenDevice( path, devunit, libobject->lo_IORequest, devflags))
				{
					AFreemem( libobject->lo_IORequest);
					Result = PATERR_OpenDev;
				}
				else
				{
					libobject->lo_Object = (struct Library *)(libobject->lo_IORequest->io_Device);
					libobject->lo_Flags |= MPSF_CloseDev;
				}

			}
			else
				Result = PATERR_OutOfMem;
		}
		return(Result);
	}
	return( PATERR_InvalidTags);
}
/****i* patch.library/AddSystemNode ***************************************
*
*   NAME
*        AddSystemNode -- Create the minimum required nodes for patching
*                         a function.
*
*   SYNOPSIS
*        master = AddSystemNode( libobject, lvo )
*
*        struct MasterPatch *AddSystemNode( struct libObject *, WORD);
*
*   FUNCTION
*        Create the minimum required nodes (PS_TYPE_MASTER, PS_TYPE_START,
*        PS_TYPE_ORIG, PS_TYPE_END) for patching a library function.
*
*   INPUTS
*        libobject = pointer to a valid libobject structure
*        lvo = library offset vector of function to patch.
*
*   RESULT
*        master = pointer to newly created MasterPatch structure or NULL
*                 on failure.
*
*   NOTES
*
*   BUGS
*
*   SEE ALSO
*        RemoveSystemNode(), patch.h
*
******************************************************************************
*
*/
struct MasterPatch *AddSystemNode( struct libObject *libobject, WORD LVO)
{
struct MasterPatch *master;
struct Patch *patch;

	/* Make MasterStruct */
	if( master = AddStruct( &(PatchBase->PB_MasterPatchHeader), libobject->lo_Name, PS_TYPE_MASTER, 0L))
	{
		/* Make StartStruct */
		patch = AddStruct( (struct List *)&(master->MPS_PatchHeader), NULL, PS_TYPE_START, +127);
		SetPCode( patch, TAG_DONE);
		patch = CreatePatchCode( patch);
		if( patch)
		{

			/* Make OriginalStruct */
			patch = AddStruct( (struct List *)&(master->MPS_PatchHeader), NULL, PS_TYPE_ORIG, 0);
			SetPCode( patch, TAG_DONE);
			patch = CreatePatchCode( patch);
			if( patch)
			{
				/* Make EndStruct */
				if( AddStruct( (struct List *)&(master->MPS_PatchHeader), NULL, PS_TYPE_END, -128))
				{
					master->MPS_PatchedLibraryBase = libobject->lo_Object;
					master->MPS_PatchedLVO = LVO;
					master->MPS_Flags |= libobject->lo_Flags;
					master->MPS_PatchIORequest = libobject->lo_IORequest;
					return( master);
				}
			}
		}
		RemoveSystemNode( master);
	}
	return( NULL);
}
