//
// YourFault, 1995 Lee Kindness.
// Patches the Fault() and PrintFault() functions in dos.library so that
// they return a user defined strings! (ie. Bananna in diskdrive :)
//
// Based on the source of WBTitle by Mark Thomas -- Thanks
//
// This source is in the public domain, do with it as you wish...
//

// ANSI headers
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

// Amiga headers
#include <exec/types.h>
#include <exec/memory.h>
#include <intuition/intuition.h>
#include <dos/dos.h>

// Protos
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/dos.h>

// Amiga.lib
#include <clib/alib_protos.h>

// The Fault function offsets
#define FaultOffset -468
#define PrintFaultOffset -474

// File from which the strings are to read from
#define ERRSFILENAME "S:FaultStrings"

enum {
	JMPINSTR = 0x4ef9
};

typedef struct JmpEntry {
	UWORD Instr;
	APTR Func;
} JmpEntry;

// ErrorStringNode
typedef struct ESNode {
	struct ESNode *es_Succ;
	struct ESNode *es_Pred;
	UBYTE es_Type;
	BYTE es_Pri;
	STRPTR es_String;
	LONG es_Number;
} ESNode;

// Prototypes
static BOOL FaultReplace(void);
static void FaultRestore(void);
static BOOL PrintFaultReplace(void);
static void PrintFaultRestore(void);
static struct List *LoadErrStrings(STRPTR);
static LONG __asm new_Fault(register __d1 LONG,
                            register __d2 STRPTR,
                            register __d3 STRPTR,
                            register __d4 LONG,
                            register __a6 struct Library *);
static BOOL __asm new_PrintFault(register __d1 LONG,
                                 register __d2 STRPTR,
                                 register __a6 struct Library *);

// Local variables
static char verstring[] = "$VER: YourFault 1.0 " __AMIGADATE__;
static char port_name[] = "YourFault";
static LONG __asm (*old_Fault)(register __d1 LONG,
                               register __d2 STRPTR,
                               register __d3 STRPTR,
                               register __d4 LONG,
                               register __a6 struct Library *);
static BOOL __asm (*old_PrintFault)(register __d1 LONG,
                                    register __d2 STRPTR,
                                    register __a6 struct Library *);
static JmpEntry *FaultEntry;
static JmpEntry *PrintFaultEntry;
static struct List *codes;
static struct Remember *grk;

// *************************************************************************
//
// The main method for replacing an Amiga OS function as safe as
// possible is to place the function with a jump table that is
// allocated.  While the function is replaced, the jump table simply
// jumps to my routine:
//
// jmp  _new_Fault
//
// When the user asks the program to quit, we can't simply put the
// pointer back that SetFunction() gives us since someone else might
// have replaced the function.  So, we first see if the pointer we
// get back points to the out jump table.  If so, then we _can_ put
// the pointer back like normal (no one has replaced the function
// while we has it replaced).  But if the pointer isn't mine, then
// we have to replace the jump table function pointer to the old
// function pointer:
//
// jmp  _old_Fault
//
// Finally, we only deallocate the jump table _if_ we did not have
// to change the jump table.
//

