/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* |_o_o|\\ Copyright (c) 1986 The Software Distillery.  All Rights Reserved */
/* |. o.| || This program may not be distributed without the permission of   */
/* | .  | || the authors.                                          BBS:      */
/* | o  | ||   Dave Baker    Ed Burnette        Stan Chow    (919)-471-6436  */
/* |  . |//    Jay Denebeim  Gordon Keener      Jack Rouse        Voice:     */
/* ======      John Toebes   Mary Ellen Toebes  Doug Walker  (919)-469-4210  */ 
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* This program watches the first 0x100 bytes of memory for random trashing, */
/* attempts repair of the damage and then puts up an alert indicating the    */
/* damage that was done.  Many thanks to EA for suggesting this program at   */
/* the developer's conference.                                               */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/* * * * * * * * * INCLUDE FILES * * * * * * * * * * * */
#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <exec/libraries.h>
#include <exec/io.h>
#include <exec/tasks.h>
#include <exec/execbase.h>
#include <exec/devices.h>
#include <devices/timer.h>
#include <intuition/intuition.h>
#include <libraries/dosextens.h>

/* * * * * * * * * * * STRUCTURES * * * * * * * * * * * * */
typedef struct stomped
   {
   long data;           /* value that was thrown into address wildly */
   long address;        /* address that was changed */
   } STOMPED;

/* * * * * * * * * * * CONSTANTS * * * * * * * * * * * * */
#define TIMEINTERVAL 2500L      /* in micro seconds */
#define LOWLIMIT     20L        /* smallest safe interval */
#define BANNER "\x9B0;33mMemWatch\x9B0m by John Toebes - Copyright © 1986 The Software Distillery\n 235 Trillingham Ln, Cary NC 27511   BBS:(919)-471-6436\n"
#define BANNER1 "Usage: \x9B1mRUN MemWatch\x9B0m [n]\nWhere n is the optional interval between watch checks (Default 2500ms)\n"

/* * * * * * * * * * * EXTERNAL ROUTINES * * * * * * * * * */
struct IntuitionBase *IntuitionBase = NULL;
extern APTR             AllocMem(long, long);
/* ok so I lied about these tasks - it is true as long as we are working with */
/* DOS processes */
extern struct Process  *FindTask(char *);
extern void SetTaskPri(struct Process *, long);
extern long OpenDevice(char *, long, struct IORequest*, long);
extern void SaveMem();
extern int ValidateMem(struct stomped *);
int DOSBase;

/* * * * * * * * * * * Alert definition structure* * * * * * * * * */
/* we want to display the message:                                 */
/* Someone stepped on Low memory $nnnnnnnn with $nnnnnnnn cccc!    */
/*         Press either Mouse button to continue                   */
/* 345678901234567890123456789012345678901234567890123456789012    */
/*        11111111112222222222333333333344444444445555555555666    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define ALERTHEIGHT 25          /* height of alert in pixals */
#define ADDR_OFF 34             /* location of address substitution */
#define DATA_OFF 49		/* Location of data val substitution */
#define CHAR_OFF 58		/* Location of data text substitution */
static char AlertString[] = {
0, 84,                          /* 2 byte x absolute offset of first string */
10,                             /* 1 byte y absolute offset of first string */
'S', 'o', 'm', 'e', 'o', 'n', 'e', ' ',         /* first message string */
'S', 't', 'e', 'p', 'p', 'e', 'd', ' ',
'o', 'n', ' ', 'L', 'o', 'w', ' ',
'm', 'e', 'm', 'o', 'r', 'y', ' ',
'$', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', ' ', /* address we substitute */
'w', 'i', 't', 'h', ' ',
'$', 'h', 'h', 'h', 'h', 'h', 'h', 'h', 'h', ' ', /* data we substitute */
'c', 'c', 'c', 'c', '!',                          /* chars we substitute */
0,                                              /* null terminator on string */
1,                              /* flag to indicate another alert string */
0, 164,                         /* 2 byte x absolute offset of second string */
18,                             /* 1 byte y absolute offset of second string */
'P', 'r', 'e', 's', 's', ' ',                   /* second message string */
'e', 'i', 't', 'h', 'e', 'r', ' ',
'M', 'o', 'u', 's', 'e', ' ',
'B', 'u', 't', 't', 'o', 'n', ' ',
't', 'o', ' ', 
'c', 'o', 'n', 't', 'i', 'n', 'u', 'e',
0,                                              /* null terminator on string */
0 };                            /* flag to indicate no more strings */

