/* pp_patcher v1.3
**
**     PP_Patcher  is  a  small  utility  which enables programs to get at
** PowerPacked  datafiles,  as  if  these  were  normal  files.   This  is
** acomplished  by  patching  some  vital  DOS functions, thus redirecting
** calls to these to my own internal routines.  The overall effect is that
** PowerPacked  datafiles  appear as normal files.  You can TYPE them in a
** CLI window, or bring them directly into your favourite editor.  Another
** good  way  to  use  this  proggy is if you crunch your workbench icons.
** Workbench  will never know the difference, but it reduces the diskspace
** occupied  by  icons  by  some  65-70% (usually).  Of course, there is a
** nominal  performance reduction due to the fact that we have to decrunch
** the  icons  before  passing them on to Workbench, but this doesn't seem
** too  annoying.   Especially not if you use optimized (B.A.D.) disks, or
** increase   the   size   of  the  diskbuffers  (using  the  CLI  command
** 'Addbuffers' or equivalent).
**
** For further info, read the DOC file.
**
** Compiles  under  Aztec V5.0 using large code/large data/32 bit integers
** Should do fine under Lattice, too, if you fix the #pragma's.
**
** Freeware 1991, Copyright (C) 1991 by Michael Berg
**					Sct. Peders Gade 24A, 2th
**					8900  Randers
**					DENMARK
*/

/* That's right -- ALL of these are necessary! */
#include <functions.h>
#include <exec/execbase.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <intuition/intuition.h>
#include <workbench/startup.h>
#include "ppbase.h"			/* enclosed in this directory */

/* Comment out the following line if you don't like autodetaching programs! */
#define DETACHED

/* Well, the compiler has the inline-code ability -- why not use it? */
#define strlen	_BUILTIN_strlen
#define strcpy	_BUILTIN_strcpy

/* Some defines which make the source code look pretty */
typedef struct PPBase PPBASE;

/* powerpacker.library related functions */
extern int ppLoadData(char *, int, int, UBYTE **, int *, int (*)());
#pragma amicall(PPBase, 0x1e, ppLoadData(a0,d0,d1,a1,a2,a3))

/* Open()-patch related functions */
extern void MakeOpen(void), RestOpen(void);
extern BPTR RealOpen(char *, int);
extern BPTR NewOpen(char *, int);
#pragma regcall(RealOpen(d1,d2))	/* All of these PRAGMAs are EXTREMELY */
#pragma regcall(NewOpen(d1,d2))		/* important !!!!!!!!!!		      */

/* Close()-patch related functions */
extern void MakeClose(void), RestClose(void);
extern void RealClose(BPTR);
extern void NewClose(BPTR);
#pragma regcall(RealClose(d1))
#pragma regcall(NewClose(d1))

/* Examine()-patch related functions */
extern void MakeExamine(void), RestExamine(void);
extern int RealExamine(BPTR, struct FileInfoBlock *);
extern int NewExamine(BPTR, struct FileInfoBlock *);
#pragma regcall(RealExamine(d1,d2))
#pragma regcall(NewExamine(d1,d2))

/* Write()-patch related functions */
extern void MakeWrite(void), RestWrite(void);
extern int RealWrite(BPTR, char *, int);
extern int NewWrite(BPTR, char *, int);
#pragma regcall(RealWrite(d1,d2,d3))
#pragma regcall(NewWrite(d1,d2,d3))

/* Define the maximum length of a filename and its path.
** 256 is, as far as I can tell, the AmigaDOS limit (due to BSTR's).
*/
#define MAXPATHLEN	256

/* Match tags for PowerPacked files */
#define PP20		(('P' << 24) + ('P' << 16) + ('2' << 8) + '0')
#define PX20		(('P' << 24) + ('X' << 16) + ('2' << 8) + '0')

/* One of these for each opened, powerpacked file, which has not yet been
** closed by the Open()'er.
*/
struct filenode
{
	struct MinNode mn;

	BPTR filehandle;
	char *new_filename, *orig_filename;
	short dirty;
};

/* One of these for each caller to NewOpen() */
struct caller
{
	struct MinNode mn;

	struct Task *tc;
};

/* For inter-process communication */
typedef struct
{
	struct Message Msg;
	/* Additional parameters will live here, eventually */
} MYMSG;

/* Global data */
struct MinList templist, callers;
struct Window *win;
struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;
struct PPBase *PPBase;
int patched, wbmode;

