#include <exec/types.h>
#include <intuition/intuition.h>
#include <libraries/gadtools.h>
#include <dos/dos.h>
#include <exec/memory.h>
#include <libraries/iffparse.h>

#include <dos/datetime.h>

#include <stdlib.h>
#include "amigan.h"

#define CHUNKS 24
// Variable-length blocks, and/or blocks that we read entirely, are
// represented as chunksize = 0 and fixed = FALSE.
struct
{   TEXT   formname[5], chunkname[5];
    ULONG  bytes;
    ABOOL  fixed;
} chunks[CHUNKS + 1] =
{ {"    ", "(c) ",  0, FALSE}, // "    " means any
  {"    ", "ANNO",  0, FALSE},
  {"    ", "AUTH",  0, FALSE},
  {"    ", "NAME",  0, FALSE},
  {"8SVX", "CHAN",  0, FALSE},
  {"8SVX", "PAN ",  0, FALSE},
  {"8SVX", "FADE",  0, FALSE},
  {"8SVX", "SEQN",  0, FALSE},
  {"8SVX", "VHDR",  0, FALSE},
  {"AIFF", "APPL",  4, TRUE },
  {"AIFF", "COMM",  8, TRUE }, // we currently ignore 'SampleRate' field
  {"AIFF", "COMT",  0, FALSE},
  {"AIFF", "MARK",  0, FALSE},
  {"AIFF", "SSND",  8, TRUE },
  {"AIFF", "INST",  0, FALSE},
  {"FTXT", "FONS",  0, FALSE},
  {"!!!!", "BMHD",  0, FALSE}, // "!!!!" means "ACBM or ILBM"
  {"!!!!", "CAMG",  0, FALSE},
  {"!!!!", "GRAB",  0, FALSE},
  {"!!!!", "DPI ",  0, FALSE},
  {"!!!!", "DEST",  0, FALSE},
  {"!!!!", "EPSF",  8, TRUE },
  {"!!!!", "SPRT",  0, FALSE},
  {"SMUS", "INS1",  0, FALSE},
  {"SMUS", "SHDR",  0, FALSE}
};

#define ITEMS 255
MODULE struct
{   TEXT   name[5];
    ULONG  bytes;
    UBYTE* DataPtr;
} contents[ITEMS + 1];

#define FORMS         89

#define CD             1
#define OBSOLETE       2
#define PRIVATE        4
#define PROPOSAL       8
#define RESERVED      16
#define RKM           32
#define STANDARD      64
#define UNREGISTERED 128

#define IFFXCHARS     17

MODULE void iffquery(void);
MODULE void PrintTopChunk(void);
MODULE void readiff(void);
MODULE void tellchunk(UWORD which);
MODULE void chunkdesc(STRPTR desc);
MODULE void binarize(UBYTE data);
MODULE ULONG getulong(UBYTE* start);
MODULE SLONG getslong(UBYTE* start);
MODULE ULONG getuword(UBYTE* start);
MODULE SLONG getsword(UBYTE* start);
MODULE SLONG getsbyte(UBYTE* start);

/* Text error messages for possible IFFERR_#? returns from various IFF
 * routines. To get the index into this array, take your IFFERR code,
 * negate it, and subtract one.
 * idx = -error - 1;
 */
STRPTR errormsgs[] =
{   "End of file (not an error).",
    "End of context (not an error).",
    "No lexical scope.",
    "Insufficient memory.",
    "Stream read error.",
    "Stream write error.",
    "Stream seek error.",
    "File is corrupt.",
    "IFF syntax error.",
    "Not an IFF file.",
    "Required call-back hook missing.",
    "Return to client. You should never see this."
};

// from rp.c
IMPORT ABOOL             fillwindows;
IMPORT SBYTE             page;
IMPORT struct NewGadget* Gadget;
IMPORT struct Gadget    *BU99_Right,
                        *PrevGadPtr;
IMPORT struct Window*    MainWindowPtr;
IMPORT TEXT              aslresult[VLONGFIELD + 1],
                         weekdaystring[LEN_DATSTRING],
                         datestring[LEN_DATSTRING],
                         timestring[LEN_DATSTRING];

MODULE ABOOL             known;
MODULE struct IFFHandle* IFFHandle             = NULL;
MODULE struct List      *List1Ptr              = NULL,
                        *List2Ptr              = NULL;
MODULE struct Gadget  *TE61_Contributor        = NULL,
                      *CB61_CD                 = NULL,
                      *CB61_Obsolete           = NULL,
                      *TE61_FORM               = NULL,
                      *TE61_FORMID             = NULL,
                      *CB61_Private            = NULL,
                      *CB61_Proposal           = NULL,
                      *CB61_Reserved           = NULL,
                      *CB61_RKM                = NULL,
                      *CB61_Standard           = NULL,
                      *CB61_Unregistered       = NULL,
                      *TE61_Status             = NULL,
                      *ST61_Pathname           = NULL,
                      *BU61_PathnameASL        = NULL,
                      *LV61_List1              = NULL,
                      *LV61_List2              = NULL,
                      *TE61_ChunkDesc          = NULL;
MODULE TEXT  queryform[5] = "",
             tempstring1[VLONGFIELD + 1],
             tempstring2[13];
