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

		Cybergraphics Animation Viewer v1.1	     24 Oct 1996
		-----------------------------------

The files in this archive may be distributed anywhere provided they are
unmodified and are not sold for profit.

Ownership and copyright of all files remains with the author:

	Peter McGavin, 86 Totara Crescent, Lower Hutt, New Zealand.
	e-mail: peterm@maths.grace.cri.nz

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

#include "CyberAnim.h"  /* separate file for GST */

#include "unpack.h"
#include "math64.h"

#define NBITMAPS   2

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

#ifdef __SASC
const char version[] = "$VER: CyberAnim 1.1 " __AMIGADATE__ ;
long __oslibversion = 38;	/* we require at least OS3.0 */
char __stdiowin[] = "CON:20/50/500/130/CyberAnim";
char __stdiov37[] = "/AUTO/CLOSE";
#endif

#ifndef max
#define max(x,y) ((x)>=(y))?(x):(y)
#endif

char programname[20];
BPTR olddir = NULL;
struct RDArgs *rdargs = NULL;

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

#define ID_ANIM MAKE_ID('A','N','I','M')
#define ID_ILBM MAKE_ID('I','L','B','M')
#define ID_ANHD MAKE_ID('A','N','H','D')
#define ID_BMHD MAKE_ID('B','M','H','D')
#define ID_CMAP MAKE_ID('C','M','A','P')
#define ID_CAMG MAKE_ID('C','A','M','G')
#define ID_BODY MAKE_ID('B','O','D','Y')
#define ID_DLTA MAKE_ID('D','L','T','A')

/*  Masking techniques	*/
#define	mskNone			0
#define	mskHasMask		1
#define	mskHasTransparentColor	2
#define	mskLasso		3
#define	mskHasAlpha		4

/*  Compression techniques  */
#define	cmpNone			0
#define	cmpByteRun1		1
#define	cmpByteRun2		2

/*  Bitmap header (BMHD) structure  */
struct BitMapHeader
{
  UWORD bmh_Width;		/* Width in pixels */
  UWORD bmh_Height;		/* Height in pixels */
  WORD  bmh_Left;		/* Left position */
  WORD  bmh_Top;		/* Top position */
  UBYTE bmh_Depth;		/* Number of planes */
  UBYTE bmh_Masking;		/* Masking type */
  UBYTE bmh_Compression;	/* Compression type */
  UBYTE bmh_Pad;
  UWORD bmh_Transparent;	/* Transparent color */
  UBYTE bmh_XAspect;
  UBYTE bmh_YAspect;
  WORD  bmh_PageWidth;
  WORD  bmh_PageHeight;
};

/*  Animation compression modes  */
#define cmpDirect		0
#define cmpXor			1
#define cmpLongdelta		2
#define cmpShortdelta		3
#define cmpDelta		4
#define cmpBytedelta		5
#define cmpStereo		6
#define cmpAnim7		7
#define cmpAnim8		8
#define cmpJ			74

/*  Animation header flags  */
#define anfLongdata	(1<<0)
#define anfXor		(1<<1)
#define anfOnelist	(1<<2)
#define anfRLC		(1<<3)
#define anfVertical	(1<<4)
#define anfLongoffsets	(1<<5)

/*  Animation header (ANHD) structure  */
struct AnimHeader
{
  UBYTE anh_Operation;		/* Compression method */
  UBYTE anh_Mask;		/* plane mask (xor mode only) */
  UWORD anh_W, anh_H;		/* w & h of body (xor mode only) */
  WORD  anh_X, anh_Y;		/* offset of body (xor mode only) */
  ULONG anh_Abstime;		/* 1/60s sec relative to 1st frame */
  ULONG anh_Reltime;		/* 1/60s sec relative to prev frame */
  UBYTE anh_Interleave;		/* Modify frame this many back (0=2) */
  UBYTE anh_Pad0;
  ULONG anh_Bits;		/* animation header flags */
  UBYTE anh_Pad[16];
};

struct mystream {
  FILE *f;
  UBYTE *rambuf;
  UBYTE *rambufptr;
  ULONG rambufsize;
};

struct options {
  BOOL ram;
  BOOL once;
  BOOL dbuf;
  BOOL warp;
  BOOL modereq;
  BOOL waittof;
};

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

struct Library *CyberGfxBase = NULL;

static struct Rectangle rect;

static struct Screen *s = NULL;
static struct Window *w = NULL;

static struct BitMap *bm[NBITMAPS] = {NBITMAPS * NULL};
static BOOL using_fastmem_bitmap = FALSE;
static BOOL using_intermediate_buffer = FALSE;
static WORD *dirty = NULL;

static struct IFFHandle *iff = NULL;
static BOOL iff_is_open = FALSE;

static UWORD *emptypointer = NULL;

static struct FileRequester *fr = NULL;
static struct ScreenModeRequester *smr = NULL;

struct Library *TimerBase = NULL;
static struct MsgPort *timermp = NULL;
static struct timerequest *timerio = NULL;
static ULONG timerclosed = TRUE;
static struct EClockVal *time = NULL;
static struct EClockVal *time0 = NULL;
static struct EClockVal *time1 = NULL;
static double micros_per_eclock; /* Length of EClock tick in microseconds */

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

static void partial_cleanup (void)
{
  int i, plane;

  if (iff_is_open) {
    CloseIFF (iff);
    iff_is_open = FALSE;
  }
  if (iff != NULL) {
    if (iff->iff_Stream != NULL) {
      if (((struct mystream *)iff->iff_Stream)->f != NULL)
        fclose (((struct mystream *)iff->iff_Stream)->f);
      if (((struct mystream *)iff->iff_Stream)->rambuf != NULL)
        free (((struct mystream *)iff->iff_Stream)->rambuf);
      free ((void *)iff->iff_Stream);
    }
    FreeIFF (iff);
    iff = NULL;
  }
  for (i = 0; i < NBITMAPS; i++) {
    if (!using_intermediate_buffer) {
      if (bm[i] != NULL)
        free (bm[i]);
    } else if (using_fastmem_bitmap) {
      if (bm[i] != NULL) {
        for (plane = 0; plane < bm[i]->Depth; plane++)
          if (bm[i]->Planes[plane] != NULL)
            free (bm[i]->Planes[plane]);
        free (bm[i]);
      }
    } else
      FreeBitMap (bm[i]);
    bm[i] = NULL;
  }
  if (dirty != NULL) {
    free (dirty);
    dirty = NULL;
  }
  if (w != NULL) {
    CloseWindow (w);
    w = NULL;
  }
  if (s != NULL) {
    CloseScreen (s);
    s = NULL;
  }
  if (emptypointer != NULL) {
    FreeMem (emptypointer, 12);
    emptypointer = NULL;
  }
}

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