char temppath[MAXPATHLEN];

struct MsgPort *pp_port;
char *pp_portname = "pp.port";

#ifdef DETACHED

/* Exported to detach module */
int _stack = 4000;
int _priority = 5;
char *_procname = "Powerpacker Patcher";
long _BackGroundIO = 1;				/* We want stdio */
extern BPTR _Backstdout;

#else

#define _Backstdout Output()

#endif

/* Misc */
char GBANNER[] = "Powerpacker Patcher V1.1, Copyright (C) 1991, Michael Berg\n";
char WBBANNER[] = "Just double-click on my icon, or do an extended selection \
on a disk or a\ndrawer icon (this tells me where to put temporary files)\n";
char CLIBANNER[] = "Syntax: PP [<temppath>] (temppath defaults to RAM:)\n";
char DEFPATH[] = "RAM:";

void climsg(register char *what)
{
	if (_Backstdout)
		Write(_Backstdout,what,strlen(what));
}

void wbmsg(register char *what)
{
	register BPTR fh;

	if (fh = Open("CON:10/88/620/80/PP Notices:",MODE_NEWFILE))
	{
		char dummy;

		Write(fh,what,strlen(what));
		Write(fh,"Press <RETURN>\n",15);
		Read(fh,&dummy,1);
		Close(fh);
	}
}

/* Small puts() function (doesn't do a newline, though). If running from
** CLI, print the message in the CLI window. If running from WB, show the
** message in a small console window.
*/
void Say(register char *what)
{
	if (wbmode)
		wbmsg(what);
	else
#ifndef DETACHED
		climsg(what);
#else
	{
		register char *cr = "\r";

		climsg(cr);
		climsg(what);
	}
#endif
}

/* CreatePort() should exist in the standard library, but here it is, just
** in case your library lacks this function (it isn't in ROM)
*/
struct MsgPort *myCreatePort(register char *name, register pri)
{
	register UBYTE sigbit;
	register struct MsgPort *gotten;

	if
	(
		gotten = (struct MsgPort *)AllocMem
					   (
						sizeof(*gotten),
						MEMF_CLEAR | MEMF_PUBLIC
					   )
	)
	{
		if ((sigbit = AllocSignal(-1)) != -1)
		{
			gotten->mp_Node.ln_Name = name;
			gotten->mp_Node.ln_Pri  = pri;
			gotten->mp_Node.ln_Type = NT_MSGPORT;
			gotten->mp_Flags        = PA_SIGNAL;
			gotten->mp_SigBit       = sigbit;
			gotten->mp_SigTask      = FindTask(0);

			AddPort(gotten);

			return(gotten);
		}
		else
			FreeMem((char *)gotten,sizeof(*gotten));
	}

	return(NULL);
}

/* DeletePort is also supposed to be in your standard library. Here it is,
** just in case...
*/
void myDeletePort(register struct MsgPort *p)
{
	RemPort(p);
	FreeSignal(p->mp_SigBit);
	FreeMem((char *)p,sizeof(*p));
}

/* Universal termination code */
void die(char *errmsg)
{
	/* Print the (optional) error message */
	if (errmsg)
		Say(errmsg);

	/* Get rid of our port */
	if (pp_port)
		myDeletePort(pp_port);

	/* Restore the original DOS functions. You will not find these
	** functions in the sourcecode. They are generated by the assembler
	** macro DOSLibPatch -- see asmsup.c
	*/
	if (patched)
	{
		Forbid();
			RestOpen();
			RestClose();
			RestExamine();
			RestWrite();
		Permit();
	}

	/* Close libraries */
	if (PPBase) CloseLibrary(PPBase);

	/* Finally! */
	exit(errmsg? 20 : 0);
}

/* Open up all required libraries */
void openlibs()
{
	if (!(PPBase = (PPBASE *)OpenLibrary("powerpacker.library",0)))
		die("You need powerpacker.library V33+\n");
}

/* Install the DOS patches */
void installpatch()
{
	Forbid();
		MakeOpen();
		MakeClose();
		MakeExamine();
		MakeWrite();
	Permit();

	patched = 1;
}

/* Some Lists need to be initialized */
void initlist()
{
	NewList((struct List *)&templist);
	NewList((struct List *)&callers);
}

void doport()
{
	if (!(pp_port = myCreatePort(pp_portname,0)))
		die("Couldn't create a message port\n");
}