MODULE ULONG items = 0; // value of this is actual number of items allocated
MODULE UWORD selectedform = 0;
MODULE struct
{   ABOOL cd, rkm, obsolete, private, proposal, reserved, standard, unregistered;
    TEXT  pathname[VLONGFIELD + 1];
} iff;
MODULE struct
{   UBYTE  flags;
    TEXT   name[5];
    STRPTR desc;
    STRPTR contributor;
} form[FORMS + 1] =
{   STANDARD | RKM | CD,      "8SVX", "EA IFF 85 8-bit sound sample form",                    "EA/CBM",
    RKM | CD,                 "AIFF", "Audio 1-32 bit samples",                               "Apple",
    RKM | CD,                 "ACBM", "Amiga Contiguous Bitmap",                              "CBM",
    UNREGISTERED,             "AHAM", "?",                                                    "?",
    RKM | CD,                 "ANBM", "Animated BitMap form",                                 "EA",
    RKM | CD,                 "ANIM", "CEL ANIMation form",                                   "Sparta/Aegis",
    PROPOSAL,                 "ARC ", "ARChive format",                                       "?",
    UNREGISTERED,             "ARES", "?",                                                    "?",
    RESERVED,                 "ATXT", "Temporarily reserved",                                 "?",
    PRIVATE,                  "AVCF", "AmigaVision Flow",                                     "CBM",
    NULL,                     "BANK", "Soundquest Editor/Librarian MIDI Sysex dump",          "?",
    NULL,                     "BBSD", "BBS Database",                                         "Phalanx Software",
    PRIVATE,                  "C100", "?",                                                    "Cloanto",
    STANDARD,                 "CAT ", "EA IFF 85 group identifier",                           "EA/CBM",
    PROPOSAL,                 "CELP", "Compressed ZyXEL voice data",                          "?",
    RESERVED,                 "CHBM", "Chunky bitmap",                                        "Eric Lavitsky",
    NULL,                     "CLIP", "CAT CLIP to hold various formats in clipboard",        "CBM",
    PROPOSAL | CD,            "CMUS", "Common MUsical Score",                                 "Talin (David Joiner)",
    NULL,                     "CPFM", "Cloanto Personal FontMaker",                           "Cloanto",
    NULL,                     "DCCL", "DCTV paint clip",                                      "?",
    NULL,                     "DCPA", "DCTV paint palette",                                   "?",
    NULL,                     "DCTV", "DCTV raw picture file",                                "?",
    PRIVATE,                  "DECK", "Inovatronics CanDo",                                   "Innovatronics",
    CD,                       "DEEP", "Chunky pixel image files (used in TV Paint)",          "Amiga Centre Scotland",
    RKM | CD,                 "DR2D", "2D object standard format",                            "Ross Cunniff & John Orr",
    RESERVED,                 "DRAW", "?",                                                    "Jim Bayless",
    CD,                       "DTYP", "DataTYPes identification",                             "Amiga, Inc.",
    PROPOSAL | CD,            "EXEC", "Executable (loadseg-able) code",                       "Chris Ludwig",
    RKM | CD,                 "FANT", "Fantavision movie format",                             "Broderbund",
    OBSOLETE | PRIVATE,       "FAX3", "Facsimile image",                                      "GPSoftware",
    CD,                       "FAXX", "Facsimile image",                                      "Atlantis Design Group",
    RESERVED,                 "FIGR", "Deluxe Video",                                         "EA",
    NULL,                     "FILM", "LIST FILM - stores ILBMs with interleaved 8SVX audio", "?",
    RESERVED,                 "FNTR", "Raster font",                                          "EA",
    RESERVED,                 "FNTV", "Vector font",                                          "EA",
    STANDARD,                 "FORM", "Group identifier",                                     "EA/CBM",
    STANDARD | RKM,           "FTXT", "Formatted TeXT",                                       "EA/CBM",
    PROPOSAL | PRIVATE,       "GRYP", "Byteplane storage",                                    "?",
    RESERVED,                 "GSCR", "General music SCoRe",                                  "EA",
    PROPOSAL | PRIVATE,       "GUI ", "User interface storage",                               "?",
    RKM | CD,                 "HEAD", "Flow idea processor",                                  "New Horizons Software",
    STANDARD | RKM | CD,      "ILBM", "EA IFF 85 raster bitmap form",                         "EA/CBM",
    RESERVED,                 "IOBJ", "?",                                                    "Seven Seas Software",
    RESERVED,                 "IODK", "?",                                                    "Merging Technologies",
    RESERVED,                 "ITRF", "?",                                                    "?",
    RESERVED,                 "JMOV", "?",                                                    "Merging Technologies",
    STANDARD,                 "LIST", "EA IFF 85 group identifier",                           "EA/CBM",
    RESERVED,                 "MFAX", "Facsimile?",                                           "TKR GmbH & Co.",
    NULL,                     "MIDI", "MIDI?",                                                "Circum Design",
    PRIVATE,                  "MOVI", "LIST MOVIe?",                                          "?",
    PRIVATE,                  "MSCX", "Music-X format",                                       "?",
    RESERVED,                 "MSMP", "Temporarily reserved",                                 "?",
    RKM | CD,                 "MTRX", "Numerical data storage (MathVision)",                  "Seven Seas Software",
    NULL,                     "NSEQ", "Numerical sequence",                                   "Stockhausen GmbH",
    PROPOSAL | CD,            "OB3D", "Standard 3D object",                                   "?",
    RKM | CD,                 "PGTB", "ProGram TraceBack",                                    "SAS Institute",
    RESERVED,                 "PICS", "Macintosh picture",                                    "EA",
    RESERVED | OBSOLETE,      "PLBM", "?",                                                    "EA",
    RESERVED | PROPOSAL | CD, "PMBC", "24-bit accurate images",                               "Black Belt Systems",
    RESERVED | PRIVATE,       "PREF", "User preferences data",                                "CBM",
    STANDARD,                 "PROP", "Group identifier",                                     "EA/CBM",
    RKM | CD,                 "PRSP", "DPaint IV perspective move form",                      "EA",
    NULL,                     "PTCH", "Patch file",                                           "SAS Institute",
    RESERVED,                 "PTXT", "Temporarily reserved",                                 "?",
    NULL,                     "RGB4", "4-bit RGB",                                            "?",
    RKM | CD,                 "RGB8", "RGB image (Turbo Silver)",                             "Impulse",
    RKM | CD,                 "RGBN", "RGB image (Turbo Silver)",                             "Impulse",
    RESERVED,                 "RGBX", "Temporarily reserved",                                 "?",
    PRIVATE,                  "ROXN", "Animation",                                            "?",
    RKM | CD,                 "SAMP", "Sampled sound",                                        "Jim Fiore & Jeff Glatt",
    PRIVATE,                  "SC3D", "SCene format (Sculpt-3D)",                             "?",
    PRIVATE,                  "SHAK", "SHAKespeare format",                                   "?",
    RESERVED | PRIVATE,       "SHO1", "?",                                                    "Gary Bonham",
    RESERVED | PRIVATE,       "SHOW", "?",                                                    "Gary Bonham",
    STANDARD | RKM,           "SMUS", "Simple MUsic Score",                                   "EA/CBM",
    CD,                       "SPLT", "File SPLiTting system",                                "ASDG",
    RESERVED,                 "SSRE", "?",                                                    "Merging Technologies",
    UNREGISTERED,             "SWRT", "?",                                                    "?",
    NULL,                     "SYTH", "SoundQuest Master Librarian MIDI System driver",       "?",
    RESERVED,                 "TCDE", "?",                                                    "Merging Technologies",
    RKM | CD,                 "TDDD", "3D rendering data (Turbo Silver)",                     "Impulse",
    UNREGISTERED,             "TERM", "?",                                                    "?",
    CD,                       "TMUI", "ToolMaker User Interface",                             "Michael Erwin",
    CD,                       "TREE", "Arbitrary data structures as trees (or nested lists)", "Stefan Reisner",
    CD | PROPOSAL,            "TRKR", "TRacKeR style music module",                           "Full Tilt Entertainment",
    RESERVED,                 "USCR", "Uhuru SCoRe",                                          "EA",
    RESERVED,                 "UVOX", "Uhuru Macintosh VOiXe",                                "EA",
    PRIVATE,                  "VDEO", "Deluxe ViDEO",                                         "EA",
    RKM | CD,                 "WORD", "ProWrite document",                                    "New Horizons Software",
    CD,                       "YUVN", "For storage of Y:U:V image data",                      "MacroSystem"
};