static void cleanup (void)
{
  partial_cleanup ();
  if (IFFParseBase != NULL) {
    CloseLibrary ((struct Library *)IFFParseBase);
    IFFParseBase = NULL;
  }
  if (olddir != NULL) {
    CurrentDir (olddir);
    olddir = NULL;
  }
  if (rdargs != NULL) {
    FreeArgs (rdargs);
    rdargs = NULL;
  }
  if (time1 != NULL) {
    FreeMem (time1, sizeof(struct EClockVal));
    time1 = NULL;
  }
  if (time0 != NULL) {
    FreeMem (time0, sizeof(struct EClockVal));
    time0 = NULL;
  }
  if (time != NULL) {
    FreeMem (time, sizeof(struct EClockVal));
    time = NULL;
  }
  if (!timerclosed) {
    CloseDevice ((struct IORequest *)timerio);
    timerclosed = TRUE;
    TimerBase = NULL;
  }
  if (timerio != NULL) {
    DeleteExtIO ((struct IORequest *)timerio);
    timerio = NULL;
  }
  if (timermp != NULL) {
    DeletePort (timermp);
    timermp = NULL;
  }
  if (smr != NULL) {
    FreeAslRequest (smr);
    smr = NULL;
  }
  if (fr != NULL) {
    FreeAslRequest (fr);
    fr = NULL;
  }
  if (AslBase != NULL) {
    CloseLibrary ((struct Library *)AslBase);
    AslBase = NULL;
  }
  if (CyberGfxBase != NULL) {
    CloseLibrary ((struct Library *)CyberGfxBase);
    CyberGfxBase = NULL;
  }
}

#ifdef __SASC
void _CXBRK (void)
{
  cleanup ();
  fprintf (stderr, "**Break\n");
  exit (0);
}
#endif

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

static char bodystring[64];

static struct TextAttr topaz80 = {
  "topaz.font", 8, 0, 0
};

static struct IntuiText bodytext[] = {
  {0, 1, JAM2, 10, 8, &topaz80, programname, &bodytext[1]},
  {0, 1, JAM2, 10, 20, &topaz80, bodystring, NULL},
};

static struct IntuiText negtext = {0, 1, JAM2, 6, 3, &topaz80, "Ok", NULL};

static void die (char *msg, ...)
/* Exit program with message, return code 10 */
{
  va_list arglist;

  cleanup ();
  va_start (arglist, msg);
  vsprintf (bodystring, msg, arglist);
  va_end (arglist);
  AutoRequest (w, &bodytext[0], NULL, &negtext, 0, 0, 320, 60);
  exit (10);
}

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

static void *malloc_check (size_t size)
{
  void *p;

  if ((p = malloc (size)) == NULL)
    die ("%s: Out of memory trying to allocate %ld bytes!", programname,
         size);
  return (p);
}

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

static void delay_until (struct EClockVal *next_time)
{
  ULONG sig;

  ReadEClock (time);
  if (cmp64 (time, next_time) > 0) {
    timerio->tr_node.io_Command = TR_ADDREQUEST;
    *(struct EClockVal *)&timerio->tr_time = *next_time;
    sub64 ((struct EClockVal *)&timerio->tr_time, time); /* timerio->tr_time -= time */
    /* oldpri = SetTaskPri (thistask, 20); */ /* don't flicker when mouse moves */
    BeginIO ((struct IORequest *)timerio);    /* begin delay */
    sig = Wait ((1 << timermp->mp_SigBit) |
                (1 << w->UserPort->mp_SigBit) | /* also return for IDCMP */
                SIGBREAKF_CTRL_C);              /* or SIGBREAKF_CTRL_C */
    if ((sig & (1 << timermp->mp_SigBit)) == 0) {
      SetSignal (sig, sig);  /* restore IDCMP and SIGBREAKF_CTRL_C signals */
      AbortIO ((struct IORequest *)timerio);
      WaitIO ((struct IORequest *)timerio);
    }
    while (GetMsg (timermp) != NULL)
      /* nothing */ ;
/*
    while (cmp64 (time, next_time) > 0)
      ReadEClock (time);
*/
    /* SetTaskPri (thistask, oldpri); */ /* restore task priority */
  }
  ReadEClock (next_time);
}

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

static void parse_tooltypes (char *fname, struct options *opt)
{
  struct DiskObject *obj;
  char **toolarray;

  if ((obj = GetDiskObject (fname)) != NULL) {
    toolarray = obj->do_ToolTypes;
    if (FindToolType (toolarray, "DISK") != NULL)
      opt->ram = FALSE;
    if (FindToolType (toolarray, "RAM") != NULL)
      opt->ram = FALSE;
    if (FindToolType (toolarray, "ONCE") != NULL)
      opt->once = TRUE;
    if (FindToolType (toolarray, "WARP") != NULL)
      opt->warp = TRUE;
    if (FindToolType (toolarray, "NOMODEREQ") != NULL)
      opt->modereq = FALSE;
    if (FindToolType (toolarray, "WAITTOF") != NULL)
      opt->waittof = TRUE;
    FreeDiskObject (obj);
  }
}

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

static void load_cmap (struct Screen *s, UBYTE *cmap, int ncolours,
                       ULONG intended_mode)
{
  int c;
  ULONG *colourtable;

  colourtable = (ULONG *)malloc_check ((1 + 3 * ncolours + 1) * sizeof(ULONG));
  colourtable[0] = (ncolours << 16) + 0;
  for (c = 0; c < ncolours; c++) {
    colourtable[3 * c + 1] = *cmap++ * 0x01010101;
    colourtable[3 * c + 2] = *cmap++ * 0x01010101;
    colourtable[3 * c + 3] = *cmap++ * 0x01010101;
  }
  if ((intended_mode & EXTRAHALFBRITE_KEY) != 0) {
    cmap -= 3 * ncolours;
    for (c = ncolours >> 1; c < ncolours; c++) {
      colourtable[3 * c + 1] = (cmap[0] >> 1) * 0x01010101;
      colourtable[3 * c + 2] = (cmap[1] >> 1) * 0x01010101;
      colourtable[3 * c + 3] = (cmap[2] >> 1) * 0x01010101;
      cmap += 3;
    }
  }
  colourtable[1 + 3 * ncolours] = 0;
  LoadRGB32 (&s->ViewPort, colourtable);
  free (colourtable);
}

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

static BOOL unpackrow (BYTE **src0, BYTE *dst, WORD dstbytes)
{
  BYTE *src;
  BYTE n;
  BOOL err;

  err = TRUE;
  src = *src0;
  while (dstbytes > 0) {
    n = *src++;
    if (n >= 0) {
      n += 1;
      if ((dstbytes -= n) < 0)
        goto error;
      memcpy (dst, src, n);
      src += n;
    } else if (n != -128) {
      n = 1 - n;
      if ((dstbytes -= n) < 0)
        goto error;
      memset (dst, *src++, n);
    }
    dst += n;
  }
  err = FALSE;
error:
  *src0 = src;
  return (err);
}

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

