/**************************************************************************/
/* Discription: 							  */
/*  The exec.library SetFunction function is very unsafe to use when	  */
/*  more than one program changes a function in a library.		  */
/*									  */
/*  The programs must change the function back in reverse order.	  */
/*									  */
/*  example:								  */
/*   Two different programs (A & B) changes the DisplayBeep function      */
/*   in intuition.library.						  */
/*									  */
/*-------------  First both programs changes the function ----------------*/
/*   A: A_OldFunc = SetFunction(IntuitionBase,_LVODisplayBeep,A_DispBeep);*/
/*	 A_OldFunc will contain the original position of DisplayBeep.	  */
/*									  */
/*   B: B_OldFunc SetFunction(IntuitionBase,_LVODisplayBeep,B_DispBeep);  */
/*	 B_OldFunc will contain the position of A_DispBeep.		  */
/*									  */
/*----	Then both programs restores the function (In wrong order)---------*/
/*   A: SetFunction(IntuitionBase, _LVODisplayBeep; A_OldFunc)            */
/*	 The original DisplayBeep function will be restored.		  */
/*	 (B_DispBeep will not be called)                                  */
/*									  */
/*   B: SetFunction(IntuitionBase, _LVODisplayBeep; B_OldFunc)            */
/*	 The function A_DispBeep will be started again. 		  */
/*	 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~			  */
/*	 (If program A is no longer present the guru comes when           */
/*	  someone calls the DisplayBeep function.)			  */
/*									  */
/*  This program fixes these problems in a fully tranparent way 	  */
/*									  */
/**************************************************************************/


/**************************************************************************/
/*  LibHeader								  */
/*     NextLib:  Next library in a linked list. 			  */
/*     LibBase:  Pointer to the libraries base (e.g. intuitionbase).      */
/*     Patches:  Pointer to linked list of patches made to this library.  */
/**************************************************************************/
struct LibHeader {
  struct Library    *LibBase;
  struct Patches    *Patches;
  struct LibHeader  *NextLib;
};

/************************************************************************/
/*  Patches								*/
/*     JMP	: Opcode for JMP (used to call the old function).       */
/*     OldFunc	: Address to the previus function (before SetFunction). */
/*     NewFunc	: The new function.					*/
/*     Offset	: Offset in library (Must be negativ).                  */
/*     NextPatch: Pointer to next patch in list.			*/
/*									*/
/*  The first Patch in the library list is just a dummy,		*/
/*  Offset = MAGIC_OFFSET (makes the code more simple).                 */
/*  After the dummy comes all patches made to this library sorted after */
/*  their Offset (lowest value first). If there are more then one patch */
/*  at one offset, the most recent comes first. 			*/
/************************************************************************/
struct Patches {
  USHORT	  JMP;
  LONG		  OldFunc;
  LONG		  NewFunc;
  SHORT 	  Offset;
  struct Patches *NextPatch;
};
/******* Some useful defines ********/
  /* This offset identifies our dummy patch */
#define MAGIC_OFFSET	  123
  /* Kickstart library versions */
#define KS13  35
  /*************/
#define AND &&
#define OR ||

/************* Functions ****************/
       /*			A0		    A1		     D0 	*/
STATIC LONG  NewSetFunction(APTR Offset, struct Library *Library, LONG NewFunc);
extern LONG  OldSetFunction(APTR Offset, struct Library *Library, LONG NewFunc);

       struct LibHeader *MakeHead(struct Library *Library);
STATIC struct Patches	*MakePatch(APTR Offset, struct Patches *Next, LONG NewFunc);

/************* Global variables **********/
extern struct DosBase *DOSBase; 		 /* Must be first */
extern struct SignalSemaphore SetFuncSemaphore;
extern struct LibHeader *MainHeader;

extern __fptr  OldSetFuncPtr;