AGLOBAL void iff1(void)
{   verynewwindow
    (   IFF1WIDTH, IFF1HEIGHT,
        "Report+: IFF Registry",
        BUTTONIDCMP | STRINGIDCMP | LISTVIEWIDCMP
    );
    if (fillwindows)
    {   SetAPen(MainWindowPtr->RPort, 0);
        RectFill(MainWindowPtr->RPort,  10, 120,  10 + 160, 120 +  68 - 1); // LV61_List1
        RectFill(MainWindowPtr->RPort, 180, 120, 180 + 450, 120 +  68 - 1); // LV61_List2
        RectFill(MainWindowPtr->RPort, 130,  22, 130 +  48,  22 +  12); // TE61_FORMID
        RectFill(MainWindowPtr->RPort, 130,  34, 130 + 440,  34 +  12); // TE61_Description
        RectFill(MainWindowPtr->RPort, 130,  46, 130 + 440,  46 +  12); // TE61_Contributor
        RectFill(MainWindowPtr->RPort, 130,  58, 130 + 440,  58 +  12); // TE61_ChunkDesc
        RectFill(MainWindowPtr->RPort, 130, 200, 130 + 350, 200 +  12); // TE61_Status
    }

    /* FORM ID */
    setgadget(130, 22, 48, 12, "FORM ID:", NULL);
    TE61_FORMID = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GTTX_Text, "",
        GTTX_Border, TRUE,
        TAG_DONE
    );
    /* FORM description */
    setgadget(130, 34, 440, 12, "Description:", NULL);
    TE61_FORM = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GTTX_Text, "-",
        GTTX_Border, TRUE,
        TAG_DONE
    );
    /* contributor name */
    setgadget(130, 46, 440, 12, "Contributor:", NULL);
    TE61_Contributor = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GTTX_Text, "-",
        GTTX_Border, TRUE,
        TAG_DONE
    );
    /* chunk description */
    setgadget(130, 58, 440, 12, "Chunk info:", NULL);
    TE61_ChunkDesc = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GTTX_Text, "-",
        GTTX_Border, TRUE,
        TAG_DONE
    );

    /* flags */
    setgadget(130, 76, 0, 0, "Obsolete:", NULL);
    CB61_Obsolete = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GA_Disabled, TRUE,
        GTCB_Checked, iff.obsolete,
        TAG_DONE
    );
    setgadget(130, 90, 0, 0, "Private:", NULL);
    CB61_Private = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GA_Disabled, TRUE,
        GTCB_Checked, iff.private,
        TAG_DONE
    );
    setgadget(282, 76, 0, 0, "Proposal:", NULL);
    CB61_Proposal = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GA_Disabled, TRUE,
        GTCB_Checked, iff.proposal,
        TAG_DONE
    );
    setgadget(282, 90, 0, 0, "Reserved:", NULL);
    CB61_Reserved = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GA_Disabled, TRUE,
        GTCB_Checked, iff.reserved,
        TAG_DONE
    );
    setgadget(434, 76, 0, 0, "Standard:", NULL);
    CB61_Standard = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GA_Disabled, TRUE,
        GTCB_Checked, iff.standard,
        TAG_DONE
    );
    setgadget(434, 90, 0, 0, "Unregistered:", NULL);
    CB61_Unregistered = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GA_Disabled, TRUE,
        GTCB_Checked, iff.unregistered,
        TAG_DONE
    );
    setgadget(588, 76, 0, 0, "RKM:", NULL);
    CB61_RKM = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GA_Disabled, TRUE,
        GTCB_Checked, iff.rkm,
        TAG_DONE
    );
    setgadget(588, 90, 0, 0, "CD-ROM:", NULL);
    CB61_CD = PrevGadPtr = (struct Gadget *) CreateGadget
    (   CHECKBOX_KIND,
        PrevGadPtr,
        &Gadget,
        GA_Disabled, TRUE,
        GTCB_Checked, iff.cd,
        TAG_DONE
    );

    setgadget( 10, 120, 160, 68, "Chunks:", NULL);
    LV61_List1 = PrevGadPtr = (struct Gadget *) CreateGadget
    (   LISTVIEW_KIND,
        PrevGadPtr,
        &Gadget,
        GTLV_Labels, NULL,
        GTLV_ShowSelected, NULL,
        TAG_DONE
    );
    setgadget(180, 120, 450, 68, "Chunk contents:", NULL);
    LV61_List2 = PrevGadPtr = (struct Gadget *) CreateGadget
    (   LISTVIEW_KIND,
        PrevGadPtr,
        &Gadget,
        GTLV_Labels, NULL,
        TAG_DONE
    );

    /* status */
    setgadget(130, 200, 350, 12, "Status:", NULL);
    TE61_Status = PrevGadPtr = (struct Gadget *) CreateGadget
    (   TEXT_KIND,
        PrevGadPtr,
        &Gadget,
        GTTX_Text, "Ready.",
        GTTX_Border, TRUE,
        TAG_DONE
    );

    /* pathname */
    setgadget(130, 214, 320, 12, "_Pathname:", NULL);
    ST61_Pathname = PrevGadPtr = (struct Gadget *) CreateGadget
    (   STRING_KIND,
        PrevGadPtr,
        &Gadget,
        GTST_String, &(iff.pathname),
        GTST_MaxChars, VLONGFIELD,
        GA_TabCycle, TRUE,
        GA_Immediate, TRUE,
        GT_Underscore, '_',
        TAG_DONE
    );
    /* pathname... */
    setgadget(452, 214, 28, 12, "_...", NULL);
    BU61_PathnameASL = PrevGadPtr = (struct Gadget *) CreateGadget
    (   BUTTON_KIND,
        PrevGadPtr,
        &Gadget,
        GT_Underscore, '_',
        TAG_DONE
    );

    /* There are 36 gadgets:
    BU99_Right        1= 1
    TE61_FORMID       2= 3
    TE61_FORM         2= 5
    TE61_Contributor  2= 7
    CB61_CD           2= 9
    CB61_RKM          2=11
    CB61_Obsolete     2=13
    CB61_Private      2=15
    CB61_Proposal     2=17
    CB61_Reserved     2=19
    CB61_Standard     2=21
    CB61_Unregistered 2=23
    LV61_List1        4=27
    LV61_List2        4=31
    ST61_Pathname     2=33
    BU61_PathnameASL  1=34
    TE61_ChunkDesc    2=36 */
    drawgadgets(32);

    ActivateGadget(ST61_Pathname, MainWindowPtr, NULL);
    loop();
    closewindow();
}

AGLOBAL void iff_loop(ULONG class, struct Gadget* addr, UWORD code, UWORD qual)
{   if (class == IDCMP_VANILLAKEY)
    {   code = toupper(code);
        if (code == ESCAPE)
            page = 0;
        elif (code == 'P')
            ActivateGadget(ST61_Pathname, MainWindowPtr, NULL);
        elif (code == '.')
        {   asl();
            if (aslresult[0])
            {   strcpy(iff.pathname, aslresult);
                GT_SetGadgetAttrs
                (   ST61_Pathname,
                    MainWindowPtr,
                    NULL,
                    GTST_String, iff.pathname,
                    TAG_DONE
                );
                readiff();
            }
    }   }
    elif (class == IDCMP_GADGETUP)
    {   /* IDCMP_GADGETUP is sent by the string gadget
        when the user presses RETURN, ENTER, Help, Tab
        or Shift-Tab inside the string gadget. */
        
        if (addr == BU99_Right)
            page = 0;
        elif (addr == ST61_Pathname)
	{   strcpy
            (   iff.pathname,
                ((struct StringInfo *) ST61_Pathname->SpecialInfo)->Buffer
	    );
            readiff();
        } elif (addr == BU61_PathnameASL)
        {   asl();
            if (aslresult[0])
            {   strcpy(iff.pathname, aslresult);
                GT_SetGadgetAttrs
                (   ST61_Pathname,
                    MainWindowPtr,
                    NULL,
                    GTST_String, iff.pathname,
                    TAG_DONE
                );
        }   }
        elif (addr == LV61_List1)
        {   tellchunk(code); // ordinal number of item
}   }   }