/* passargs passes messages to the 'PP' already running somewhere */
void passargs(register ac, register char **av)
{
	register MYMSG *m;

	if (m = (MYMSG *)AllocMem(sizeof(MYMSG),MEMF_CLEAR))
	{
		register struct Process *myself;

		myself = (struct Process *)FindTask(0);

		m->Msg.mn_Node.ln_Type	= NT_MESSAGE;
		m->Msg.mn_Length	= sizeof(MYMSG);
		m->Msg.mn_ReplyPort	= &myself->pr_MsgPort;

		/* Passing of future parameters go here */

		/* Tell him the bad news */
		PutMsg(pp_port,(struct Message *)m);

		/* Receiver will free the message (he MUST NOT reply to it!) */
	}
	else
		Say("Not enough memory for interprocess communication\n");
}

void finalizepath()
{
	register char c;

	c = temppath[strlen(temppath)-1];
	if (c != ':' && c != '/')
		strcat(temppath,"/");
}

void badstartup()
{
	die(wbmode? WBBANNER : CLIBANNER);
}

void checkokpath()
{
	register BPTR lock;
	register char *c;
	register allok = 0;
	char testdir[MAXPATHLEN];

	strcpy(testdir,temppath);
	c = testdir + strlen(testdir) - 1;
	if (*c == '/') *c = '\0';

	if (lock = Lock(testdir,ACCESS_READ))
	{
		register struct FileInfoBlock *fib;

		if (fib = AllocMem(sizeof(*fib),MEMF_CLEAR))
		{
			if (Examine(lock,fib))
				allok = (fib->fib_DirEntryType >= 0);

			FreeMem(fib,sizeof(*fib));
		}
		UnLock(lock);
	}

	if (!allok)
		die("Could not validate your path selection\n");
}

void buildfromlock(register BPTR lock, register char *name)
{
	register struct FileInfoBlock *fib;
	register BPTR olddir,newlock;
	char *fullname();

	olddir  = CurrentDir(lock);

	if (!(newlock = Lock(name,ACCESS_READ)))
	{
		CurrentDir(olddir);
		die("Could not get a lock on your specified PATH\n");
	}

	if (fib = AllocMem(sizeof(*fib),MEMF_CLEAR))
	{
		register retval;

		if (retval = Examine(newlock,fib))
			strcpy(temppath,fullname(newlock,fib));

		UnLock(newlock);
		CurrentDir(olddir);
		FreeMem(fib,sizeof(*fib));

		if (!retval)
			die("Cannot examine your PATH specification\n");
	}
	else
	{
		UnLock(newlock);
		die("Running low on memory\n");
	}
}

/* Executed when PP starts up the very first time */
void doinitargs(register ac, register char **av)
{
	register char c;
	register struct WBArg *arg;
	char debugstr[40];
	extern struct WBStartup *WBenchMsg;

	/* So far, the only thing you can tell PP is where to put all
	** the temporary files. This defaults to RAM: when no argument
	** is given. Workbench argument passing is fully supported.
	*/
	switch (ac)
	{
		case 0 : /* Workbench */
			 switch (WBenchMsg->sm_NumArgs)
			 {
			 	case 1 : strcpy(temppath,DEFPATH);
					 break;

				case 2 : arg = &WBenchMsg->sm_ArgList[1];
					 if (arg->wa_Lock)
					 	buildfromlock(arg->wa_Lock,arg->wa_Name);
					 else
						/* Should never happen */
					 	strcpy(temppath,arg->wa_Name);
					 break;

				default: badstartup();
			 }
			 break;

		case 1 : strcpy(temppath,DEFPATH);
			 break;

		case 2 : strcpy(temppath,av[1]);
			 break;

		default: badstartup();
	}

 	finalizepath();
	checkokpath();
}

/* Open up everything */
void openstuff(register ac, register char **av)
{
	if (pp_port = FindPort(pp_portname))
	{
		/* There's already a working copy of PP running somewhere.
		** Pass the arguments along to it, and then exit.
		*/
		passargs(ac,av);

		/* Don't let die() remove the port */
		pp_port = NULL;
		die(NULL);
	}

	/* This is the first time around. Install everything */
	doinitargs(ac,av);

	openlibs();
	initlist();
	installpatch();
	doport();

	/* Tell the world the good news */
	Say(GBANNER);
}