/*************************************************/
/* NewSetFunction: Replaces the exec SetFunction */
/*************************************************/
LONG NewSetFunction(APTR Offset, struct Library *Library, LONG NewFunc)         /* D0 */
{
  struct LibHeader *HeadPtr, *HeadPtr1;
  struct Patches *Patch;
  LONG	OldFunc = NULL;

  geta4();

  /* old dos.library can't be handled here */
  if (Library == DOSBase  AND  Library->lib_Version <= KS13)
    return OldSetFunction(Offset, Library, NewFunc);

  /* Be sure no one else is using the patch list */
  ObtainSemaphore(&SetFuncSemaphore);

  HeadPtr = MainHeader;

  while (HeadPtr1 = HeadPtr->NextLib) {
    /************************/
    /* Find the library     */
    /************************/

    if (HeadPtr1->LibBase == Library) {
      Patch = HeadPtr1->Patches;

      FOREVER { /* We leave this loop with goto */
	/*******************************************/
	/* Look through the sorted list of patches */
	/*******************************************/
	struct Patches *Patch1 = Patch->NextPatch;

	if (Patch1 == NULL OR Patch1->Offset < (WORD)Offset) {
	  /****************************************/
	  /* No patch at this offset is installed */
	  /* Make a new one.			  */
	  /****************************************/
	  Patch1 = MakePatch(Offset, Patch1, NewFunc);
	  if (Patch1)
	    if (Patch1->OldFunc = OldSetFunction(Offset, Library, NewFunc))
	      OldFunc = (LONG)(Patch->NextPatch = Patch1);
	    else /* SetFunction failed */
	      FreeMem(Patch1,sizeof(struct Patches));
	  goto Done;		    /******************************/
	}

	if (Patch1->Offset == (WORD)Offset) {
	  /***************************************/
	  /* There are patches already installed */
	  /* Find out if anyone is to be removed */
	  /***************************************/
	  struct Patches *Patch2;

	  /********************************************************/
	  /* Some tasks don't care about the data returned from   */
	  /* SetFunction (e.g. Xoper), instead they look directly */
	  /* into library structure to find the old function.	  */
	  /* Since this is bad programming we can't do much about */
	  /* it.  If we are lucky no other patches are made to	  */
	  /* the same offset.					  */
	  /********************************************************/
	  if ((LONG)Patch1 == NewFunc OR Patch1->OldFunc == NewFunc) {
	    /*******************************************************/
	    /* User want's to remove the last (or only) made patch */
	    /* This is easy, just remove it			   */
	    /*******************************************************/
	    OldFunc = OldSetFunction(Offset, Library, Patch1->OldFunc);
	    if (OldFunc == Patch1->NewFunc) {
	      Patch->NextPatch = Patch1->NextPatch;
	      FreeMem(Patch1,sizeof(struct Patches));
	    }
	    else {
	      /******************************************************/
	      /* If we gets here something is very wrong	    */
	      /* Either has someone managed to change the vector    */
	      /* from the outside (Virus !?) or SetFunction failed  */
	      /* for some strange reason.			    */
	      /* We just restore the vector and hopes for the best. */
	      /******************************************************/
	      if (OldFunc) {
		OldSetFunction(Offset, Library, OldFunc);
		OldFunc = NULL;
	      }
	      goto Done;
	    }

	    /**************************************************/
	    /* If this was the last patch made to the library */
	    /* Release unused memory.			      */
	    /**************************************************/
	    if (Patch->Offset == MAGIC_OFFSET AND
		Patch->NextPatch == NULL) {
	      HeadPtr->NextLib = HeadPtr1->NextLib;
	      FreeMem(Patch,sizeof(struct Patches));
	      FreeMem(HeadPtr1,sizeof(struct LibHeader));
	    }
	    goto Done;		    /******************************/
	  }

	  /********************************************************/
	  /* Are there any more patches made to the same offset ? */
	  /********************************************************/
	  while ((Patch2 = Patch1->NextPatch)  AND
		 Patch2->Offset == (WORD)Offset) {
	    if ((LONG)Patch2 == NewFunc OR Patch2->OldFunc == NewFunc) {
	      /*****************************************************/
	      /* The user want's to remove an older patched        */
	      /* This is more tricky, must restore OldFunc pointer */
	      /*****************************************************/
	      Patch1->NextPatch = Patch2->NextPatch;
	      Patch1->OldFunc = Patch2->OldFunc;
	      OldFunc = Patch2->NewFunc;  /* No SetFunction here !! */
	      FreeMem(Patch2,sizeof(struct Patches));
	      goto Done;	    /******************************/
	    }
	    Patch1 = Patch2;
	  }
	  /*****************************************/
	  /* This was a new patch which takes over */
	  /*****************************************/
	  Patch1 = MakePatch(Offset, Patch->NextPatch, NewFunc);
	  if (Patch1)
	    if (Patch1->OldFunc = OldSetFunction(Offset, Library, NewFunc))
	      OldFunc = (LONG)(Patch->NextPatch = Patch1);
	    else  /* SetFunction failed */
	      FreeMem(Patch1,sizeof(struct Patches));
	  goto Done;		    /******************************/
	} /* if (Patch1->Offset == ... */
	/* Try next patch in list */
	Patch = Patch1;
      } /* FOREVER */
    } /* if (HeadPtr->Lib ... */
    /* try next library in list */
    HeadPtr = HeadPtr1;
  } /* while (HeadPtr1 = ... */

  /***************/
  /* New library */
  /***************/
  HeadPtr1 = HeadPtr->NextLib = MakeHead(Library);

  /*******************/
  /* Crete the patch */
  /*******************/
  if (HeadPtr1) {
    if (Patch = MakePatch(Offset, NULL, NewFunc)) {
      /* crete a dummy patch */
      if (HeadPtr1->Patches = MakePatch((APTR)MAGIC_OFFSET, Patch, NULL)) {
	if (Patch->OldFunc = OldSetFunction(Offset, Library, NewFunc)) {
	  HeadPtr->NextLib = HeadPtr1;
	  OldFunc = (LONG)Patch;
	  goto Done;
	} /* SetFunction failed */
	FreeMem(HeadPtr1->Patches,sizeof(struct Patches));
      } /* Can't crete dummy patch */
      FreeMem(Patch,sizeof(struct Patches));
    } /* Can't crete real patch */
    FreeMem(HeadPtr1,sizeof(struct LibHeader));
  } /* Can't crete header */

Done:
  ReleaseSemaphore(&SetFuncSemaphore);

  return OldFunc;
}

struct LibHeader *MakeHead(struct Library *Library)
{
  struct LibHeader *Head;

  if (Head = AllocMem(sizeof(struct LibHeader),MEMF_CLEAR))
    Head->LibBase = Library;
  return Head;
}

struct Patches *MakePatch(APTR Offset, struct Patches *Next, LONG NewFunc)
{
  struct Patches *Patch;

  if (Patch = AllocMem(sizeof(struct Patches),MEMF_CLEAR)) {
    Patch->JMP = 0x4ef9; /* JMP opcode */
    Patch->NewFunc = NewFunc;
    Patch->Offset = (WORD)Offset;
    Patch->NextPatch = Next;
  }
  return Patch;
}
