/*
 * ToolAlias
 * 
 * Commodity that patches loadseg to substitute specific programs
 * with others. With settings window and hotkey. Patches may be
 * disabled and re-enabled via Commodities Exchange.
 * 
 * Martin W. Scott,  5 February 1993
 */
#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <exec/semaphores.h>
#include <dos/dos.h>
#include <libraries/commodities.h>
#include <string.h>
#include <clib/alib_protos.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/commodities.h>

#include "icon.h"
#include "list.h"
#include "file.h"
#include "window.h"

#define ASM __asm __saveds
#define REG(x) register __## x

/* Local protos */
BOOL OpenLibs(void);
VOID CloseLibs(void);
VOID _main(VOID);
LONG ProcessMsg(void);
BOOL InstallWedge(VOID);
BOOL RemoveWedge(VOID);
struct JumpTable *GetJumpTable(UBYTE *);

/* Libraries etc. */
extern struct WBStartup *WBenchMsg;
extern struct DosLibrary *DOSBase;
struct IntuitionBase *IntuitionBase;
struct Library *CxBase, *IconBase, *GadToolsBase;
struct GfxBase *GfxBase;

/* The number of 'replacement' functions */
#define NUMBEROFFUNCTIONS (1)

/* prototypes for the functions to be SetFunction()'ed. */
#define DEC_LVO(lvoname)   extern lvoname

/* declare stuff for standard dos one-parameter function */
/* UBYTE * in d1 */
#define DEC11_OLD(oldname) LONG (*ASM oldname) (REG(d1) UBYTE *, REG(a6) struct Library *base)
#define DEC11_NEW(newname) LONG ASM newname(REG(d1) UBYTE *, REG(a6) struct Library *base)
#define DEC11(name) DEC11_OLD(old## name); DEC11_NEW(new## name); DEC_LVO(LVO## name)

/* the patched functions themselves */
DEC11(LoadSeg);

/* Use a table and an array to make it a little more generic and easier to
 * add functions.
 */
struct LVOTable
{
    LONG lt_LVO;
    struct Library *lt_LibBase;
    ULONG lt_oldFunction;
    ULONG lt_newFunction;
};

struct LVOTable LVOArray[] =
{
    {&LVOLoadSeg, (struct Library *) & DOSBase, &oldLoadSeg, &newLoadSeg}
};

struct JumpTable
{
    struct SignalSemaphore jt_Semaphore;
    UWORD pad_word;
    struct Task *jt_Owner;
    UBYTE jt_Function[NUMBEROFFUNCTIONS * 6];
};

/* Strings */
/* The name this JumpTable/Semaphore will get. */
static UBYTE *JTName = "ToolAlias-JT";
static UBYTE *VersTag = "\0$VER: ToolAlias 1.02";

/* Commodity globals */
CxObj *broker, *popobj;
struct MsgPort *broker_mp;

struct NewBroker newbroker =
{
    NB_VERSION,
    "ToolAlias",
    "ToolAlias 1.02",
    "Name substitution facility",
    NBU_UNIQUE | NBU_NOTIFY,
    COF_SHOW_HIDE
};

#define EVT_POPKEY 1L
ULONG cxsigflag, wndsigflag;
UBYTE popstr[100];
int wedged = 1;			/* initially, wedges will be in place */

/* close what we opened */
VOID
CloseLibs()
{
    if (IntuitionBase)
	CloseLibrary(IntuitionBase);
    if (IconBase)
	CloseLibrary(IconBase);
    if (GfxBase)
	CloseLibrary(GfxBase);
    if (GadToolsBase)
	CloseLibrary(GadToolsBase);
    if (CxBase)
	CloseLibrary(CxBase);
}

/* open libraries, devices that we need */
BOOL
OpenLibs()
{
    if ((IntuitionBase = (void *) OpenLibrary("intuition.library", 37L)) &&
	(IconBase = (void *) OpenLibrary("icon.library", 37L)) &&
	(GfxBase = (void *) OpenLibrary("graphics.library", 37L)) &&
	(GadToolsBase = OpenLibrary("gadtools.library", 37L)) &&
	(CxBase = OpenLibrary("commodities.library", 37L)))
    {
	return TRUE;
    }
    CloseLibs();
    return FALSE;
}

/* pop up a requester */
void
EasyEasyRequest(char *str)
{
    struct EasyStruct es;

    es.es_StructSize = sizeof(struct EasyStruct);

    es.es_Flags = 0L;
    es.es_Title = "ToolAlias Message";
    es.es_TextFormat = str;
    es.es_GadgetFormat = "OK";
    EasyRequestArgs(NULL, &es, NULL, NULL);
}