/* Add a new caller to NewOpen() to the list of callers */
addcaller(register struct Task *tc)
{
	register struct caller *memgot;

	if (memgot = AllocMem(sizeof(*memgot),0))
	{
		memgot->tc = tc;
		AddTail((struct List *)&callers, (struct Node *)memgot);
		return(1);
	}
	else
		return(0);
}

/* Add a file node to the list of files which we have created. These
** exist temporarily in 'temppath' and we need to get rid of these along
** the way, as they are Close()'d.
*/
addfilenode(register BPTR fn, register char *filename, register char *orig)
{
	register struct filenode *memgot;

	if
	(
		(memgot = AllocMem(sizeof(*memgot),MEMF_CLEAR))		 &&
		(memgot->new_filename  = AllocMem(strlen(filename)+1,0)) &&
		(memgot->orig_filename = AllocMem(strlen(orig)+1,0))
	)
	{
		memgot->filehandle = fn;
		strcpy(memgot->new_filename,filename);
		strcpy(memgot->orig_filename,orig);
		AddTail((struct List *)&templist, (struct Node *)memgot);
		return(1);
	}
	else
		return(0);
}

/* Find a filenode (keyed by its filehandle) */
struct filenode *findfilenode(register BPTR fn)
{
	register struct filenode *search;

	/* Linear search is employed */
	for
	(
		search = (struct filenode *)(templist.mlh_Head);
		search->mn.mln_Succ;
		search = (struct filenode *)(search->mn.mln_Succ)
	)
		if (search->filehandle == fn)
			return(search);

	return(NULL);
}

/* Find a caller on the callers list */
struct caller *findcaller(register struct Task *tc)
{
	register struct caller *search;

	for
	(
		search = (struct caller *)(callers.mlh_Head);
		search->mn.mln_Succ;
		search = (struct caller *)(search->mn.mln_Succ)
	)
		if (search->tc == tc)
			return(search);

	return(NULL);
}

/* This baby builds a complete filename (including a path) from a BCPL
** pointer to a filehandle. Optimizations are most welcome. All those
** ParentDir() calls take a LONG time.
*/
char *fullname(register BPTR lock, register struct FileInfoBlock *fib)
{
	static char pathandfile[MAXPATHLEN];
	char tmp[MAXPATHLEN];
	register BPTR parentlock, unlocklock;
	register char *co;

	strcpy(pathandfile,fib->fib_FileName);

	parentlock = lock;
	unlocklock = (BPTR)0;

	while (parentlock = ParentDir(parentlock))
	{
		if (unlocklock)
			UnLock(unlocklock);

		if (patched? RealExamine(parentlock,fib) : Examine(parentlock,fib))
		{
			strcpy(tmp,fib->fib_FileName);
			strcat(tmp,"/");
			strcat(tmp,pathandfile);
			strcpy(pathandfile,tmp);
		}
		else
		{
			UnLock(parentlock);
			return(NULL);
		}

		unlocklock = parentlock;
	}

	if (unlocklock)
	{
		UnLock(unlocklock);

		if (co = (char *)index(pathandfile,'/'))
			*co = ':';
	}
	else
		strcat(pathandfile,":");

	/* This fixes a bug in the old RAM disk */
	if (!strcmp(pathandfile,":"))
		strcpy(pathandfile,"RAM:");

	return(pathandfile);
}

/* Is a filename really a file? We need to know this, because it is
** rediculous to try to read in the PowerPacker matchtag from something
** like CON:0/0/544/23/ConWindow.
*/
reallyfile(register char *filename)
{
	/* Don't like to hard-wire it like this, but it seems to
	** be the only realistic approach. Don't worry, you can't
	** do an ASSIGN to any of these. If you could, we would have
	** to check all volumenodes on the DeviceList stored in
	** the RootNode of the DosLibrary. (Got that?!)
	*/
	static char *duds[] =
	{
		"NIL:",	"CON:",
		"RAW:",	"PRT:",
		"PAR:",	"SER:"
	};

	register short i;

	/* A simple, linear search is employed */
	for (i = 0; i < sizeof(duds)/sizeof(char *); i++)
	{
		register short foundit;
		register char *cmp, *cmp2;
		register short j;

		cmp  = duds[i];
		cmp2 = filename;

		for (foundit = 1, j = 0; j < 4; j++)
		{
			if (toupper(*cmp++) != toupper(*cmp2++))
			{
				foundit = 0;
				break;
			}
		}

		if (foundit)
			return(0);
	}

	/* One last check, just to be sure */
	if (!strcmp(filename,"*"))
		return(0);

	return(1);
}