MODULE void iffquery(void)
{   ULONG i;

    /* Make any corrections to input now, to make it
    easier to search the database. */
    for (i = 0; i <= 3; i++)
        if (queryform[i] >= 97 && queryform[i] <= 122)
            queryform[i] -= 32;
        elif (queryform[i] == 0)
            queryform[i] = ' ';
    queryform[4] = 0;
    GT_SetGadgetAttrs
    (   TE61_FORMID,
        MainWindowPtr,
        NULL,
        GTTX_Text, queryform,
        TAG_DONE
    );
    selectedform = (UWORD) -1;
    for (i = 0; i <= FORMS; i++)
    if (queryform[0] == form[i].name[0] &&
        queryform[1] == form[i].name[1] &&
        queryform[2] == form[i].name[2] &&
        queryform[3] == form[i].name[3])
    {   selectedform = i;
        break;
    }
    if (selectedform == (UWORD) -1)
    {   iff.cd           =
        iff.rkm          =
        iff.private      =
        iff.proposal     =
        iff.obsolete     =
        iff.reserved     =
        iff.standard     =
        iff.unregistered = FALSE;
        GT_SetGadgetAttrs
        (   TE61_FORM,
            MainWindowPtr,
            NULL,
            GTTX_Text, "-",
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   TE61_Contributor,
            MainWindowPtr,
            NULL,
            GTTX_Text, "-",
            TAG_DONE
        );
    } else
    {   iff.cd           = form[selectedform].flags & CD;
        iff.rkm          = form[selectedform].flags & RKM;
        iff.private      = form[selectedform].flags & PRIVATE;
        iff.proposal     = form[selectedform].flags & PROPOSAL;
        iff.obsolete     = form[selectedform].flags & OBSOLETE;
        iff.reserved     = form[selectedform].flags & RESERVED;
        iff.standard     = form[selectedform].flags & STANDARD;
        iff.unregistered = form[selectedform].flags & UNREGISTERED;
        GT_SetGadgetAttrs
        (   TE61_FORM,
            MainWindowPtr,
            NULL,
            GTTX_Text, form[selectedform].desc,
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   TE61_Contributor,
            MainWindowPtr,
            NULL,
            GTTX_Text, form[selectedform].contributor,
            TAG_DONE
        );
    }
    GT_SetGadgetAttrs
    (   CB61_CD,
        MainWindowPtr,
        NULL,
        GTCB_Checked, iff.cd,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   CB61_RKM,
        MainWindowPtr,
        NULL,
        GTCB_Checked, iff.rkm,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   CB61_Obsolete,
        MainWindowPtr,
        NULL,
        GTCB_Checked, iff.obsolete,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   CB61_Private,
        MainWindowPtr,
        NULL,
        GTCB_Checked, iff.private,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   CB61_Proposal,
        MainWindowPtr,
        NULL,
        GTCB_Checked, iff.proposal,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   CB61_Reserved,
        MainWindowPtr,
        NULL,
        GTCB_Checked, iff.reserved,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   CB61_Standard,
        MainWindowPtr,
        NULL,
        GTCB_Checked, iff.standard,
        TAG_DONE
    );
    GT_SetGadgetAttrs
    (   CB61_Unregistered,
        MainWindowPtr,
        NULL,
        GTCB_Checked, iff.unregistered,
        TAG_DONE
    );
}

MODULE void readiff(void)
{   LONG  error;
    ULONG i;

    // Detach list, free everything, then make new list and then attach.
    GT_SetGadgetAttrs
    (   LV61_List1,
        MainWindowPtr,
        NULL,
        GTLV_Labels, (UWORD) ~0,
        TAG_DONE
    );
    if (List1Ptr)
    {   FreeNameNodes(List1Ptr);
        FreeMem(List1Ptr, sizeof(struct List));
        List1Ptr = NULL;
    }
    for (i = 0; i < items; i++)
    {   if (contents[i].DataPtr)
        {   FreeMem(contents[i].DataPtr, contents[i].bytes);
            contents[i].DataPtr = NULL;
    }   }
    items = 0;
    if (!(List1Ptr = AllocMem(sizeof(struct List), MEMF_CLEAR)))
        rq("Out of memory!");
    NewList(List1Ptr);

    /* Allocate IFF_File structure. */
    if (!(IFFHandle = AllocIFF()))
    {   DisplayBeep(NULL);
        GT_SetGadgetAttrs
        (   TE61_Status,
            MainWindowPtr,
            NULL,
            GTTX_Text, "AllocIFF() failed!",
            TAG_DONE
        );
        goto bye;
    }

    /* Set up IFF_File for AmigaDOS I/O.  */
    if (!(IFFHandle->iff_Stream = Open(iff.pathname, MODE_OLDFILE)))
    {   DisplayBeep(NULL);
        GT_SetGadgetAttrs
        (   TE61_Status,
            MainWindowPtr,
            NULL,
            GTTX_Text, "Can't open file!",
            TAG_DONE
        );
        goto bye;
    }
    InitIFFasDOS(IFFHandle);

    /* Start the IFF transaction. */
    if (error = OpenIFF(IFFHandle, IFFF_READ))
    {   DisplayBeep(NULL);
        GT_SetGadgetAttrs
        (   TE61_Status,
            MainWindowPtr,
            NULL,
            GTTX_Text, "OpenIFF() failed!",
            TAG_DONE
        );
        goto bye;
    }

    while (1)
    {   /* IFFPARSE_RAWSTEP permits us to have precision monitoring of the
         * parsing process, which is necessary if we wish to print the
         * structure of an IFF file.
         *   ParseIFF() with _RAWSTEP will return the following things for
         * the following reasons:
         *
         * Return code:                 Reason:
         * 0                            Entered new context.
         * IFFERR_EOC                   About to leave a context.
         * IFFERR_EOF                   Encountered end-of-file.
         * <anything else>              A parsing error.
         */
        error = ParseIFF(IFFHandle, IFFPARSE_RAWSTEP);

        /*
         * Since we're only interested in when we enter a context, we
         * "discard" end-of-context (_EOC) events.
         */
        if (error == IFFERR_EOC)
            continue;
        elif (error)
        {   // Leave the loop if there is any other error.
            break;
        }

        /* If we get here, error was zero. Print out the current state of
         * affairs. */
        PrintTopChunk();
    }

    /* If error was IFFERR_EOF, then the parser encountered the end of
     * the file without problems. Otherwise, we print a diagnostic.
     */
    if (error == IFFERR_EOF)
    {   GT_SetGadgetAttrs
        (   TE61_Status,
            MainWindowPtr,
            NULL,
            GTTX_Text, "Done.",
            TAG_DONE
        );
        GT_SetGadgetAttrs
        (   LV61_List1,
            MainWindowPtr,
            NULL,
            GTLV_Labels, List1Ptr,
            GTLV_Selected, (UWORD) ~0,
            TAG_DONE
        );
    } else
    {   GT_SetGadgetAttrs
        (   TE61_Status,
            MainWindowPtr,
            NULL,
            GTTX_Text, errormsgs[-error - 1],
            TAG_DONE
        );
    }

bye:
    if (IFFHandle)
    {   /* Terminate the IFF transaction with the stream. Free all
         * associated structures. */
        CloseIFF(IFFHandle);
        // Close the stream itself.
        if (IFFHandle->iff_Stream)
        {   Close(IFFHandle->iff_Stream);
        }
        // Free the IFF_File structure itself.
        FreeIFF(IFFHandle);
        IFFHandle = NULL;
}   }

AGLOBAL void iff_exit(void)
{   ULONG i;

    if (IFFHandle)
    {   /* Terminate the IFF transaction with the stream. Free all
         * associated structures. */
        CloseIFF(IFFHandle);
        // Close the stream itself.
        if (IFFHandle->iff_Stream)
        {   Close(IFFHandle->iff_Stream);
        }
        // Free the IFF_File structure itself.
        FreeIFF(IFFHandle);
        IFFHandle = NULL;
    }

    if (List2Ptr)
    {   FreeNameNodes(List2Ptr);
        FreeMem(List1Ptr, sizeof(struct List));
        List2Ptr = NULL;
    }
    if (List1Ptr)
    {   FreeNameNodes(List1Ptr);
        FreeMem(List1Ptr, sizeof(struct List));
        List1Ptr = NULL;
    }
    for (i = 0; i < items; i++)
    {   if (contents[i].DataPtr)
        {   FreeMem(contents[i].DataPtr, contents[i].bytes);
            contents[i].DataPtr = NULL;
    }   }
    items = 0;
}