#define Msg(s) EasyEasyRequest(s)

VOID
_main(VOID)
{
    if (OpenLibs())
    {
	if (get_config(DEF_CONFIG))
	{
	    if (broker_mp = CreateMsgPort())
	    {
		GetOurIcon(WBenchMsg);
		newbroker.nb_Pri = (BYTE) TTInt("CX_PRIORITY", 0);
		strcpy(popstr, TTString("CX_POPKEY", "control alt t"));

		newbroker.nb_Port = broker_mp;
		if (broker = CxBroker(&newbroker, NULL))
		{
		    if (popobj = HotKey(popstr, broker_mp, EVT_POPKEY))
		    {
			AttachCxObj(broker, popobj);
			cxsigflag = 1L << broker_mp->mp_SigBit;
			ActivateCxObj(broker, 1L);

			if (TTBool("CX_POPUP", TRUE))
			    ShowWindow();
			FreeOurIcon();
			if (InstallWedge())
			    while (ProcessMsg());
			else
			    Msg("Couldn't install patches");

			HideWindow();
			if (wedged)
			    RemoveWedge();
		    }
		    else
			Msg("Couldn't create HotKey");
		    DeleteCxObjAll(broker);
		}
		FreeOurIcon();	/* can be called twice avec no probs */

		DeleteMsgPort(broker_mp);

	    }
	    else
		Msg("Couldn't create port");

	    free_list();	/* free memory */
	}
	CloseLibs();
    }
}

LONG
ProcessMsg(void)
{
    CxMsg *msg;
    ULONG sigrcvd, msgid, msgtype;
    LONG returnvalue = 1L;
    LONG gui_rc;

    /* wait for something to happen */
    sigrcvd = Wait(SIGBREAKF_CTRL_C | cxsigflag | wndsigflag);

    if (sigrcvd & wndsigflag)	/* settings change */
	if (gui_rc = HandleIDCMP())
	{
	    returnvalue = 0;
	    if (gui_rc == GUI_ABEND)
		Msg("No memory - terminating");
	}

    /* process any messages */
    while (msg = (CxMsg *) GetMsg(broker_mp))
    {
	/* Extract necessary information from the CxMessage and return it */
	msgid = CxMsgID(msg);
	msgtype = CxMsgType(msg);
	ReplyMsg((struct Message *) msg);

	if (msgtype == CXM_IEVENT)	/* popkey */
	{
	    if (msgid == EVT_POPKEY)
		ShowWindow();
	}
	else if (msgtype == CXM_COMMAND)	/* std commodity message */
	{
	    switch (msgid)
	    {
	    case CXCMD_UNIQUE:
	    case CXCMD_APPEAR:
		ShowWindow();	/* check error return? */
		break;

	    case CXCMD_DISAPPEAR:
		HideWindow();
		break;

	    case CXCMD_DISABLE:
		if (wedged)
		{
		    RemoveWedge();
		    wedged = 0;
		}
		ActivateCxObj(broker, 0L);
		break;

	    case CXCMD_ENABLE:
		if (!wedged)
		{
		    InstallWedge();
		    wedged = 1;
		}
		ActivateCxObj(broker, 1L);
		break;

	    case CXCMD_KILL:
		returnvalue = 0L;
		break;
	    }
	}
    }

    /* Test to see if user tried to break */
    if (sigrcvd & SIGBREAKF_CTRL_C)
	returnvalue = 0L;

    return (returnvalue);
}

BOOL
InstallWedge(VOID)
{
    struct JumpTable *jumptable;
    ULONG *addressptr;
    UCOUNT i, j;

    Forbid();

    /* Get pointer to JumpTable. Create it if necessary */
    if (jumptable = GetJumpTable(JTName))
    {
	/* Try to get exclusive lock on semaphore, in case it already existed. */
	if (AttemptSemaphore((struct SignalSemaphore *) jumptable))
	{
	    /* Make sure nobody else has function addresses in the jumptable */
	    if (jumptable->jt_Owner == NULL)
	    {
		jumptable->jt_Owner = FindTask(0);
		/* Don't want to disable any longer than necessary */
		Disable();

		for (i = 2, j = 0; i < NUMBEROFFUNCTIONS * 6; i += 6, j++)
		{
		    /* Replace addresses in the jumptable with my own. */
		    addressptr = (ULONG *) ((UBYTE *) jumptable->jt_Function + i);
		    (*((ULONG *) LVOArray[j].lt_oldFunction)) = (ULONG) * addressptr;
		    *addressptr = (ULONG) LVOArray[j].lt_newFunction;
		}
		CacheClearU();
		Enable();
	    }
	    else
		Msg("Already running.\n");
	    ReleaseSemaphore((struct SignalSemaphore *) jumptable);
	}
	else
	    Msg("Can't lock table.\n");
    }
    else
	Msg("Can't create jumptable\n");
    Permit();
    return ((BOOL) jumptable);
}