/* When somebody opens a PP file, we decrunch it into a temporary file
** and return a filehandle to that file. When the caller closes the file
** (which it thinks is the original disk file), it will really be closing
** the temporary file. This is a good chance for us to get rid of it,
** so that the temporary directory won't get crowded in time. HOWEVER!
** If the caller has written new data into the file, we have to rewrite the
** temporary file over the original (disk) file. flushout() does exactly
** that.
*/
void flushout(register struct filenode *fn)
{
	register BPTR orighandle;

	/* First of all, we have to open the original file. We're in
	** trouble if this is not possible...
	*/
	if (orighandle = RealOpen(fn->orig_filename, MODE_NEWFILE))
	{
		char buffer[2048];		/* Should suffice */
		register short readlen;

		Seek(fn->filehandle, 0, -1);

		do
		{
			readlen = Read(fn->filehandle, buffer, 2048);
			RealWrite(orighandle, buffer, readlen);
		}
			while (readlen == 2048);

		RealClose(orighandle);
	}
}

/* Look for a powerpacker matchtag at the beginning of a file. Returns TRUE
** if the file was a powerpacker datafile.
*/
isppfile(register BPTR fh)
{
	int ppmatchtag;

	/* This function leaves the file position ptr. at 0 for non-PP files */
	Seek(fh,0,-1);
	Read(fh,(char *)&ppmatchtag,sizeof(int));

	if (ppmatchtag == PP20 || ppmatchtag == PX20)
		return(1);
	else
	{
		Seek(fh,0,-1);
		return(0);
	}
}

/* This is the new Open() functions. All future calls to the DOS Open()
** function will be rerouted through here.
*/
BPTR NewOpen(register char *filename, register mode)
{
	UBYTE *memgot;
	int filelen;
	register BPTR tempfh;
	register struct Task *thistask;
	register struct caller *thiscaller;

	tempfh = RealOpen(filename,mode);

	/* We only deal with a few of the incoming calls:
	**
	** 1) Files which CAN in fact be opened
	**
	** 2) We can't do anything about new files
	**
	** 3) Equally, we don't care about CON: or NIL: file open requests
	**
	** 4) We don't care about non-crunched files
	**
	** 5) If we have seen the calling task before, the one who is making
	**    the request must be ppLoadData. It is absolutely vital that
	**    we forward this request to the original DOS code. Otherwise
	**    we would end up in an infinite (recursive) loop, with ppLoadData
	**    calling NewOpen calling ppLoadData ...
	**
	** 6) If we cannot add a caller to the list of callers (see 3), we
	**    ignore the call. If we miss a few in a low memory situation,
	**    so be it.
	*/

	if
	(
		!tempfh				   ||
		mode == MODE_NEWFILE		   ||
		!reallyfile(filename)		   ||
		!isppfile(tempfh)		   ||
		findcaller(thistask = FindTask(0)) ||
		!addcaller(thistask)
	)
		return(tempfh);

	/* We won't be needing the original file handle anymore */
	RealClose(tempfh);
	tempfh = (BPTR)0;

	/* Now, ask ppLoadData to bring in the file */
	if (!ppLoadData(filename,DECR_NONE,0,&memgot,&filelen,(int (*)())0))
	{
		char filnambuf[MAXPATHLEN];
		register char *t, *m;

		/* Generate a name for the temporary file */
		t = filename;
		if (m = (char *)index(t,':')) t = m+1;
		while (m = (char *)index(t,'/')) t = m+1;

		strcpy(filnambuf,temppath);
		strcat(filnambuf,t);
		strcat(filnambuf,".tmp");

		/* We have to ensure that the name is unique on 'temppath' */
		while (tempfh = RealOpen(filnambuf,MODE_OLDFILE))
		{
			char *xtra = "?";

			/* Pad the name with random characters. This
			** should do the trick
			*/
			RealClose(tempfh);
			*xtra = 'A' + (rand() % 26);
			strcat(filnambuf,xtra);
		}

		/* Now, open the temporary file and flush data we loaded into
		** this file.
		*/
		if (tempfh = RealOpen(filnambuf,MODE_NEWFILE))
		{
			/* Remember that WE created that file */
			if (!addfilenode(tempfh,filnambuf,filename))
			{
				/* Couln't do it. Simulate a "Can't open
				** file" from the real Open().
				*/
				RealClose(tempfh);
				DeleteFile(filnambuf);
				tempfh = (BPTR)0;
			}
			else
			{
				/* Flush out the file. Probably should do
				** a check on RealWrite... Oh well.
				*/
				RealWrite(tempfh,(char *)memgot,filelen);
				Seek(tempfh,0,-1);
			}
		}
		/* Housekeeping */
		FreeMem(memgot,filelen);
	}

	/* We no longer have to worry about this caller */
	thiscaller = findcaller(thistask);
	Remove((struct Node *)thiscaller);
	FreeMem(thiscaller, sizeof(*thiscaller));

	/* Return a filehandle to the file */
	return(tempfh);
}