main(void)
{
	struct MsgPort *port;
	
	if (DOSBase->dl_lib.lib_Version > 36) {

		grk = NULL;
		// FindPort() Forbid()
		Forbid();

		port = FindPort(port_name);
		if (port) {
			struct MsgPort *reply_port;
			
			// We are already active, send a msg to the other 
			// occurance telling it to quit
	
			// Create a reply port
			reply_port = CreateMsgPort();
			if (reply_port) {
				struct Message msg;
	
				// Set fields in message structure
				msg.mn_ReplyPort = reply_port;
				msg.mn_Length = sizeof(struct Message);
	
				// Send the message
				PutMsg(port, &msg);
	
				// Finished with port, so stop FindPort() Forbid()
				Permit();
	
				// Wait for a reply
				do {
					WaitPort(reply_port);
				} while (GetMsg(reply_port) == NULL);
	
				// Clear and Delete reply_port Forbid()
				Forbid();
	
				// Clear any messages
				while (GetMsg(reply_port));
	
				// Delete the reply port
				DeleteMsgPort(reply_port);
	
				// Clear and Delete reply_port stop Forbid()
				Permit();
			} else {
				// Finished with port, so stop FindPort() Forbid()
				Permit();
			}
		} else if (port = CreateMsgPort()) {
			struct Message *msg;
	
			// Finished with port, so stop FindPort() Forbid()
			Permit();
	
			// Setup quitting port
			port->mp_Node.ln_Name = port_name;
			port->mp_Node.ln_Pri = -120;
	
			// Add quitting port to public list
			AddPort(port);
	
			// Load strings
			codes = LoadErrStrings(ERRSFILENAME);
			if (codes) {
				// Attempt to replace function
				if (FaultReplace()) {
					if (PrintFaultReplace()) {
						ULONG sig, sret;
						BOOL finished;
						finished = FALSE;
						sig = 1 << port->mp_SigBit;
						// Wait for someone to signal me to quit or CTRL-C
						do {
							sret = Wait(SIGBREAKF_CTRL_C | sig);
							if (sret & sig) {
								// signaled
								msg = GetMsg(port);
								if (msg) {
									ReplyMsg(msg);
									finished = TRUE;
								}
							}
							if (sret & SIGBREAKF_CTRL_C)
								finished = TRUE;
						} while (!finished);
		
						// Restore function
						PrintFaultRestore();
					}
					// Restore function
					FaultRestore();
				}
			} else
				DisplayBeep(NULL);
	
			// Remove port from public access
			RemPort(port);
	
			// Clear and Delete port Forbid()
			Forbid();
	
			// Clear the port of messages
			while (msg = GetMsg(port)) {
				ReplyMsg(msg);
			}
	
			// Closedown quitting port
			DeleteMsgPort(port);
	
			// Clear and Delete port stop Forbid()
			Permit();
		}
		FreeRemember(&grk, TRUE);
	}
}

// *************************************************************************

static struct List *LoadErrStrings(STRPTR fname)
{
	BPTR f;
	char *sptr, *s, *buf;
	struct List *l;
	ESNode *esn;
	
	l = NULL;
	// alloc buffer
	buf = AllocVec(180, MEMF_CLEAR);
	if (buf) {
		// open the file
		f = Open(fname, MODE_OLDFILE);
		if (f) {
			// alloc the list
			l = AllocRemember(&grk, sizeof(struct List), MEMF_CLEAR);
			if (l) {
				// initilise the list
				NewList(l);
				// Parse the file...
				s = FGets(f, buf, 180);
				while (s) {
					// is it a comment?
					if (s[0] != ';') {
						// no, alloc node
						esn = AllocRemember(&grk, sizeof(ESNode), MEMF_CLEAR);
						if (esn) {
							// convert integer at start of string
							esn->es_Number = atol(buf);
							sptr = strchr(buf, ':');
							if (sptr) {
								sptr++;
								// remove 0x0A
								sptr[strlen(sptr)-1] = 0;
								// strip blanks
								sptr = stpblk(sptr);
								// alloc mem
								esn->es_String = AllocRemember(&grk, (strlen(sptr)+1), MEMF_CLEAR);
								if (esn->es_String) {
									// copy string
									strcpy(esn->es_String, sptr);
									// add to list
									AddTail(l, (struct Node *)esn);
								}
							}
						}
					}
					s = FGets(f, buf, 180);
				}
			}
			Close(f);
		}
		FreeVec(buf);
	}
	return(l);
}


// *************************************************************************

static BOOL FaultReplace(void)
{
	// Allocate the jump table
	FaultEntry = AllocMem(sizeof(JmpEntry), 0);
	if (FaultEntry) {
		// Replacement Forbid()
		Forbid();

		// Replace the function with pointer to jump table
		old_Fault = SetFunction((struct Library *)DOSBase,
											FaultOffset, (ULONG (*)())FaultEntry);

		// Setup the jump table
		FaultEntry->Instr = JMPINSTR;
		FaultEntry->Func = new_Fault;

		// Clear the cpu's cache so the execution cache is valid
		CacheClearU();

		// Stop the replacement Forbid()
		Permit();

		return TRUE;
	} else {
		return FALSE;
	}
}

// *************************************************************************