static void unpacklongdelta (WORD *wdata, LONG *plane, WORD bytesperrow,
                             WORD *dirty)
{
  WORD offset;
  UWORD count;

  while ((offset = *wdata++) != -1) {
    if (offset >= 0) {
      plane += offset;
      *plane = *(LONG *)wdata;
      wdata += 2;
    } else {
      count = *(UWORD *)wdata;
      wdata++;
      plane += -offset - 1;
      memcpy (plane, wdata, count << 2);
      plane += count - 1;
      wdata += (count << 1);
    }
  }
}

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

static void unpackshortdelta (WORD *wdata, WORD *plane, WORD bytesperrow,
                              WORD *dirty)
{
  WORD offset;
  UWORD count;

  while ((offset = *wdata++) != -1) {
    if (offset >= 0) {
      plane += offset;
      *plane = *wdata++;
    } else {
/*
      plane += -offset - 1;
      for (count = *wdata++; count > 0; count--)
        *plane++ = *wdata++;
      plane--;
*/
      count = *(UWORD *)wdata;
      wdata++;
      plane += -offset - 1;
      memcpy (plane, wdata, count << 1);
      plane += count - 1;
      wdata += count;
    }
  }
}

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

#if 0
void __asm unpackbytedelta (register __a0 BYTE *bdata,
                            register __a1 PLANEPTR plane,
                            register __d0 WORD bytesperrow,
                            register __a2 WORD *dirty);

static void unpackbytedelta (BYTE *bdata, PLANEPTR plane, WORD bytesperrow,
                             WORD *dirty)
{
  WORD x, y, count, w, first, last;
  BYTE *bp;
  UBYTE ub;

  for (x = 0; x < bytesperrow; x++) {
    first = -1;
    last = -1;
    bp = &plane[x];
    y = 0;
    for (count = *bdata++; count > 0; count--) {
      if ((w = *bdata++) > 0) {
        y += w;
        bp += w * bytesperrow;
      } else if (w < 0) {
        if (first < 0)
          first = y;
        last = (y += (w &= 0x7f));
        for ( ; w > 0; w--) {
          *bp = *bdata++;
          bp += bytesperrow;
        }
      } else /* w == 0 */ {
        if (first < 0)
          first = y;
        last = (y += (ub = (UBYTE)*bdata++));
        w = *bdata++;
        for ( ; ub > 0; ub--) {
          *bp = w;
          bp += bytesperrow;
        }
      }
    }
    if (first >= 0 && (dirty[0] < 0 || first < dirty[0]))
      dirty[0] = first;
    if (last >= 0 && (dirty[1] < 0 || last > dirty[1]))
      dirty[1] = last;
    if ((x & 3) == 3)
      dirty += 2;
  }
}
#endif

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

#if 0
static void unpackanim7word (BYTE *bdata, WORD *data, PLANEPTR plane,
                             WORD wordsperrow, WORD *dirty)
{
  WORD x, y, count, w, first, last;
  WORD *wp;
  UBYTE ub;

  for (x = 0; x < wordsperrow; x++) {
    first = -1;
    last = -1;
    wp = &((WORD *)plane)[x];
    y = 0;
    for (count = *bdata++; count > 0; count--) {
      if ((w = *bdata++) > 0) {
        y += w;
        wp += w * wordsperrow;
      } else if (w < 0) {
        if (first < 0)
          first = y;
        last = (y += (w &= 0x7f));
        for ( ; w > 0; w--) {
          *wp = *data++;
          wp += wordsperrow;
        }
      } else /* w == 0 */ {
        if (first < 0)
          first = y;
        last = (y += (ub = (UBYTE)*bdata++));
        w = *data++;
        for ( ; ub > 0; ub--) {
          *wp = w;
          wp += wordsperrow;
        }
      }
    }
    if (first >= 0 && (dirty[0] < 0 || first < dirty[0]))
      dirty[0] = first;
    if (last >= 0 && (dirty[1] < 0 || last > dirty[1]))
      dirty[1] = last;
    if ((x & 1) == 1)
      dirty += 2;
  }
}
#endif

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

#if 0
void __asm unpackanim7long (register __a0 BYTE *bdata,
                            register __a4 LONG *data,
                            register __a1 PLANEPTR plane,
                            register __d0 WORD bytesperrow,
                            register __a2 WORD *dirty);

static void unpackanim7long (BYTE *bdata, LONG *data, PLANEPTR plane,
                             WORD bytesperrow, WORD *dirty)
{
  WORD x, y, count, w, first, last;
  LONG *lp, l;
  UBYTE ub;

  for (x = 0; x < bytesperrow; x += 4) {
    first = -1;
    last = -1;
    lp = &((LONG *)plane)[x];
    y = 0;
    for (count = *bdata++; count > 0; count--) {
      if ((w = *bdata++) > 0) {
        y += w;
        lp += w * (bytesperrow >> 2);
      } else if (w < 0) {
        if (first < 0)
          first = y;
        last = (y += (w &= 0x7f));
        for ( ; w > 0; w--) {
          *lp = *data++;
          lp += (bytesperrow >> 2);
        }
      } else /* w == 0 */ {
        if (first < 0)
          first = y;
        last = (y += (ub = (UBYTE)*bdata++));
        l = *data++;
        for ( ; ub > 0; ub--) {
          *lp = l;
          lp += (bytesperrow >> 2);
        }
      }
    }
    if (first >= 0 && (dirty[0] < 0 || first < dirty[0]))
      dirty[0] = first;
    if (last >= 0 && (dirty[1] < 0 || last > dirty[1]))
      dirty[1] = last;
    dirty += 2;
  }
}
#endif

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

static void blit_opt (struct BitMap *bm, struct RastPort *rp, WORD *dirty,
                      int yoffset)
{
  WORD x, firstx, miny, maxy;

  firstx = -1;
  for (x = 0; x < bm->BytesPerRow; x += 4) {
    if (dirty[0] >= 0 && dirty[1] >= 0) {
      if (firstx == -1) {
        firstx = x;
        miny = dirty[0];
        maxy = dirty[1];
      } else if (abs(dirty[0] - miny) < 10 &&
                 abs(dirty[1] - maxy) < 10) {
        miny = min(dirty[0], miny);
        maxy = max(dirty[1], maxy);
      } else {
        BltBitMapRastPort (bm, firstx << 3, miny, rp, firstx << 3,
                           miny + yoffset, (x - firstx) << 3,
                           maxy - miny, 0xc0);
        firstx = x;
        miny = dirty[0];
        maxy = dirty[1];
      }
    } else if (firstx != -1) {
      BltBitMapRastPort (bm, firstx << 3, miny, rp, firstx << 3,
                         miny + yoffset, (x - firstx) << 3,
                         maxy - miny, 0xc0);
      firstx = -1;
    }
    dirty += 2;
  }
  if (firstx != -1)
    BltBitMapRastPort (bm, firstx << 3, miny, rp, firstx << 3,
                       miny + yoffset, (bm->BytesPerRow - firstx) << 3,
                       maxy - miny, 0xc0);
}

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