MODULE void PrintTopChunk(void)
{   struct ContextNode* top;
    short               i;
    char                idbuf[5];
    ULONG               length;

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

    /* Print spaces equivalent to the current nesting depth of chunks
     * processed so far.
     *   This will cause nested chunks to be printed out indented. */

    strcpy(tempstring1, "");
    for (i = IFFHandle->iff_Depth - 1; i--; )
        strcat(tempstring1, " ");

    strcpy(contents[items].name, IDtoStr(top->cn_ID, idbuf));
    contents[items].DataPtr = NULL;

    /* Print out the current chunk's ID and size. */
    strcat(tempstring1, IDtoStr(top->cn_ID, idbuf));
    stcl_d(tempstring2, top->cn_Size);

    length = IFFXCHARS - strlen(tempstring1) - strlen(tempstring2);
    for (i = 1; i <= length; i++)
    {   strcat(tempstring1, " ");
    }
    strcat(tempstring1, tempstring2);

    /* Print the current chunk's type, with a newline. */
    AddNameToTail(List1Ptr, tempstring1);

    if (!strcmp(IDtoStr(top->cn_ID, idbuf), "FORM"))
    {   strcpy(queryform, IDtoStr(top->cn_Type, idbuf));
        iffquery();
    } else
    {   for (i = 0; i <= CHUNKS; i++)
        {   if (
                (       !strcmp(queryform, chunks[i].formname)
                 ||     !strcmp("    ", chunks[i].formname)
                 || (   !strcmp("!!!!", chunks[i].formname)
                     && (!strcmp(queryform, "ILBM") || !strcmp(queryform, "ACBM"))
                    )
                )
             &&         !strcmp(IDtoStr(top->cn_ID, idbuf), chunks[i].chunkname)
               )
            {   if (!chunks[i].fixed)
                {   contents[items].bytes = top->cn_Size;
                } else
                {   contents[items].bytes = chunks[i].bytes;
                }
                if (!(contents[items].DataPtr = AllocMem(contents[items].bytes, MEMF_CLEAR)))
                {   rq("Out of memory!");
                }
                if (ReadChunkBytes(IFFHandle, contents[items].DataPtr, contents[items].bytes) < 0)
                {   rq("ReadChunkBytes() failed!");
                }
    }   }   }
    items++;
}