/* Yep! A new Write() function. We have to know if a process has updated
** the (substitute) file we created for it. If true, mark the file as
** being "dirty", so that we can later save it over the original PP file.
*/
int NewWrite(register BPTR filehandle, register char *buffer, int length)
{
	register struct filenode *fn;
	register wrtret;

	wrtret = RealWrite(filehandle, buffer, length);

	if (fn = findfilenode(filehandle))
		fn->dirty = 1;

	return(wrtret);
}

/* A new Close() function. It removes non-dirty, temporary files from
** 'temppath', and it keeps track of which files have to be updated back
** onto disk.
*/
void NewClose(register BPTR filehandle)
{
	register struct filenode *fn;

	if (fn = findfilenode(filehandle))
	{
		if (fn->dirty)
			flushout(fn);

		RealClose(filehandle);
		DeleteFile(fn->new_filename);
		Remove((struct Node *)fn);
		FreeMem(fn,sizeof(*fn));
	}
	else
		RealClose(filehandle);
}

/* A new Examine() function. Often, programs examine a file before opening
** it. This way, they can allocate just enough memory to hold the entire
** file. However, we have to correct Examine() calls to PowerPacked files,
** so that the correct amount of memory will be allocated by the caller.
*/
int NewExamine(BPTR lock, struct FileInfoBlock *fib)
{
	int decrunchinfo;
	register examinereturn;
	register BPTR tmpfh;
	struct FileInfoBlock fib_backup;

	/* Start off by examining the lock */
	if (!(examinereturn = RealExamine(lock,fib)))
		return(0);

	/* If it's a directory, or if it's pp_LoadData, never mind */
	if (fib->fib_DirEntryType >= 0 || findcaller(FindTask(0)))
		return(examinereturn);

	/* The lock target was a simple file. Check to see if it's a
	** PP file
	*/
	fib_backup = *fib;
	tmpfh	   = RealOpen(fullname(lock,fib),MODE_OLDFILE);
	*fib	   = fib_backup;

	if (!tmpfh)
		return(examinereturn);

	if (isppfile(tmpfh))
	{
		/* It was. Examine decrunchinfo to get at the original
		** filesize, so that programs trying to allocate enough
		** memory to hold a certain file will get the correct
		** filesize (which is, of corz, size of the decrunched
		** file!)
		*/
		Seek(tmpfh,-4,1);
		Read(tmpfh,(char *)&decrunchinfo,sizeof(int));
		fib->fib_Size = decrunchinfo >> 8;
	}

	RealClose(tmpfh);

	return(examinereturn);
}

maydie()
{
	if (callers.mlh_Head->mln_Succ)
		return(0);
	else
		return(1);
}

/* Hangaround() waits for messages to arrive at our port, and then deals
** with them. This routine could be written in a much smaller version, but
** I've written it so that it will be easy to add more 'commands'.
*/
hangaround()
{
	register MYMSG *m;

	FOREVER
	{
		WaitPort(pp_port);

		while (m = (MYMSG *)GetMsg(pp_port))
		{
			/* Eventually, some kind of actual communication
			** will take place here. In this version, we simply
			** quit whenever we spot an incomming message.
			*/

			/* Note! MUST NOT reply! (Caller may already have
			** terminated, since he doesn't WaitPort())
			*/
			FreeMem(m,sizeof(*m));

			if (maydie())
				die("PP V1.1: Terminated.\n");
			else
				Say("PP V1.1: Can't terminate just yet.\n");
		}
	}
}

/* Entry point */
void main(int argc, char *argv[])
{
	wbmode = (argc == 0);

	openstuff(argc,argv);
	hangaround();			/* Never returns */
}