#if 0
static void PrintTopChunk (struct IFFHandle *iff)
{
  struct ContextNode *top;
  short i;
  char idbuf[5];

  /* Get a pointer to the context node describing the current context. */
  if (!(top = CurrentChunk (iff)))
		return;

  /*
   * Print a series of dots equivalent to the current nesting depth of chunks processed so far.
   * This will cause nested chunks to be printed out indented.
   */
  for (i = iff->iff_Depth;  i--; )
    printf (". ");

  /* Print out the current chunk's ID and size. */
  printf ("%s %ld ", IDtoStr (top->cn_ID, idbuf), top->cn_Size);

  /* Print the current chunk's type, with a newline. */
  puts (IDtoStr (top->cn_Type, idbuf));
}
#endif

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

#ifdef __SASC
static __saveds __asm LONG mystreamhandler (
  register __a0 struct Hook *hook,
  register __a2 struct IFFHandle *iff,
  register __a1 struct IFFStreamCmd *actionpkt)
{
#else /* gcc */
static LONG mystreamhandler (void)
{
  register struct Hook *hook __asm("a0");
  register struct IFFHandle *iff __asm("a2");
  register struct IFFStreamCmd *actionpkt __asm("a1");
#endif
  struct mystream *s;
  LONG nbytes, error;
  UBYTE *buf;

  s = (struct mystream *)iff->iff_Stream;
  nbytes = actionpkt->sc_NBytes;
  buf = (UBYTE *)actionpkt->sc_Buf;
  switch (actionpkt->sc_Command) {
    case IFFCMD_READ:
      if (s->f != NULL)
        error = (fread (buf, 1, nbytes, s->f) != nbytes);
      else if (s->rambuf != NULL &&
               nbytes <= s->rambuf + s->rambufsize - s->rambufptr &&
               nbytes >= 0) {
        /* printf ("Reading %d from %d\n", nbytes, s->rambufptr - s->rambuf); */
        if (nbytes > 0) {
          memcpy (buf, s->rambufptr, nbytes);
          s->rambufptr += nbytes;
        }
        error = FALSE;
      } else
        error = TRUE;
      break;
    case IFFCMD_WRITE:
      if (s->f != NULL)
        error = (fwrite (buf, 1, nbytes, s->f) != nbytes);
      else
        error = TRUE;
      break;
    case IFFCMD_SEEK:
      if (s->f != NULL)
        error = (fseek (s->f, nbytes, SEEK_CUR) == -1);
      else if (s->rambuf != NULL) {
        s->rambufptr += nbytes;
        error = s->rambufptr > (s->rambuf + s->rambufsize) ||
                s->rambufptr < s->rambuf;
      } else
        error = TRUE;
      break;
    case IFFCMD_INIT:
    case IFFCMD_CLEANUP:
      error = FALSE;
      break;
    default:
      error = TRUE;
  }
  return (error);
}

static struct Hook mystreamhook = {
  {NULL},
  (ULONG (*)())mystreamhandler,
  NULL,
  NULL
};

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

static void animate_file (char *fname, struct options opt)
{
  int plane, /* x, */ y, i, width, height, oscan_height, depth, which, ifferror;
  ULONG class, size, mode, intended_mode, totalframes, sig;
  UWORD code, count, propertymask, srcbytesperrow;
  WORD *wdata;
  UBYTE *body, *src, *dst[8], *dlta;
  LONG fsize;
  struct IntuiMessage *msg;
  BOOL going, first_time;
  struct StoredProperty *bmhdprop, *cmapprop, *camgprop, *anhdprop;
  struct BitMapHeader *bmhd;
  struct AnimHeader *anhd;
  struct DimensionInfo dimsinfo;
  struct mystream *mystream;
  char reqtitle[30], *type_string;
  struct EClockVal eclocks, warp_eclocks, total_eclocks, next_time;


  parse_tooltypes (fname, &opt);

  if ((iff = AllocIFF ()) == NULL)
    die ("%s: AllocIFF() failed", programname);

  mystream = (struct mystream *)malloc_check (sizeof(struct mystream));
  memset (mystream, 0, sizeof(struct mystream));

  if ((mystream->f = fopen (fname, "r")) == 0)
    die ("%s: Can't open %s", programname, fname);
  if (fseek (mystream->f, 0, SEEK_END) == -1 ||
      (fsize = ftell (mystream->f)) == -1)
    die ("%s: Error seeking %s", programname, fname);
  rewind (mystream->f);

  if (opt.ram && (mystream->rambuf = malloc (fsize)) != NULL) {
    if (fread (mystream->rambuf, 1, fsize, mystream->f) != fsize ||
        fclose (mystream->f) == EOF)
      die ("%s: Error reading %s", programname, fname);
    mystream->f = NULL;
    mystream->rambufptr = mystream->rambuf;
    mystream->rambufsize = fsize;
  } else {
    if (opt.ram)
      printf ("Not enough memory to play from RAM, playing from DISK instead\n");
    mystream->rambuf = NULL;
  }

  iff->iff_Stream = (ULONG)mystream;
  InitIFF (iff, IFFF_FSEEK | IFFF_RSEEK, &mystreamhook);

  if (OpenIFF (iff, IFFF_READ) != 0)
    die ("%s: OpenIFF() failed", programname);
  iff_is_open = TRUE;

  if (PropChunk (iff, ID_ILBM, ID_BMHD) != 0 ||
      PropChunk (iff, ID_ILBM, ID_ANHD) != 0 ||
      PropChunk (iff, ID_ILBM, ID_CMAP) != 0 ||
      PropChunk (iff, ID_ILBM, ID_CAMG) != 0 ||
      StopChunk (iff, ID_ILBM, ID_BODY) != 0)
    die ("%s: Error calling PropChunk or StopChunk parsing %s", programname, fname);
  if ((ifferror = ParseIFF (iff, IFFPARSE_SCAN)) != 0) {
    fprintf (stderr, "%s: IFF error %d parsing %s\n", programname, ifferror, fname);
    partial_cleanup ();
    return;
  }
  if ((bmhdprop = FindProp (iff, ID_ILBM, ID_BMHD)) == NULL ||
      (cmapprop = FindProp (iff, ID_ILBM, ID_CMAP)) == NULL) {
    fprintf (stderr, "%s: Missing BMHD or CMAP parsing %s\n", programname, fname);
    partial_cleanup ();
    return;
  }

  bmhd = (struct BitMapHeader *)bmhdprop->sp_Data;

  width = bmhd->bmh_Width;
  height = bmhd->bmh_Height;
  depth = bmhd->bmh_Depth;
  if ((camgprop = FindProp (iff, ID_ILBM, ID_CAMG)) != NULL)
    intended_mode = *(ULONG *)camgprop->sp_Data;
  else
    intended_mode = 0;

  /* display file attributes */
  printf ("\nFile = %s, size = %ld bytes\n", fname, fsize);
  printf ("Anim size %ux%ux%u\n", width, height, depth);
  printf ("Intended ModeID = %08lx", intended_mode);

  type_string = "";
  propertymask = DIPF_IS_EXTRAHALFBRITE | DIPF_IS_DUALPF | DIPF_IS_PF2PRI |
                 DIPF_IS_HAM;
  if (camgprop != NULL &&
      (*(ULONG *)camgprop->sp_Data & EXTRAHALFBRITE_KEY) != 0) {
    printf ("  (EXTRAHALFBRITE)");
    type_string = " EHB";
    propertymask &= ~DIPF_IS_EXTRAHALFBRITE;
  }
  if (camgprop != NULL &&
      (*(ULONG *)camgprop->sp_Data & HAM_KEY) != 0) {
    switch (depth) {
      case 6:
        printf ("  (HAM)");
        type_string = " HAM6";
        break;
      case 8:
        printf ("  (HAM8)");
        type_string = " HAM8";
        break;
      default:
        die ("%s: HAM but not depth 6 or 8", programname);
    }
    propertymask &= ~DIPF_IS_HAM;
  }
  printf ("\n");

  first_time = TRUE;
  warp_eclocks.ev_hi = 0;
  warp_eclocks.ev_lo = 0;
  eclocks.ev_hi = 0;
  eclocks.ev_lo = 0;
  total_eclocks.ev_hi = 0;
  total_eclocks.ev_lo = 0;
  if ((anhdprop = FindProp (iff, ID_ILBM, ID_ANHD)) != NULL) {
    anhd = (struct AnimHeader *)anhdprop->sp_Data;
    eclocks.ev_lo = (ULONG)((1000000.0 / 60.0) * anhd->anh_Reltime /
                    micros_per_eclock + 0.5);
    add64 (&total_eclocks, &eclocks);
  }

  if (CyberGfxBase != NULL)
    mode = BestCModeIDTags (CYBRBIDTG_NominalWidth, max(width, 320),
                            CYBRBIDTG_NominalHeight, max(height, 200),
                            CYBRBIDTG_Depth, max(depth, 4),
                            TAG_DONE);
  else if (GfxBase->LibNode.lib_Version >= 39)
    mode = BestModeID (BIDTAG_NominalWidth, width,
                       BIDTAG_NominalHeight, height,
                       BIDTAG_Depth, depth,
                       BIDTAG_DIPFMustNotHave, propertymask,
                       TAG_DONE);
  else
    mode = 0;
  if (opt.modereq) {
    sprintf (reqtitle, "%s %dx%d%s", programname, width, height, type_string);
    if (!AslRequestTags (smr,
                         ASLSM_TitleText,     (ULONG)reqtitle,
                         ASLSM_InitialDisplayID, mode,
                         ASLSM_MinWidth,      width,
                         ASLSM_MinHeight,     height,
                         ASLSM_MinDepth,      depth,
                         ASLSM_MaxDepth,      8,
                         ASLSM_PropertyMask,  propertymask,
                         ASLSM_PropertyFlags, 0,
                         TAG_DONE))
      die ("%s: ScreenMode requester failed or cancelled", programname);
    mode = smr->sm_DisplayID;
  }

  printf ("Using ModeID = %08lx\n", mode);

  if ((count = GetDisplayInfoData (NULL, (UBYTE *)&dimsinfo,
                                   sizeof(struct DimensionInfo), DTAG_DIMS,
                                   mode)) < 66
                                             /* sizeof(struct DimensionInfo) */)
    die ("%s: GetDisplayInfoData(Dims) failed (%d)", programname, count);

  oscan_height = max(height, dimsinfo.MaxOScan.MaxY - dimsinfo.MaxOScan.MinY + 1);

  rect.MinX = 0;
  rect.MaxX = width - 1;
  rect.MinY = 0;
  rect.MaxY = height - 1;

  if ((s = OpenScreenTags (NULL,
                           SA_DisplayID, mode,
                           SA_DClip, (ULONG)&rect,
                           SA_Width, width,
                           SA_Height, oscan_height << 1,
                           SA_Depth, max (depth, 4),
                           SA_Type, CUSTOMSCREEN,
                           TAG_DONE)) == NULL)
    die ("%s: Can't open Screen", programname);

  load_cmap (s, (UBYTE *)cmapprop->sp_Data, 1 << depth, intended_mode);

  printf ("Screen size %ux%u\n", s->Width, s->Height >> 1);

  if ((w = OpenWindowTags (NULL,
                           WA_Left,   0,
                           WA_Top,    0,
                           WA_Width,  width,
                           WA_Height, oscan_height << 1,
                           WA_IDCMP,  IDCMP_VANILLAKEY | IDCMP_RAWKEY | IDCMP_MOUSEBUTTONS,
                           WA_Flags,  WFLG_ACTIVATE | WFLG_BORDERLESS | WFLG_RMBTRAP,
                           WA_CustomScreen, s,
                           TAG_DONE)) == NULL)
    die ("%s: Can't open Window", programname);

  emptypointer = (WORD *)AllocMem (12, MEMF_CHIP | MEMF_CLEAR);
  SetPointer (w, emptypointer, 1, 16, 0, 0);

  for (i = 0; i < NBITMAPS; i++) {
    if (CyberGfxBase != NULL && IsCyberModeID (GetVPModeID (&s->ViewPort))) {
      using_fastmem_bitmap = TRUE;
      using_intermediate_buffer = TRUE;
      bm[i] = malloc_check (sizeof(struct BitMap));
      memset (bm[i], 0, sizeof(struct BitMap));
      InitBitMap (bm[i], depth, width, height);
      for (plane = 0; plane < bm[i]->Depth; plane++)
        bm[i]->Planes[plane] = malloc_check (bm[i]->BytesPerRow * bm[i]->Rows);
    } else if (GfxBase->LibNode.lib_Version >= 39 &&
               (GetBitMapAttr (s->ViewPort.RasInfo->BitMap, BMA_FLAGS) &
                               BMF_STANDARD) != 0 &&
               (GetBitMapAttr (s->ViewPort.RasInfo->BitMap, BMA_FLAGS) &
                               BMF_INTERLEAVED) == 0) {
      using_fastmem_bitmap = FALSE;
      using_intermediate_buffer = FALSE;
      bm[i] = malloc_check (sizeof(struct BitMap));
      memset (bm[i], 0, sizeof(struct BitMap));
      bm[i]->Rows = height;
      bm[i]->BytesPerRow = s->ViewPort.RasInfo->BitMap->BytesPerRow;
      bm[i]->Depth = s->ViewPort.RasInfo->BitMap->Depth;
      for (plane = 0; plane < bm[i]->Depth; plane++)
        bm[i]->Planes[plane] = &s->ViewPort.RasInfo->BitMap->Planes[plane]
                                [i * bm[i]->BytesPerRow * oscan_height];
    } else {
      using_fastmem_bitmap = FALSE;
      using_intermediate_buffer = TRUE;
      if ((bm[i] = AllocBitMap (width, height, depth, 0, NULL)) == NULL)
        die ("%s: Can't allocate bitmap", programname);
    }
  }
  printf ("Bitmap size %ux%ux%u\n", bm[0]->BytesPerRow << 3,
          bm[0]->Rows, bm[0]->Depth);
  if (!using_fastmem_bitmap)
    printf ("Using CHIPMEM\n");
  if (!using_intermediate_buffer)
    printf ("Using direct rendering\n");

  dirty = (WORD *)malloc_check ((((bm[0]->BytesPerRow + 3) >> 2) << 1)
                                  * sizeof(WORD));

  /* PrintTopChunk (iff); */

  size = CurrentChunk (iff)->cn_Size;
  if (mystream->f != NULL) {
    body = (UBYTE *)malloc_check (size);
    if (ReadChunkBytes (iff, body, size) != size)
      die ("%s: Error reading %s", programname, fname);
  } else
    body = mystream->rambufptr;

  src = body;
  srcbytesperrow = RASSIZE (width, 1);
  for (plane = 0; plane < depth; plane++)
    dst[plane] = bm[0]->Planes[plane];

  for (y = 0; y < height; y++) {
    for (plane = 0; plane < depth; plane++) {
      switch (bmhd->bmh_Compression) {
        case cmpNone:
          memcpy (dst[plane], src, srcbytesperrow);
          src += srcbytesperrow;
          break;
        case cmpByteRun1:
          if (unpackrow ((BYTE **)&src, dst[plane], srcbytesperrow))
            die ("%s: Error unpacking BODY", programname);
          break;
        default:
          die ("%s: Unrecognised compression", programname);
      }
      dst[plane] += bm[0]->BytesPerRow;
    }
  }

  if (mystream->f != NULL)
    free (body);

  for (plane = 0; plane < depth; plane++)
    memcpy (bm[1]->Planes[plane], bm[0]->Planes[plane],
            bm[0]->BytesPerRow * bm[0]->Rows);

  if (using_intermediate_buffer) {
    BltBitMapRastPort (bm[0], 0, 0, w->RPort, 0, 0, width, height, 0xc0);
    BltBitMapRastPort (bm[1], 0, 0, w->RPort, 0, oscan_height, width, height,
                       0xc0);
  }

  /* read the start time */
  ReadEClock (time0);
  next_time = *time0;
  add64 (&next_time, &eclocks);

  which = 0;
  totalframes = 1;
  going = TRUE;

  /* loop for each frame */
  while (going) {

    /* check for input */
    while ((msg = (struct IntuiMessage *)GetMsg (w->UserPort)) != NULL) {
      class = msg->Class;
      code = msg->Code;
      ReplyMsg ((struct Message *)msg);
      switch (class) {
        case IDCMP_VANILLAKEY:
          switch (code) {
            case 0x03: /* CTRL/C */
            case 0x1b: /* ESC */
            case 0x51: /* q */
            case 0x71: /* Q */
              going = FALSE;
              break;
            default:
              break;
          }
          break;
        case IDCMP_RAWKEY:
          switch (code) {
            case 0x50: /* F1 */
              warp_eclocks.ev_lo = 0;
              opt.warp = TRUE;
              break;
            case 0x51: /* F2 */
              warp_eclocks.ev_lo = (ULONG)((1000000.0 / 60.0) / micros_per_eclock + 0.5);
              opt.warp = TRUE;
              break;
            case 0x52: /* F3 */
              warp_eclocks.ev_lo = (ULONG)((1000000.0 / 30.0) / micros_per_eclock + 0.5);
              opt.warp = TRUE;
              break;
            case 0x53: /* F4 */
              warp_eclocks.ev_lo = (ULONG)((1000000.0 / 24.0) / micros_per_eclock + 0.5);
              opt.warp = TRUE;
              break;
            case 0x54: /* F5 */
              warp_eclocks.ev_lo = (ULONG)((1000000.0 / 15.0) / micros_per_eclock + 0.5);
              opt.warp = TRUE;
              break;
            case 0x55: /* F6 */
              warp_eclocks.ev_lo = (ULONG)((1000000.0 / 12.0) / micros_per_eclock + 0.5);
              opt.warp = TRUE;
              break;
            case 0x56: /* F7 */
              warp_eclocks.ev_lo = (ULONG)((1000000.0 / 10.0) / micros_per_eclock + 0.5);
              opt.warp = TRUE;
              break;
            case 0x57: /* F8 */
              warp_eclocks.ev_lo = (ULONG)((1000000.0 / 5.0) / micros_per_eclock + 0.5);
              opt.warp = TRUE;
              break;
            case 0x58: /* F9 */
              warp_eclocks.ev_lo = (ULONG)(1000000.0 / micros_per_eclock + 0.5);
              opt.warp = TRUE;
              break;
            case 0x59: /* F10 */
              opt.warp = FALSE;
              break;
            default:
              break;
          }
          break;
        case IDCMP_MOUSEBUTTONS:
          if (code == MENUDOWN)
            going = FALSE;
          break;
        default:
          break;
      }
    }

#ifdef __SASC
    chkabort ();
#endif

    /* PrintTopChunk (iff); */

    while ((ifferror = ParseIFF (iff, IFFPARSE_RAWSTEP)) == IFFERR_EOC)
      /* do nothing */ ;

    /* check for end of file */
    if (ifferror == IFFERR_EOF)
      if (opt.once)
        break;
      else {
        /* found end of file --- restart from 2nd DLTA */
        CloseIFF (iff);
        iff_is_open = FALSE;
        if (mystream->f != NULL)
          rewind (mystream->f);
        else if (mystream->rambuf != NULL)
          mystream->rambufptr = mystream->rambuf;
        else
          die ("%s: Internal error", programname);
        if (OpenIFF (iff, IFFF_READ) != 0)
          die ("%s: OpenIFF() failed", programname);
        iff_is_open = TRUE;
        if (StopChunk (iff, ID_ILBM, ID_BODY) != 0)
          die ("%s: Error calling StopChunk parsing %s", programname, fname);
        if ((ifferror = ParseIFF (iff, IFFPARSE_SCAN)) != 0)
          die ("%s: IFF error %d parsing %s for BODY", programname, ifferror, fname);
        while ((ifferror = ParseIFF (iff, IFFPARSE_RAWSTEP)) == IFFERR_EOC)
          /* do nothing */ ;
        if (StopChunk (iff, ID_ILBM, ID_DLTA) != 0)
          die ("%s: Error calling StopChunk parsing %s", programname, fname);
        if ((ifferror = ParseIFF (iff, IFFPARSE_SCAN)) != 0)
          if (ifferror != IFFERR_EOF)
            die ("%s: IFF error %d parsing %s for DLTA", programname, ifferror, fname);
          else {
            /* DLTA frame not found --- display BODY and wait for quit event */
            while (going) {
              sig = Wait ((1 << w->UserPort->mp_SigBit) | SIGBREAKF_CTRL_C);
              if ((sig & (1 << w->UserPort->mp_SigBit)) != 0) {
                while ((msg = (struct IntuiMessage *)GetMsg (w->UserPort)) != NULL) {
                  class = msg->Class;
                  code = msg->Code;
                  ReplyMsg ((struct Message *)msg);
                  switch (class) {
                    case IDCMP_VANILLAKEY:
                      switch (code) {
                        case 0x03: /* CTRL/C */
                        case 0x1b: /* ESC */
                        case 0x51: /* q */
                        case 0x71: /* Q */
                          going = FALSE;
                          break;
                        default:
                          break;
                      }
                      break;
                    case IDCMP_MOUSEBUTTONS:
                      if (code == MENUDOWN)
                        going = FALSE;
                      break;
                    default:
                      break;
                  }
                }
              }
              if ((sig & SIGBREAKF_CTRL_C) != 0)
                going = FALSE;
            }
          }
        continue;
      }
    else if (ifferror != 0)
      die ("%s: IFF error %d parsing %s", programname, ifferror, fname);

    /* render to the other bitmap */
    which = 1 - which;

    /* PrintTopChunk (iff); */

    if (PropChunk (iff, ID_ILBM, ID_ANHD) != 0 ||
        PropChunk (iff, ID_ILBM, ID_CMAP) != 0 ||
        StopChunk (iff, ID_ILBM, ID_DLTA) != 0)
      die ("%s: Error calling PropChunk or StopChunk parsing %s", programname, fname);
    if ((ifferror = ParseIFF (iff, IFFPARSE_SCAN)) != 0)
      die ("%s: IFF error %d parsing %s", programname, ifferror, fname);
    if ((anhdprop = FindProp (iff, ID_ILBM, ID_ANHD)) == NULL)
      die ("%s: missing ANHD chunk parsing %s", programname, fname);

    anhd = (struct AnimHeader *)anhdprop->sp_Data;
    eclocks.ev_lo = (ULONG)((1000000.0 / 60.0) * anhd->anh_Reltime /
                    micros_per_eclock + 0.5);
    add64 (&total_eclocks, &eclocks);
    if (first_time) {
      printf ("Anim type %d, abstime %ld, reltime %ld, interleave %d, flags %08x\n",
              (int)anhd->anh_Operation, anhd->anh_Abstime, anhd->anh_Reltime,
              (int)anhd->anh_Interleave, anhd->anh_Bits);
      first_time = FALSE;
    }

    size = CurrentChunk (iff)->cn_Size;
    /* printf ("DLTA size %d at %d\n", size, CurrentChunk (iff)->cn_Scan); */
    if (mystream->f != NULL) {
      dlta = malloc_check (size);
      if (ReadChunkBytes (iff, dlta, size) != size)
        die ("%s: Error reading %s", programname, fname);
    } else
      dlta = mystream->rambufptr;

    if (using_intermediate_buffer)
      memset (dirty, -1, (((bm[0]->BytesPerRow + 3) >> 2) << 1) * sizeof(WORD));
    else
      if (opt.waittof)
        WaitTOF ();

    /* unpack DLTA */
    for (plane = 0; plane < depth; plane++) {
      wdata = (WORD *)&dlta[((ULONG *)dlta)[plane]];
      /* printf ("%d %08x %ld\n", plane, wdata, ((ULONG *)dlta)[plane]); */

      switch (anhd->anh_Operation) {

        case cmpLongdelta:	/* 2 */
          if (wdata != (WORD *)dlta)
            unpacklongdelta (wdata, (LONG *)bm[which]->Planes[plane],
                             bm[which]->BytesPerRow, dirty);
          break;

        case cmpShortdelta:	/* 3 */
          if (wdata != (WORD *)dlta)
            unpackshortdelta (wdata, (WORD *)bm[which]->Planes[plane],
                             bm[which]->BytesPerRow, dirty);
          break;

        case cmpBytedelta:	/* 5 */
          if (wdata != (WORD *)dlta)
            if (using_intermediate_buffer)
              unpackbytedelta ((BYTE *)wdata, bm[which]->Planes[plane],
                               bm[which]->BytesPerRow, dirty);
            else
              unpackbytedeltanodirty ((BYTE *)wdata, bm[which]->Planes[plane],
                                      bm[which]->BytesPerRow);
          break;

        case cmpAnim7:		/* 7 */
          if (wdata != (WORD *)dlta)
            if ((anhd->anh_Bits & anfLongdata) != 0)
              if (using_intermediate_buffer)
                unpackanim7long ((BYTE *)wdata,
                                 (LONG *)&dlta[((ULONG *)dlta)[plane + 8]],
                                 bm[which]->Planes[plane],
                                 bm[which]->BytesPerRow,
                                 dirty);
              else
                unpackanim7longnodirty ((BYTE *)wdata,
                                        (LONG *)&dlta[((ULONG *)dlta)[plane + 8]],
                                        bm[which]->Planes[plane],
                                        bm[which]->BytesPerRow);
            else
              if (using_intermediate_buffer)
                unpackanim7word ((BYTE *)wdata,
                                 (WORD *)&dlta[((ULONG *)dlta)[plane + 8]],
                                 bm[which]->Planes[plane],
                                 bm[which]->BytesPerRow >> 1,
                                 dirty);
              else
                unpackanim7wordnodirty ((BYTE *)wdata,
                                        (WORD *)&dlta[((ULONG *)dlta)[plane + 8]],
                                        bm[which]->Planes[plane],
                                        bm[which]->BytesPerRow);
          break;

        case cmpAnim8:		/* 8 */
          if (wdata != (WORD *)dlta)
            if ((anhd->anh_Bits & anfLongdata) != 0)
              if (using_intermediate_buffer)
                unpackanim8long ((LONG *)wdata,
                                 bm[which]->Planes[plane],
                                 bm[which]->BytesPerRow,
                                 dirty);
              else
                unpackanim8longnodirty ((LONG *)wdata,
                                        bm[which]->Planes[plane],
                                        bm[which]->BytesPerRow);
            else
              if (using_intermediate_buffer)
                unpackanim8word ((WORD *)wdata,
                                 bm[which]->Planes[plane],
                                 bm[which]->BytesPerRow,
                                 dirty);
              else
                unpackanim8wordnodirty ((WORD *)wdata,
                                        bm[which]->Planes[plane],
                                        bm[which]->BytesPerRow);
          break;

        case cmpDirect:		/* 0 */
        case cmpXor:		/* 1 */
        case cmpDelta:		/* 4 */
        case cmpStereo:		/* 6 */
        case cmpJ:		/* 74 */
        default:
          die ("%s: Anim type %d not recognised", programname,
                    (int)anhd->anh_Operation);
      }
    }

    if (mystream->f != NULL)
      free (dlta);

    if (using_intermediate_buffer) {

      /* need to wait for cybergraphics TOF to avoid flicker here, but HOW????
         WaitTOF() appears to wait for custom chips which are different
         frequency and phase to cybergraphics display.  */
      if (opt.waittof)
        WaitTOF ();

      if (anhd->anh_Operation == cmpBytedelta ||
          anhd->anh_Operation == cmpAnim7 ||
          anhd->anh_Operation == cmpAnim8)
        blit_opt (bm[which], w->RPort, dirty, which != 0 ? oscan_height : 0);
      else
        BltBitMapRastPort (bm[which], 0, 0, w->RPort, 0,
                           which != 0 ? oscan_height : 0, width, height, 0xc0);
    }

    /* wait for time between frames */
    delay_until (&next_time);
    if (opt.warp)
      add64 (&next_time, &warp_eclocks);
    else
      add64 (&next_time, &eclocks);

    if ((cmapprop = FindProp (iff, ID_ILBM, ID_CMAP)) != NULL)
      load_cmap (s, (UBYTE *)cmapprop->sp_Data, 1 << depth, intended_mode);

    /* MoveScreen (s, which != 0 ? oscan_height : -oscan_height, 0); */

    if (which != 0)
      s->ViewPort.RasInfo->RyOffset += oscan_height;
    else
      s->ViewPort.RasInfo->RyOffset -= oscan_height;
    ScrollVPort (&s->ViewPort);

    totalframes++;

  }  /* end of loop for each frame */

  /* find out and display how long it took */
  ReadEClock (time1);
  if (total_eclocks.ev_hi != 0 || total_eclocks.ev_lo != 0)
    printf ("%s: Intended frames per second = %4.1lf\n", programname,
            1000000.0 * totalframes /
            ((total_eclocks.ev_hi * 4294967296.0 + total_eclocks.ev_lo) *
             micros_per_eclock));
  sub64 (time1, time0);
  printf ("%s: Achieved frames per second = %4.1lf\n", programname,
          1000000.0 * totalframes /
          ((time1->ev_hi * 4294967296.0 + time1->ev_lo) * micros_per_eclock));

  partial_cleanup ();
}

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

static void filerequestloop (struct options *opt)
{
  static char resultstring[128];

  if (AslBase != NULL) {
    while (AslRequest (fr, NULL)) {
      strcpy (resultstring, fr->rf_Dir);
      AddPart (resultstring, fr->rf_File, 128);
      animate_file (resultstring, *opt);
    }
  }
}

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

static LONG argarray[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};

int main (int argc, char *argv[])
{
  struct WBStartup *argmsg;
  struct WBArg *wb_arg;
  char **fnames;
  UWORD ktr;
  struct options opt;

  if (atexit (cleanup) != 0)
    die ("Can't install exit handler");

  GetProgramName (programname, 19);

  CyberGfxBase = OpenLibrary ("cybergraphics.library", 0);

  if ((IFFParseBase = OpenLibrary ("iffparse.library", 0)) == NULL)
    die ("%s: Can't open iff.library", programname);

  if ((AslBase = (struct Library *)OpenLibrary ("asl.library", 38L)) == NULL)
    die ("%s: Can't open asl.library v38", programname);

  if ((fr = (struct FileRequester *)AllocAslRequestTags (ASL_FileRequest,
           ASL_Hail,           (ULONG)programname,
           ASL_Pattern,        (ULONG)"~(#?.info)",
           ASL_OKText,         (ULONG)"Play",
           ASL_CancelText,     (ULONG)"Cancel",
           ASL_FuncFlags,      FILF_PATGAD,
           TAG_DONE)) == NULL)
    die ("%s: Can't allocate file requester\n", programname);

  if ((smr = AllocAslRequestTags (ASL_ScreenModeRequest, TAG_DONE)) == NULL)
    die ("%s: Can't allocate screenmode requester\n", programname);

  /* timer stuff */
  if ((timermp = CreatePort (0, 0)) == NULL)
    die ("%s: Can't create messageport!", programname);
  if ((timerio = (struct timerequest *)CreateExtIO (timermp,
                 sizeof(struct timerequest))) == NULL)
    die ("%s: Can't create External IO!", programname);
  if (timerclosed = OpenDevice (TIMERNAME, UNIT_ECLOCK,
                                (struct IORequest *)timerio, 0))
    die ("%s: Can't open timer.device!", programname);
  TimerBase = (struct Library *)timerio->tr_node.io_Device;
  if ((time = (struct EClockVal *)AllocMem (sizeof(struct EClockVal),
                                          MEMF_CLEAR | MEMF_PUBLIC)) == NULL ||
      (time0 = (struct EClockVal *)AllocMem (sizeof(struct EClockVal),
                                          MEMF_CLEAR | MEMF_PUBLIC)) == NULL ||
      (time1 = (struct EClockVal *)AllocMem (sizeof(struct EClockVal),
                                          MEMF_CLEAR | MEMF_PUBLIC)) == NULL)
    die ("%s: Out of memory", programname);
  micros_per_eclock = 1000000.0 / (double)ReadEClock (time);

  opt.ram = TRUE;
  opt.once = FALSE;
  opt.dbuf = TRUE;
  opt.warp = FALSE;
  opt.modereq = TRUE;
  opt.waittof = FALSE;

  /* parse workbench message or commandline */
  if (argc == 0) {
    argmsg = (struct WBStartup *)argv;
    wb_arg = argmsg->sm_ArgList;
    strcpy (programname, wb_arg->wa_Name);
    parse_tooltypes (wb_arg->wa_Name, &opt);
    if (argmsg->sm_NumArgs <= 1)
      filerequestloop (&opt);
    else {
      wb_arg++;
      for (ktr = 1; ktr < argmsg->sm_NumArgs; ktr++, wb_arg++)
        if (wb_arg->wa_Lock != NULL) {
          olddir = CurrentDir (wb_arg->wa_Lock);
          animate_file (wb_arg->wa_Name, opt);
          CurrentDir (olddir);
          olddir = NULL;
        } else
          animate_file (wb_arg->wa_Name, opt);
      }
  } else {
    if ((rdargs = ReadArgs
        ("FILE/M,DISK/S,RAM/S,ONCE/S,WARP/S,NOMODEREQ/S,WAITTOF/S", argarray,
         NULL)) != NULL) {
      if (argarray[1])
        opt.ram = FALSE;
      if (argarray[2])
        opt.ram = TRUE;
      if (argarray[3])
        opt.once = TRUE;
      if (argarray[4])
        opt.warp = TRUE;
      if (argarray[5])
        opt.modereq = FALSE;
      if (argarray[6])
        opt.waittof = TRUE;
      fnames = (char **)argarray[0];
      if (fnames == NULL || *fnames == NULL)
        filerequestloop (&opt);
      else
        while (*fnames != NULL)
          animate_file (*fnames++, opt);
      FreeArgs (rdargs);
      rdargs = NULL;
    }
  }

  return (0);
}

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