MODULE void tellchunk(UWORD which)
{   SLONG           svalue;
    ULONG           i, uvalue, markers, currentpos;
    struct DateTime DateTime;

    GT_SetGadgetAttrs
    (   LV61_List2,
        MainWindowPtr,
        NULL,
        GTLV_Labels, (UWORD) ~0,
        TAG_DONE
    );
    if (List2Ptr)
    {   FreeNameNodes(List2Ptr);
        FreeMem(List2Ptr, sizeof(struct List));
        List2Ptr = NULL;
    }
    if (!(List2Ptr = AllocMem(sizeof(struct List), MEMF_CLEAR)))
        rq("Out of memory!");
    NewList(List2Ptr);

    known = FALSE;

    if (!stricmp(contents[which].name, "FORM"))
    {   chunkdesc("-");
    } elif (!stricmp(contents[which].name, "(c) "))
    {   chunkdesc("© Copyright");
        strcpy(tempstring1, "© Copyright:        ");
        strcat(tempstring1, contents[which].DataPtr);
        AddNameToTail(List2Ptr, tempstring1);
    } elif (!stricmp(contents[which].name, "AUTH"))
    {   chunkdesc("Author");
        strcpy(tempstring1, "Author:             ");
        strcat(tempstring1, contents[which].DataPtr);
        AddNameToTail(List2Ptr, tempstring1);
    } elif (!stricmp(contents[which].name, "ANNO"))
    {   chunkdesc("Annotation");
        strcpy(tempstring1, "Annotation:         ");
        strcat(tempstring1, contents[which].DataPtr);
        AddNameToTail(List2Ptr, tempstring1);
    } elif (!strcmp(contents[which].name, "NAME"))
    {   chunkdesc("Name");
        strcpy(tempstring1, "Name:               ");
        strcat(tempstring1, (STRPTR) contents[which].DataPtr + 4); // STRPTR
        AddNameToTail(List2Ptr, tempstring1);
    } elif (!strcmp(queryform, "FTXT"))
    {   if (!strcmp(contents[which].name, "FONS"))
        {   chunkdesc("Font specifier");

            strcpy(tempstring1, "ID number:          ");
            stcl_d(tempstring2, contents[which].DataPtr[0]);
            strcat(tempstring1, tempstring2);
            if (contents[which].DataPtr[0] > 9)
            {   strcat(tempstring1, " (invalid)");
            }
            AddNameToTail(List2Ptr, tempstring1);

            // Next is a pad byte ([1]).

            strcpy(tempstring1, "Proportional?       ");
            uvalue = contents[which].DataPtr[2];
            if (uvalue == 0)
            {   strcat(tempstring1, "Unknown");
            } elif (uvalue == 1)
            {   strcat(tempstring1, "No");
            } elif (uvalue == 2)
            {   strcat(tempstring1, "Yes");
            } else strcat(tempstring1, "?"); // not the same as `Unknown', of course!
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Serifs?             ");
            uvalue = contents[which].DataPtr[3];
            if (uvalue == 0)
            {   strcat(tempstring1, "Unknown");
            } elif (uvalue == 1)
            {   strcat(tempstring1, "No");
            } elif (uvalue == 2)
            {   strcat(tempstring1, "Yes");
            } else strcat(tempstring1, "?"); // not the same as `Unknown', of course!
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Font name:          ");
            strcat(tempstring1, &(contents[which].DataPtr[4]));
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "CHRS"))
        {   chunkdesc("Stream of text");
    }   }
    elif (!strcmp(queryform, "SMUS"))
    {   if (!strcmp(contents[which].name, "SHDR"))
        {   chunkdesc("Global information for the score");

            strcpy(tempstring1, "Tempo:              ");
            uvalue = getuword(contents[which].DataPtr);
            uvalue = (uvalue << 7);
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            strcat(tempstring1, " bpm");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Volume:             ");
            uvalue = contents[which].DataPtr[2];
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            strcat(tempstring1, "/127");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Tracks:             ");
            uvalue = contents[which].DataPtr[3];
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "INS1"))
        {   chunkdesc("Identifies an instrument to use");

            strcpy(tempstring1, "Register number:    ");
            stcl_d(tempstring2, contents[which].DataPtr[0]); // UBYTE
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Reference type:     ");
            if (contents[which].DataPtr[1] == 0) // UBYTE
            {   strcat(tempstring1, "Find by name");
            } elif (contents[which].DataPtr[1] == 1)
            {   strcat(tempstring1, "Find on MIDI");
            } else strcat(tempstring1, "?");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "MIDI channel:       ");
            if (contents[which].DataPtr[1] == 1) // if find on MIDI
            {   stcl_d(tempstring2, contents[which].DataPtr[2]); // UBYTE
                strcat(tempstring1, tempstring2);
            } else strcat(tempstring1, "n/a");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "MIDI preset:        ");
            if (contents[which].DataPtr[1] == 1) // if find on MIDI
            {   stcl_d(tempstring2, contents[which].DataPtr[3]); // UBYTE
                strcat(tempstring1, tempstring2);
            } else strcat(tempstring1, "n/a");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Instrument name:    ");
            strcat(tempstring1, (STRPTR) contents[which].DataPtr + 4); // STRPTR
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "TRAK"))
        {   chunkdesc("Linear stream of events");
        } elif (!strcmp(contents[which].name, "INST"))
        {   chunkdesc("Obsolete; use INS1 instead");
        } elif (!strcmp(contents[which].name, "IRev")) // this could also perhaps be IREV
        {   chunkdesc("Instant Music private chunk");
        } elif (!strcmp(contents[which].name, "BIAS"))
        {   chunkdesc("Instant Music private chunk");
    }   }
    elif (!strcmp(queryform, "AIFF"))
    {   if (!strcmp(contents[which].name, "MIDI"))
        {   chunkdesc("Stores MIDI data");
        } elif (!strcmp(contents[which].name, "MARK"))
        {   chunkdesc("Points to positions in the sound data");

            strcpy(tempstring1, "Markers:             ");
            svalue = getsword(contents[which].DataPtr);
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            if (svalue < 0)
            {   strcat(tempstring1, "!");
            }
            AddNameToTail(List2Ptr, tempstring1);
            markers = svalue;
            if (markers > 0)
            {   currentpos = 2;
                for (i = 1; i <= markers; i++)
                {   strcpy(tempstring1, "Marker ");
                    stcl_d(tempstring2, i);
                    strcat(tempstring1, tempstring2);
                    strcat(tempstring1, ":");
                    AddNameToTail(List2Ptr, tempstring1);

                    strcpy(tempstring1, " ID:                 ");
                    svalue = getsword(&(contents[which].name[currentpos]));
                    stcl_d(tempstring2, svalue);
                    strcat(tempstring1, tempstring2);
                    if (svalue < 1)
                    {   strcat(tempstring1, "!");
                    }
                    AddNameToTail(List2Ptr, tempstring1);
                    currentpos += 2;

                    strcpy(tempstring1, " Position:           ");
                    uvalue = getulong(&(contents[which].name[currentpos]));
                    stcl_d(tempstring2, uvalue);
                    strcat(tempstring1, tempstring2);
                    AddNameToTail(List2Ptr, tempstring1);
                    currentpos += 4;

                    strcpy(tempstring1, " Name:               ");
                    uvalue = contents[which].name[currentpos++];
                    // the number of characters in the string
                    if (uvalue < MEDFIELD)
                    {   strncat(tempstring1, &(contents[which].name[currentpos]), uvalue);
                    } else
                    {   strncat(tempstring1, &(contents[which].name[currentpos]), MEDFIELD);
                    }
                    AddNameToTail(List2Ptr, tempstring1);
                    currentpos += uvalue;
                    if (uvalue % 2)
                    {   currentpos++; // skip pad byte, if appropriate
        }   }   }   }
        elif (!strcmp(contents[which].name, "AESD"))
        {   chunkdesc("Pertinent to autio recording devices");
        } elif (!strcmp(contents[which].name, "COMT"))
        {   chunkdesc("Stores comments");

            strcpy(tempstring1, "Comments:            ");
            svalue = getsword(contents[which].DataPtr);
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            if (svalue < 0)
            {   strcat(tempstring1, "!");
            }
            AddNameToTail(List2Ptr, tempstring1);

            // we could have a 'comments' variable, but we just reuse
            // the 'markers' variable.
            markers = svalue;
            if (markers > 0)
            {   currentpos = 2;
                for (i = 1; i <= markers; i++)
                {   strcpy(tempstring1, "Comment ");
                    stcl_d(tempstring2, i);
                    strcat(tempstring1, tempstring2);
                    strcat(tempstring1, ":");
                    AddNameToTail(List2Ptr, tempstring1);

                    strcpy(tempstring1, " Created on:         ");
                    uvalue = getulong(&(contents[which].DataPtr[currentpos]));
                    DateTime.dat_Stamp.ds_Days   =   uvalue / 86400;             // days since 1/1/78
                    DateTime.dat_Stamp.ds_Minute =  (uvalue % 86400) / 60;       // minutes in day
                    DateTime.dat_Stamp.ds_Tick   = ((uvalue % 86400) % 60) * 50; // ticks in minute
                    DateTime.dat_Format          = FORMAT_DOS;
                    DateTime.dat_Flags           = DTF_SUBST;
                    DateTime.dat_StrDay          = weekdaystring;
                    DateTime.dat_StrDate         = datestring;
                    DateTime.dat_StrTime         = timestring;
                    if (!DateToStr(&DateTime))
                    {   rq("DateToStr() failed!");
                    }
                    strcat(tempstring1, timestring);
                    strcat(tempstring1, " ");
                    strcat(tempstring1, weekdaystring);
                    strcat(tempstring1, " ");
                    strcat(tempstring1, datestring);
                    AddNameToTail(List2Ptr, tempstring1);
                    currentpos += 4;

                    strcpy(tempstring1, " Marker ID:          ");
                    svalue = getsword(&(contents[which].DataPtr[currentpos]));
                    stci_d(tempstring2, svalue);
                    strcat(tempstring1, tempstring2);
                    if (svalue < 1)
                    {    strcat(tempstring1, "!");
                    }
                    AddNameToTail(List2Ptr, tempstring1);
                    currentpos += 2;

                    strcpy(tempstring1, " Comment:            ");
                    uvalue = getuword(&(contents[which].DataPtr[currentpos]));
                    // the number of characters in the string
                    currentpos += 2;
                    if (uvalue < MEDFIELD)
                    {   strncat(tempstring1, &(contents[which].name[currentpos]), uvalue);
                    } else
                    {   strncat(tempstring1, &(contents[which].name[currentpos]), MEDFIELD);
                    }
                    AddNameToTail(List2Ptr, tempstring1);
                    currentpos += uvalue;
                    if (uvalue % 2)
                    {   currentpos++; // skip pad byte, if appropriate
        }   }   }   }
        elif (!strcmp(contents[which].name, "COMM"))
        {   chunkdesc("Fundamental parameters for the sampled sound");

            strcpy(tempstring1, "Channels:            ");
            svalue = getsword(contents[which].DataPtr);
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            if (svalue == 1)
            {   strcat(tempstring1, " (mono)");
            } elif (svalue == 2)
            {   strcat(tempstring1, " (stereo)");
            } elif (svalue > 2)
            {   strcat(tempstring1, " (surround)");
            }
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Sample frames:       ");
            uvalue = getulong(&(contents[which].DataPtr[2]));
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Sample quality:      ");
            svalue = getsword(&(contents[which].DataPtr[6]));
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            strcat(tempstring1, "-bit");
            AddNameToTail(List2Ptr, tempstring1);

            /* strcpy(tempstring1, "Playback rate:       -");
            This is an 80-bit IEEE 754 Apple-style (SANE) floating point
            number (not yet implemented):
                 79 Sign
              64:78 Exponent
               0:63 Mantissa */
            AddNameToTail(List2Ptr, tempstring1); */
        } elif (!strcmp(contents[which].name, "APPL"))
        {   chunkdesc("Application-specific data");

            strcpy(tempstring1, "Application:         ");
            strcat(tempstring1, contents[which].DataPtr);
            if (!strcmp(tempstring1, "pdos"))
            {   strcat(tempstring1, " (Apple II)");
            }
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "SSND"))
        {   chunkdesc("The actual sample frames");

            strcpy(tempstring1, "Offset:              ");
            uvalue = getulong(contents[which].DataPtr);
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Block size:          ");
            uvalue = getulong(&(contents[which].DataPtr[4]));
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "INST"))
        {   chunkdesc("The actual sample frames");

            strcpy(tempstring1, "Base note:           ");
            svalue = getsbyte(&(contents[which].DataPtr[0]));
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            if (svalue < 0 || svalue > 127)
                strcat(tempstring1, "!");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Detune:              ");
            svalue = getsbyte(&(contents[which].DataPtr[1]));
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            if (svalue < -50 || svalue > 50)
                strcat(tempstring1, "!");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Low note:            ");
            svalue = getsbyte(&(contents[which].DataPtr[2]));
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            if (svalue < 0 || svalue > 127)
                strcat(tempstring1, "!");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "High note:           ");
            svalue = getsbyte(&(contents[which].DataPtr[3]));
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            if (svalue < 0 || svalue > 127)
                strcat(tempstring1, "!");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Low velocity:        ");
            svalue = getsbyte(&(contents[which].DataPtr[4]));
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            if (svalue < 1 || svalue > 127)
                strcat(tempstring1, "!");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "High velocity:       ");
            svalue = getsbyte(&(contents[which].DataPtr[5]));
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            if (svalue < 1 || svalue > 127)
                strcat(tempstring1, "!");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Gain:                ");
            svalue = getsword(&(contents[which].DataPtr[6]));
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            AddNameToTail(List2Ptr, "Sustain loop:");

            svalue = getsword(&(contents[which].DataPtr[8]));
            if (svalue == 0)
            {   strcpy(tempstring1, " No looping");
            } elif (svalue == 1)
            {   strcpy(tempstring1, " Forwards looping");
            } elif (svalue == 2)
            {   strcpy(tempstring1, " Forwards/backwards looping");
            } else
            {   strcpy(tempstring1, " ?");
            }
            AddNameToTail(List2Ptr, tempstring1);
            strcpy(tempstring1, " Begin loop:         ");
            svalue = getsword(&(contents[which].DataPtr[10]));
            stcl_d(tempstring1, svalue);
            AddNameToTail(List2Ptr, tempstring1);
            strcpy(tempstring1, " End loop:           ");
            svalue = getsword(&(contents[which].DataPtr[12]));
            stcl_d(tempstring1, svalue);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Release loop:        ");

            svalue = getsword(&(contents[which].DataPtr[14]));
            if (svalue == 0)
            {   strcpy(tempstring1, " No looping");
            } elif (svalue == 1)
            {   strcpy(tempstring1, " Forwards looping");
            } elif (svalue == 2)
            {   strcpy(tempstring1, " Forwards/backwards looping");
            } else
            {   strcpy(tempstring1, " ?");
            }
            AddNameToTail(List2Ptr, tempstring1);
            strcpy(tempstring1, " Begin loop:         ");
            svalue = getsword(&(contents[which].DataPtr[16]));
            stcl_d(tempstring1, svalue);
            AddNameToTail(List2Ptr, tempstring1);
            strcpy(tempstring1, " End loop:           ");
            svalue = getsword(&(contents[which].DataPtr[18]));
            stcl_d(tempstring1, svalue);
            AddNameToTail(List2Ptr, tempstring1);
    }   }
    elif (!strcmp(queryform, "8SVX"))
    {   if (!strcmp(contents[which].name, "ATAK"))
        {   chunkdesc("Gives attack amplitude contour (envelope)");
        } elif (!strcmp(contents[which].name, "RLSE"))
        {   chunkdesc("Gives release amplitude contour (envelope)");
        } elif (!strcmp(contents[which].name, "BODY"))
        {   chunkdesc("Sound data chunk");
        } elif (!strcmp(contents[which].name, "FADE"))
        {   chunkdesc("Fade away to silence");

            strcpy(tempstring1, "Fade start:          ");
            uvalue = getulong(contents[which].DataPtr);
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "SEQN"))
        {   chunkdesc("Multiple loop sequencing");

            currentpos = 0;
            while (currentpos < contents[which].bytes)
            {   strcpy(tempstring1, "Loop start:          ");
                uvalue = getulong(&(contents[which].DataPtr[currentpos]));
                stcl_d(tempstring2, uvalue);
                strcat(tempstring1, tempstring2);
                if (uvalue % 4)
                {   strcat(tempstring1, "!");
                }
                AddNameToTail(List2Ptr, tempstring1);
                currentpos += 4;

                strcpy(tempstring1, "Loop end:            ");
                uvalue = getulong(&(contents[which].DataPtr[currentpos]));
                stcl_d(tempstring2, uvalue);
                strcat(tempstring1, tempstring2);
                if (uvalue % 4)
                {   strcat(tempstring1, "!");
                }
                AddNameToTail(List2Ptr, tempstring1);
                currentpos += 4;
        }   }
        elif (!strcmp(contents[which].name, "CHAN"))
        {   chunkdesc("Specified channel, or combination of channels");
            strcpy(tempstring1, "Channel:             ");
            svalue = getslong(contents[which].DataPtr);
            if (svalue == 2)
            {   strcat(tempstring1, "Left");
            } elif (svalue == 4)
            {   strcat(tempstring1, "Right");
            } elif (svalue == 6)
            {   strcat(tempstring1, "Stereo (both)");
            } else strcat(tempstring1, "?");
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "PAN "))
        {   chunkdesc("Stereo sound using a single array of data");
            strcpy(tempstring1, "Sound position:      ");
            svalue = getslong(contents[which].DataPtr);
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            strcat(tempstring1, "/65536 ");
            if (svalue < 32768)
            {    strcat(tempstring1, "(left)");
            } elif (svalue > 32678)
            {    strcat(tempstring1, "(right)");
            } else
            {    assert(svalue == 32678);
                 strcat(tempstring1, "(centre)");
            }
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "VHDR"))
        {   chunkdesc("Playback paramaters for the sampled waveforms");
            strcpy(tempstring1, "1-shot samples:      ");
            uvalue = getulong(contents[which].DataPtr);
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Repeat samples:      ");
            uvalue = getulong(&(contents[which].DataPtr[4]));
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Samples per cycle:   ");
            uvalue = getulong(&(contents[which].DataPtr[8]));
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Sampling rate:       ");
            uvalue = getuword(&(contents[which].DataPtr[12]));
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Octaves:             ");
            uvalue = contents[which].DataPtr[14]; // UBYTE
            stcl_d(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Compression:         "); // UBYTE
            if (contents[which].DataPtr[15] == 0)
            {   strcat(tempstring1, "None");
            } elif (contents[which].DataPtr[15] == 1)
            {   strcat(tempstring1, "Fibonacci-delta encoding");
            } else strcat(tempstring1, "?");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Volume:              ");
            svalue = getslong(&(contents[which].DataPtr[16]));
            svalue /= 1024; // LONG (Fixed)
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            strcat(tempstring1, "/64");
            AddNameToTail(List2Ptr, tempstring1);
    }   }
    elif (!strcmp(queryform, "ILBM"))
    {   if (!strcmp(contents[which].name, "CAMG"))
        {   chunkdesc("Specifies the Amiga display mode of a picture");

            strcpy(tempstring1, "ModeID:             $");
            uvalue = getulong(contents[which].DataPtr);
            stcl_h(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "DPI "))
        {   chunkdesc("Dots per inch");

            strcpy(tempstring1, "Dots per inch:      ");
            // There are 20 characters in the first column.
            uvalue = getuword(contents[which].DataPtr);
            stcl_h(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            strcat(tempstring1, "x");
            uvalue = getuword(&(contents[which].DataPtr[2]));
            stcl_h(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "CMAP"))
        {   chunkdesc("Colour map data");
        } elif (!strcmp(contents[which].name, "BODY"))
        {   chunkdesc("Image data chunk");
        } elif (!strcmp(contents[which].name, "EPSF"))
        {   chunkdesc("Encapsulated PostScript representation of image");

            strcpy(tempstring1, "Left X:             ");
            svalue = getsword(contents[which].DataPtr);
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Top Y:              ");
            svalue = getsword(&(contents[which].DataPtr[2]));
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Right X:            ");
            svalue = getsword(&(contents[which].DataPtr[4]));
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Bottom Y:           ");
            svalue = getsword(&(contents[which].DataPtr[6]));
            stcl_d(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "DEST"))
        {   chunkdesc("Scatter bitplanes into a deeper destination image");

            strcpy(tempstring1, "Source bitplanes:   ");
            stcl_d(tempstring2, contents[which].DataPtr[0]);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            // next (1) is a pad byte.

            strcpy(tempstring1, "PlanePick:          %"); // UWORD
            binarize(contents[which].DataPtr[2]);
            binarize(contents[which].DataPtr[3]);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "PlaneOnOff:         %"); // UWORD
            binarize(contents[which].DataPtr[4]);
            binarize(contents[which].DataPtr[5]);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "PlaneMask:          %"); // UWORD
            binarize(contents[which].DataPtr[6]);
            binarize(contents[which].DataPtr[7]);
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "GRAB"))
        {   chunkdesc("Locates a hotspot, when used as a pointer or brush");

            strcpy(tempstring1, "Hotspot:            ");
            svalue = getsword(contents[which].DataPtr);
            stcl_h(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            strcat(tempstring1, ",");
            svalue = getsword(&(contents[which].DataPtr[2]));
            stcl_h(tempstring2, svalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "SPRT"))
        {   chunkdesc("This image is intended as a sprite");

            strcpy(tempstring1, "Precedence:         ");
            uvalue = getuword(contents[which].DataPtr);

            stcl_h(tempstring2, uvalue);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);
        } elif (!strcmp(contents[which].name, "BMHD"))
        {   /* UWORD w, h;               0-1, 2-3
            WORD  x, y;                  4-5, 6-7
            UBYTE nPlanes;               8
            UBYTE Masking;               9
            UBYTE Compression;           10
            UBYTE pad;                   11
            UWORD transparentColour;     12-13
            UBYTE xAspect, yAspect;      14, 15
            WORD  pageWidth, pageHeight; 16-17, 18-19 */

            chunkdesc("Data necessary to understand the BODY chunk");
            strcpy(tempstring1, "Image size:         ");
            stci_d(tempstring2, (256 * contents[which].DataPtr[0])
                              +        contents[which].DataPtr[1]); // UWORD
            strcat(tempstring1, tempstring2);
            strcat(tempstring1, "x");
            stci_d(tempstring2, (256 * contents[which].DataPtr[2])
                              +        contents[which].DataPtr[3]); // UWORD
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Position:           ");
            stci_d(tempstring2, (256 * contents[which].DataPtr[4])
                              +        contents[which].DataPtr[5]); // WORD
            strcat(tempstring1, tempstring2);
            strcat(tempstring1, ",");
            stci_d(tempstring2, (256 * contents[which].DataPtr[6])
                              +        contents[which].DataPtr[7]); // WORD
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Bitplanes:          ");
            stci_d(tempstring2, (LONG) contents[which].DataPtr[8]);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);
            // tell number of colours to user?

            strcpy(tempstring1, "Masking:            ");
            if (contents[which].DataPtr[9] == 0)
            {   strcat(tempstring1, "0 (Opaque)");
            } elif (contents[which].DataPtr[9] == 1)
            {   strcat(tempstring1, "1 (Mask plane)");
            } elif (contents[which].DataPtr[9] == 2)
            {   strcat(tempstring1, "2 (Transparent colour)");
            } elif (contents[which].DataPtr[9] == 3)
            {   strcat(tempstring1, "3 (Lasso)");
            } else strcat(tempstring1, "?");
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Compression:        ");
            if (contents[which].DataPtr[10] == 0)
            {   strcat(tempstring1, "None");
            } elif (contents[which].DataPtr[10] == 1)
            {   strcat(tempstring1, "Byte run encoding");
            } else strcat(tempstring1, "?");
            AddNameToTail(List2Ptr, tempstring1);

            // Next (11) is a pad byte.

            strcpy(tempstring1, "Transparent colour: ");
            if (contents[which].DataPtr[9] != 2
             && contents[which].DataPtr[9] != 3)
            {   strcat(tempstring1, "n/a");
            } else
            {   stci_d(tempstring2, (256 * contents[which].DataPtr[12])
                                  +        contents[which].DataPtr[13]);
                strcat(tempstring1, tempstring2);
            }
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Aspect ratio:       ");
            stci_d(tempstring2, (UBYTE) contents[which].DataPtr[14]);
            strcat(tempstring1, tempstring2);
            strcat(tempstring1, ":");
            stci_d(tempstring2, (UBYTE) contents[which].DataPtr[15]);
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);

            strcpy(tempstring1, "Page size:          ");
            stci_d(tempstring2, (256 * contents[which].DataPtr[16])
                              +        contents[which].DataPtr[17]); // WORD
            strcat(tempstring1, tempstring2);
            strcat(tempstring1, "x");
            stci_d(tempstring2, (256 * contents[which].DataPtr[18])
                              +        contents[which].DataPtr[19]); // WORD
            strcat(tempstring1, tempstring2);
            AddNameToTail(List2Ptr, tempstring1);
    }   }
    GT_SetGadgetAttrs
    (   LV61_List2,
        MainWindowPtr,
        NULL,
        GTLV_Labels, List2Ptr,
        TAG_DONE
    );
    if (!known)
    {   chunkdesc("?");
}   }

