/* csfix.c    (c) Frank Meijer, 1995.
 |
 | PURPOSE:
 |   Allow ComicSetter to run under OS 3.0.
 |
 | HISTORY:
 |   950103: Completed version 1.0.
 |   950101: Works fine.  To be done: install/remove,
 |           workbench launch and get rid of debugging.
 |   941231: Fix bug in mem.asm (saving register A6).
 |   941229: Identify by screen title, not by task name.
 |   941226: Intercept calls to AllocMem() and FreeMem().
 |
 | SYMPTOM:
 |   The CS display would get scrambled on a SAVE (not always,
 |   but almost).  After that, quitting from CS would cause a guru.
 |
 | DIAGNOSIS:
 |   Before a SAVE, CS reduces the screen depth to 1 plane.
 |   It restores the depth to 4 after the save is done.
 |   However, the backdrop window's BitMap does not get updated,
 |   so that if the plane memory allocated for planes 1..3 is not
 |   the same as before the SAVE, the window is out of sync with
 |   the screen.
 |   I assume this worked in OS 1.3 because there were no other
 |   allocations done during the SAVE, so that the memory was
 |   always allocated at the same location.  However, under OS 3.0
 |   some memory is allocated during the SAVE.
 |
 | SOLUTION:
 |   Intercept the calls to FreeMem() and AllocMem().
 |   When CS frees the plane memory, don't really free it but
 |   store the addresses.  When CS re-allocates plane memory,
 |   don't allocate it but return the old pointers.
 |   Note that this should not be done when all planes are freed,
 |   but only when planes 1..3 are freed.
 |
 | NOTES:
 | - In order to verify that the request comes from CS, we don't
 |   check the task name but the active screen title.  This has the
 |   advantage that changes from interlace to non-interlace are
 |   not even seen because the CS screen is closed at that time.
 |   Also, the screen title is independent from the program name
 |   so if the user renamed CS to someting else, it still works.
 |   This algorithm will fail if SAVE causes chunks of the
 |   same size as the screen planes to be allocated, but that does
 |   never seem to be the case.
 | - Plane memory is either 16000 or 32000 bytes per plane.
 |   For non-interlaced: 640*200/8 = 16000.  Double for interlaced.
 |
 */

#include <exec/types.h>
#include <exec/execbase.h>
#include <exec/tasks.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include "defs.h"
#include "mem.h"
#include "info.h"

#define AllocMem_LIBOFF -198
#define FreeMem_LIBOFF  -210

struct ExecBase *ExecBase = NULL;
struct IntuitionBase *IntuitionBase = NULL;

static APTR CSbuf[3] = { NULL, NULL, NULL };
static LONG CSsize = 0;        /* plane memory size */
static int  CSplane = 0;       /* last freed plane + 1 */
static BOOL CSfreeall = FALSE; /* true if plane 0 is also being freed */

#define PROGNAME "CSfix v1.0"
#define PORTNAME "CSfix msgport"
#define INFO_DURATION 2 /* info window display time, in seconds */

static struct MsgPort *msgport;

/*----------------------------------------------------------------*/
__geta4 APTR myAllocMem( LONG size, LONG type )
{
   APTR   memaddr=NULL;   /* return value */
   struct Screen *screen;

   if ( (CSplane > 0) && (size == CSsize) ) {
      screen = IntuitionBase->ActiveScreen;
      if ( screen && screen->Title &&
           (memcmp(screen->Title, "ComicSetter", 11) == 0) ) {
         if (CSplane == 3) {
            /* this is the first allocation */
            memaddr = CSbuf[0];
            CSplane = 1;
         } else {
            /* not the first but plane memory has been saved */
            memaddr = CSbuf[CSplane];
            CSplane++;
            if (CSplane == 3)
               CSplane = 0;  /* done */
         }
#ifdef DEBUG
         printf( "AllocMem( size=%ld, type=%08lx ) -> %08lx\n", size, type, memaddr );
#endif
      }
   }

   if (memaddr == NULL)
      memaddr = CALLrealAllocMem( size, type );

   return( memaddr );
}