static void FaultRestore(void)
{
	BOOL my_table;
	ULONG (*func)();

	// Fix back Forbid()
	Forbid();

	// Put old pointer back and get current pointer at same time
	func = SetFunction((struct Library *)DOSBase, FaultOffset,
							(ULONG (*)())old_Fault);

	// Check to see if the pointer we get back is ours
	if ((JmpEntry *)func != FaultEntry) {
		// If not, leave jump table in place
		my_table = FALSE;
		SetFunction((struct Library *)DOSBase, FaultOffset,
					func);
		FaultEntry->Func = old_Fault;
	} else {
		// If so, free the jump table
		my_table = TRUE;
		FreeMem(FaultEntry, sizeof(JmpEntry));
	}

	// Clear the cpu's cache so the execution cache is valid
	CacheClearU();

	// Stop fix back Forbid()
	Permit();

	// Let the user know if the jump table couldn't be freed
	if (!my_table) {
		DisplayBeep(NULL);
	}

	// Wait 5 seconds to try and guarantee that all tasks have
	// finished executing inside my replacement function before
	// quitting.  There's no real way to guarantee, though.
	Delay(250);
}

// *************************************************************************

static BOOL PrintFaultReplace(void)
{
	// Allocate the jump table
	PrintFaultEntry = AllocMem(sizeof(JmpEntry), 0);
	if (PrintFaultEntry) {
		// Replacement Forbid()
		Forbid();

		// Replace the function with pointer to jump table
		old_PrintFault = SetFunction((struct Library *)DOSBase,
											PrintFaultOffset, (ULONG (*)())PrintFaultEntry);

		// Setup the jump table
		PrintFaultEntry->Instr = JMPINSTR;
		PrintFaultEntry->Func = new_PrintFault;

		// Clear the cpu's cache so the execution cache is valid
		CacheClearU();

		// Stop the replacement Forbid()
		Permit();

		return TRUE;
	} else {
		return FALSE;
	}
}

// *************************************************************************

static void PrintFaultRestore(void)
{
	BOOL my_table;
	ULONG (*func)();

	// Fix back Forbid()
	Forbid();

	// Put old pointer back and get current pointer at same time
	func = SetFunction((struct Library *)DOSBase, PrintFaultOffset,
							(ULONG (*)())old_PrintFault);

	// Check to see if the pointer we get back is ours
	if ((JmpEntry *)func != PrintFaultEntry) {
		// If not, leave jump table in place
		my_table = FALSE;
		SetFunction((struct Library *)DOSBase, PrintFaultOffset,
					func);
		PrintFaultEntry->Func = old_PrintFault;
	} else {
		// If so, free the jump table
		my_table = TRUE;
		FreeMem(PrintFaultEntry, sizeof(JmpEntry));
	}

	// Clear the cpu's cache so the execution cache is valid
	CacheClearU();

	// Stop fix back Forbid()
	Permit();

	// Let the user know if the jump table couldn't be freed
	if (!my_table) {
		DisplayBeep(NULL);
	}

	// Wait 5 seconds to try and guarantee that all tasks have
	// finished executing inside my replacement function before
	// quitting.  There's no real way to guarantee, though.
	Delay(250);
}

// *************************************************************************

static LONG __saveds __asm new_Fault(register __d1 LONG code,
                                     register __d2 STRPTR header,
                                     register __d3 STRPTR buffer,
                                     register __d4 LONG len,
                                     register __a6 struct Library *lib)
{
	ESNode *esn, *foundnode;
	LONG ret;

	foundnode = NULL;
	// search for a matching code in the list
	esn = (ESNode *)codes->lh_Head;
	while (esn->es_Succ) {
		if (esn->es_Number == code)
			foundnode = esn;
		esn = esn->es_Succ;
	}
	if (foundnode) {
		if (header) {
			// copy header to buffer
			strcpy(buffer, header);
			len -= strlen(header);
			strcpy(buffer, ": ");
			len -= 2;
		}
		// copy the error string
		strncat(buffer, foundnode->es_String, len);
		ret = 1;
	} else
		// call old function
		ret = old_Fault(code, header, buffer, len, lib);
	return(ret);
}

// *************************************************************************

static BOOL __saveds __asm new_PrintFault(register __d1 LONG code,
                                          register __d2 STRPTR header,
                                          register __a6 struct Library *lib)
{
	char *buffer;
	struct Process *proc;
	BOOL rc;
	
	rc = FALSE;
	// alloc buffer
	buffer = AllocVec(180, MEMF_CLEAR);
	if (buffer) {
		// find the calling process
		proc = (struct Process *)FindTask(NULL);
		// call Fault()
		if (new_Fault(code, header, buffer, 180, lib)) {
			// output results
			FPuts(proc->pr_COS, buffer);
			FPuts(proc->pr_COS, "\n");
			rc = TRUE;
		}
		FreeVec(buffer);
	}
	return(rc);
}

// *************************************************************************