MODULE void chunkdesc(STRPTR desc)
{   GT_SetGadgetAttrs
    (   TE61_ChunkDesc,
        MainWindowPtr,
        NULL,
        GTTX_Text, desc,
        TAG_DONE
    );
    known = TRUE;
}

MODULE void binarize(UBYTE data)
{   ULONG i;

    for (i = 7; i >= 0; i--)
    {   if (data & (1 << i))
        {   strcat(tempstring1, "1");
        } else strcat(tempstring1, "0");
}   }

MODULE ULONG getulong(UBYTE* start)
{   return (ULONG) ((16777216 * *(start    ))
                  + (   65536 * *(start + 1))
                  + (     256 * *(start + 2))
                  +             *(start + 3));
}
MODULE SLONG getslong(UBYTE* start)
{   return (SLONG) ((16777216 * *(start    ))
                  + (   65536 * *(start + 1))
                  + (     256 * *(start + 2))
                  +             *(start + 3));
}
MODULE ULONG getuword(UBYTE* start)
{   return (ULONG) ((     256 * *(start    ))
                  +             *(start + 1));
}
MODULE SLONG getsword(UBYTE* start)
{   return (SLONG) ((     256 * *(start    ))
                  +             *(start + 1));
}
MODULE SLONG getsbyte(UBYTE* start)
{   return (SBYTE) (*start);
}