BOOL
RemoveWedge(VOID)
{
    struct JumpTable *jumptable;
    ULONG *addressptr;
    UCOUNT i, j;

    Forbid();

    if (jumptable = GetJumpTable(JTName))
    {
	/* Check if this task owns this jumptable */
	if (jumptable->jt_Owner == FindTask(0))
	{

	    /* Get the semaphore exclusively.
             * Depending on what got SetFunction()'ed this could take some time.
             * Also note that shared locks are used to indicate the code is
             * being executed and that shared locks can jump ahead of queue'ed
             * exclusive locks, adding to the waittime.
             */
	    ObtainSemaphore((struct SignalSemaphore *) jumptable);

	    Disable();

	    /* Restore old pointers in jumptable */

	    for (i = 2, j = 0; i < NUMBEROFFUNCTIONS * 6; i += 6, j++)
	    {
		addressptr = (ULONG *) ((UBYTE *) jumptable->jt_Function + i);
		*addressptr = (*((ULONG *) LVOArray[j].lt_oldFunction));
	    }
	    CacheClearU();
	    Enable();

	    jumptable->jt_Owner = NULL;
	    ReleaseSemaphore((struct SignalSemaphore *) jumptable);
	}
    }
    Permit();

    return (TRUE);
}

struct JumpTable *
GetJumpTable(UBYTE * name)
{
    struct JumpTable *jumptable;
    ULONG *addressptr;
    UWORD *jmpinstr;
    UBYTE *jtname;
    UCOUNT i, j;

    /* Not really necessary to forbid again, just to indicate that I don't
     * want another task to create the semaphore while I'm trying to do the
     * same. Here GetJumpTable() is only called from InstallWedge(), so it
     * will just bump the forbid count.
     */
    Forbid();

    if (!(jumptable = (struct JumpTable *) FindSemaphore(name)))
    {
	if (jumptable = AllocMem(sizeof(struct JumpTable), MEMF_PUBLIC | MEMF_CLEAR))
	{
	    if (jtname = AllocMem(strlen(name) + 1, MEMF_PUBLIC | MEMF_CLEAR))
	    {

		for (i = 0, j = 0; i < NUMBEROFFUNCTIONS * 6; i += 6, j++)
		{
		    jmpinstr = (UWORD *) ((UBYTE *) jumptable->jt_Function + i);
		    *jmpinstr = 0x4EF9;

		    addressptr = (ULONG *) ((UBYTE *) jumptable->jt_Function + i + 2);
		    *addressptr = (ULONG) SetFunction(
		    (struct Library *) (*((ULONG *) LVOArray[j].lt_LibBase)),
							 LVOArray[j].lt_LVO,
			   (VOID *) ((UBYTE *) jumptable->jt_Function + i));
		}

		jumptable->jt_Semaphore.ss_Link.ln_Pri = 0;

		strcpy(jtname, name);
		jumptable->jt_Semaphore.ss_Link.ln_Name = jtname;
		AddSemaphore((struct SignalSemaphore *) jumptable);
	    }
	    else
	    {
		FreeMem(jumptable, sizeof(struct JumpTable));

		jumptable = NULL;
	    }
	}
    }
    Permit();

    /* If succeeded, you now have a jumptable which entries point to the original
     * library functions. If another task SetFunction()'ed one or more of those
     * already, that task can never go away anymore.
     */
    return (jumptable);
}


/******************************************************************************/
/*******************                                 **************************/
/*******************   NEW DOS ROUTINES START HERE   **************************/
/*******************                                 **************************/
/******************************************************************************/

LONG ASM
newLoadSeg(REG(d1) UBYTE * name, REG(a6) struct Library * base)
{
    struct SignalSemaphore *jt;
    char *newname;
    LONG rc = 0;

    if (jt = FindSemaphore(JTName))
    {
	ObtainSemaphoreShared(jt);

	if (name && (newname = find_tool(name)))
	    rc = oldLoadSeg(newname, DOSBase);
	else
	    rc = oldLoadSeg(name, DOSBase);

	ReleaseSemaphore(jt);
    }
    return rc;
}