/*----------------------------------------------------------------*/
__geta4 void myFreeMem( APTR memaddr, LONG size )
{
   BOOL really_free = TRUE;
   struct Screen *screen;
   struct BitMap *bitmap;

   if ((size == 16000) || (size == 32000)) {

      /* When changing between interlaced and non-interlaced, */
      /* the active screen is the Workbench.  We can ignore   */
      /* those cases because CS handles them correctly.       */
      /* The only case that concern is is the temporary plane */
      /* de-allocation/allocation that is done during SAVE.   */

      screen = IntuitionBase->ActiveScreen;
      if ( screen && screen->Title &&
           (memcmp(screen->Title, "ComicSetter", 11) == 0) ) {
         bitmap = &(screen->BitMap);
         if (memaddr == bitmap->Planes[0])
            CSfreeall = TRUE;
         else {
            if ( (NOT CSfreeall) && (memaddr == bitmap->Planes[CSplane+1]) ) {
#ifdef DEBUG
               printf( "FreeMem( memaddr=%08lx size=%ld ) --> CSbuf[%d]\n",
                       memaddr, size, CSplane );
#endif
               CSbuf[CSplane] = memaddr;
               CSplane++;
               CSsize = size;
               really_free = FALSE;
            }
            CSfreeall = FALSE;
         }
      }

   }

   if (really_free)
      CALLrealFreeMem( memaddr, size );

   return;
}

/*----------------------------------------------------------------*/
main( int argc, char *argv[] )
{
   enum { doInstall, doToggle, doQuit } action;
   struct Message *msg;

   /* Open libraries */
   ExecBase = (struct ExecBase *) OpenLibrary( "exec.library", 0 );
   if (ExecBase == NULL) goto End;
   IntuitionBase = (struct IntuitionBase *) OpenLibrary ("intuition.library", 0);
   if (IntuitionBase == NULL) goto End;

   /* Get requested action from command line parameter */
   action = doToggle;
   if (argc > 1) {
      if (STREQ( argv[1], "install" ))
         action = doInstall;
      else if (STREQ( argv[1], "quit" ))
         action = doQuit;
      else {
         info_show( NULL, FALSE, PROGNAME, "invoked with invalid option!", INFO_DURATION );
         goto End;
      }
   }

   /* See if there's already a copy of me running */
   msgport = (struct MsgPort *) FindPort( PORTNAME );

   /* If we should toggle, determine which way */
   if (action == doToggle)
      if (msgport == NULL)
         action = doInstall;
      else
         action = doQuit;

   /* Perform the requested action */
   if (action == doQuit) {

      /* quit: send a message to the port of the copy of myself */
      if (msgport != NULL) {
         msg = (struct Message *) AllocMem( sizeof(*msg), MEMF_PUBLIC );
         if (msg == NULL) goto End;
         msg->mn_Node.ln_Type = NT_MESSAGE;
         PutMsg( msgport, msg );
      }

   }
   else { /* doInstall */

      /* Cannot install twice */
      if (msgport != NULL) {
         info_show( NULL, FALSE, PROGNAME, "is already installed!", INFO_DURATION );
         goto End;
      }

      /* Create the message port */
      msgport = (struct Msgport *) CreatePort( PORTNAME, 0 );
      if (msgport == NULL) goto End;

      /* Intercept calls to AllocMem */
      realAllocMem = (void *) SetFunction( ExecBase, AllocMem_LIBOFF, CALLmyAllocMem );
      if (realAllocMem == NULL) goto End;

      /* Intercept calls to FreeMem */
      realFreeMem = (void *) SetFunction( ExecBase, FreeMem_LIBOFF, CALLmyFreeMem );
      if (realFreeMem == NULL) goto End;

      /* Tell the user */
      info_show( NULL, FALSE, PROGNAME, "has been installed", INFO_DURATION );

      /* Wait for a message */
      WaitPort( msgport );

      /* Delete the message */
      while( msg = (struct Message *)GetMsg(msgport) )
         FreeMem( msg, sizeof(msg) );

      /* Delete the message port */
      DeletePort( msgport );

      /* Tell the user */
      info_show( NULL, FALSE, PROGNAME, "has been removed", INFO_DURATION );

   }

End:
   /* Restore original vectors */
   if (realAllocMem != NULL)
      SetFunction( ExecBase, AllocMem_LIBOFF, realAllocMem );
   if (realFreeMem != NULL)
      SetFunction( ExecBase, FreeMem_LIBOFF, realFreeMem );

   /* Close the libraries */
   if (ExecBase != NULL)
      CloseLibrary( ExecBase );
   if (IntuitionBase != NULL)
      CloseLibrary( IntuitionBase );

   return( 0 );
}

/*----------------------------------------------------------------*/
int wbmain()  /* When invoked from Workbench */
{

   /* Call the main program as if we were launched from CLI */
   main( 0, NULL );

   return( 0 );
}