/************************************************************************/
/* The main program to watch the memory                                 */
/************************************************************************/
void _main(cmd)
char *cmd;
   {
   register struct timerequest *timerreq = NULL;
                                    /* request structure for timer waits*/
   struct stomped rslt;             /* information on memory stomps */
   register int i;                  /* general index variable */
   register long v;                 /* temporary for formatting display */
   register long interval = TIMEINTERVAL;
                                    /* how long to wait between checks */
   register char *p;                /* display formatting index */
   /* NOTE: The declarations for i anv v MUST come before that of interval */
   /* because DOIO trashes D6 and D7 which are the first ones selected for */
   /* register variables.  It doesn't matter if i and v are trashed but    */
   /* interval MUST be maintained across the lifetime of the loop          */

   /* if we were run from CLI then output our banner and process parameters */
   if (cmd)
      {
      /* get our default output device */
      v = Output();

      /* display our copyright */
      Write(v, BANNER, sizeof(BANNER));

      /* skip over any leading spaces in the command line */
      while(*cmd == ' ')
         cmd++;

      /* now see if they gave us a number to control the interval of checking */
      interval = 0;

      while ((*cmd >= '0') && (*cmd <= '9'))
         interval = ((short)interval*(short)10) + *cmd++ - '0';

      /* if they gave us nothing (or something we didn't parse well */
      /* such as a VERY large number or just plain trash, give them a */
      /* short usage message for the program */
      if (interval <= 0)
         Write(v, BANNER1, sizeof(BANNER1));

      /* now do the magic that allows the window to be closed */
      if (v != Input())
         Close(Input());
      Close(v);

      /* just incase we got a very low number (or a 0) from the command line */
      /* apply a minimum rule to keep from killing the system performance */
      /* note that since tasks switch at about every 20mili seconds or so */
      /* (not sure where that information is from) making it less than 20000 */
      /* really isn't much of a gain (but it sure feels nice) */
      if (interval < LOWLIMIT) interval = LOWLIMIT;
      }

   /* now we are ready to go - push up our priority with the best of them */
   SetTaskPri( FindTask(NULL), 20);

   /* open up intuition - we only use this for the alert but we must do it */
   /* anyway */
   if ((IntuitionBase = (struct IntuitionBase *)
                        OpenLibrary("intuition.library", 0)) == NULL)
      goto abort;

   /* create a request structure to send the messages with */
   if ((timerreq = (struct timerequest *)
                    AllocMem(sizeof(struct timerequest),
                             MEMF_CLEAR | MEMF_PUBLIC)) == NULL)
      goto abort;

   /* fill in the struture with what we are dealing with */
   timerreq->tr_node.io_Message.mn_Node.ln_Type = NT_MESSAGE;
   timerreq->tr_node.io_Message.mn_Node.ln_Pri = 0;
   timerreq->tr_node.io_Message.mn_ReplyPort = &(FindTask(NULL)->pr_MsgPort);

   /* and open us a timer.  Note that we are using VBLANK since it is the */
   /* lowest system overhead.  We are not critical on the timing and only */
   /* want to run as often as possible without killing the system */
   if (OpenDevice(TIMERNAME, UNIT_VBLANK, (struct IORequest*)timerreq, 0))
      goto abort;

   /* let our assembler stub squirel away a copy of the low memory */
   SaveMem();

   /* go until they hit us with a ^C - kind of a simple way to stop it */
   while(!(SetSignal(0,0) & SIGBREAKF_CTRL_C))
      {
      /* Use the timer to wait our required number of seconds */
      timerreq->tr_node.io_Command = TR_ADDREQUEST;
      timerreq->tr_time.tv_secs =  0;           /* seconds */
      timerreq->tr_time.tv_micro = interval;    /* micro seconds */
      DoIO(timerreq);

      /* Now have our buddy check and fix any memory */
      /* if something was wrong, he will tell us about it */
      if (ValidateMem(&rslt))
         {
         /* format the failure data to be displayed */
         *(long *)(&AlertString[CHAR_OFF]) = rslt.data;

         /* replace any nulls so they don't screw up the message */
         for (i=CHAR_OFF; i<CHAR_OFF+4; i++)
            if (!AlertString[i]) AlertString[i] = '.';

         /* format the address for them */
         p = &AlertString[ADDR_OFF+7];
         v = rslt.address;
         for (i = 7; i>= 0; i--)
            {
            *p-- = "0123456789ABCDEF"[v&15];
            v >>= 4;
            }

         /* format the data value for them */
         p = &AlertString[DATA_OFF+7];
         v = rslt.data;
         for (i = 7; i>= 0; i--)
            {
            *p-- = "0123456789ABCDEF"[v&15];
            v >>= 4;
            }

         /* put up a requester to indicate it failed */
         DisplayAlert(0xBADC0DE, AlertString, ALERTHEIGHT);
         }
      }

   CloseDevice(timerreq);
abort:
   if (timerreq != NULL) FreeMem (timerreq, sizeof(struct timerequest));
   if (IntuitionBase != NULL) CloseLibrary(IntuitionBase);
   XCEXIT(-1);
   }
