/****************************************************************************\
*
*     MODULE: anifile.c
*
*     PURPOSE: Processes files for the Animated Cursor Editor
*
*     Copyright 1993, Microsoft Corp.
*
*
* History:
*   21-Apr-1993 JonPa   Wrote it.
*
\****************************************************************************/

#include <windows.h>
#include <commdlg.h>
#include "anidefs.h"

/****************************************************************************\
*
*     FUNCTION: BOOL CreateFrameFromCursorFile( LPSTR pszFile )
*
*     PURPOSE:  Opens a cursor file, reads the icon info out of it,
*               and creates a frame and step for that icon, then links
*               everything together and updates the listbox.
*
*     NOTES:    This function accesses the global flag gfEditFrame.
*               If this bool is TRUE, then the currently selected frame
*               in the listbox is overwritten.  If it is false, then
*               a new frame is created and inserted after the currently
*               selected frame (or at the end if no selection).
*
* History:
*   21-Apr-1993 JonPa   Created it
*
\****************************************************************************/
BOOL CreateFrameFromCursorFile(HWND hwnd,  LPTSTR pszFile, BOOL fEdit) {
    PFRAME pf;
    HANDLE hf;
    PSTEP psOld, psNew;
    DWORD ckSize;
    int iSel;
    int cSel;

    cSel = GetSelStepCount(hwnd);

    if ( (fEdit && (cSel != 1)) || cSel > 1) {
        FmtMessageBox( hwnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                TRUE, fEdit ? MSG_MUSTEQONEFAME : MSG_LESSEQONEFRAME);
        return FALSE;
    }

    /* get currently selected frame */
    GetCurrentSel(hwnd, DLG_MAIN_FRAMELIST, &iSel, 1, &cSel );

    if (cSel == 0)
        psOld = NULL;
    else
        psOld = GetStep(hwnd, iSel);

    /*
     * If not editing, create a new step
     */
    if (!fEdit || !IsValidPS(psOld)) {
        psNew = NewStep();

        if (psNew == NULL) {
            return FALSE;
        }
    } else {
        psNew = NULL;
    }

    hf = CreateFile(pszFile, GENERIC_READ,
             0, NULL,
             OPEN_EXISTING,
             FILE_ATTRIBUTE_NORMAL,
             NULL);

    if (hf == INVALID_HANDLE_VALUE)
        return FALSE;

    ckSize = GetFileSize(hf, NULL);

    /* get the frame out of the file */
    pf = ReadIconFromFile(hwnd, hf, ckSize);
    CloseHandle(hf);

    if (pf == NULL) {
        if (psNew != NULL)
            FreeMem(psNew);
        return FALSE;
    }

    if (psNew != NULL) {

        if (IsValidPS(psOld)) {
            psNew->jif = psOld->jif;
            iSel += 1;
        } else {
            psNew->jif = ganiAcon.anih.jifRate;
            iSel = SendDlgItemMessage(hwnd, DLG_MAIN_FRAMELIST, LB_GETCOUNT,
                    0, 0);
        }

        LinkStepFrame(psNew, pf);

        SendDlgItemMessage(hwnd, DLG_MAIN_FRAMELIST, LB_INSERTSTRING, iSel,
            (LPARAM)psNew);

        SetCurrentSel(hwnd, DLG_MAIN_FRAMELIST, FALSE, iSel);

    } else {
        HWND hwndLB = GetDlgItem(hwnd, DLG_MAIN_FRAMELIST);

        /*
         * Delete the old frame and point the step to the new one.
         */
        LinkStepFrame(psOld, pf);

        InvalidateRect(hwndLB, NULL, TRUE);
    }

    return TRUE;
}


/****************************************************************************\
*
*     FUNCTION: HICON ConvertDataToIcon( PFRAME pf )
*
*     PURPOSE:
*
*
*
*
* History:
*   23-Apr-1993 JonPa   copied from Win NT USERs ReadIconGuts
*
\****************************************************************************/
HICON ConvertDataToIcon( PFRAME pf, WORD *pxHotSave, WORD *pyHotSave )
{
    NEWHEADER *pnh;
    NEWHEADER *pnhBase;
    RESDIR *prd;
    int offMatch;
    ICONFILERESDIR *pird;
    PCURSORRESOURCE pcres;
    BOOL fIcon;
    HICON hicon;
    WORD x, y;

    pnhBase = (NEWHEADER *)pf->abIcon;

    /*
     * Construct a fake array of RESDIR entries using the info at the head
     * of the file.  Store the data offset in the idIcon WORD so it can be
     * returned by RtlGetIdFromDirectory.
     */
    pnh = (NEWHEADER *)LocalAlloc(LMEM_FIXED, sizeof(NEWHEADER) +
            (pnhBase->cResources * sizeof(RESDIR)));
    if (pnh == NULL)
        return NULL;

    *pnh = *pnhBase;
    prd = (RESDIR *)(pnh + 1);
    pird = (ICONFILERESDIR *)(pnhBase + 1);

    /* prime pird for first line of loop */
    pird--;

    for (offMatch = 0; offMatch < (int)pnh->cResources; offMatch++, prd++) {

        /*
         * Get the next resource directory from the icon file.
         */

        ++pird;

        /*
         * Convert from the icon editor's resource directory format
         * to the post-RC.EXE format LookupIconIdFromDirectory expects.
         */
        if (pnh->rt == 1) {     // ICON
            prd->ResInfo.Icon.Width = pird->bWidth;
            prd->ResInfo.Icon.Height = pird->bHeight;
            prd->ResInfo.Icon.ColorCount = pird->bColorCount;
            prd->ResInfo.Icon.reserved = 0;
        } else {                // CURSOR
            prd->ResInfo.Cursor.Width = pird->bWidth;
            prd->ResInfo.Cursor.Height = pird->bHeight;
        }
        prd->Planes = 0;                // Hopefully nobody uses this
        prd->BitCount = 0;              //        "        "
        prd->BytesInRes = pird->dwDIBSize;
        prd->idIcon = (WORD)pird->dwDIBOffset;
    }

    /*
     * NOTE: nh.rt is NOT an RT_ type value.  For instance, nh.rt == 1 for
     * an icon file where as 1 == RT_CURSOR, not RT_ICON.
     */

    fIcon = (pnhBase->rt == 1);
    offMatch = LookupIconIdFromDirectory((PBYTE)pnh, fIcon);

    LocalFree(pnh);

    if (fIcon) {
        pcres = (PCURSORRESOURCE)&(pf->abIcon[offMatch]);
        *pxHotSave = gcxCursor / 2;
        *pyHotSave = gcyCursor / 2;
    } else {

        offMatch -= (sizeof(pcres->xHotspot) + sizeof(pcres->yHotspot));

        for(; pird->dwDIBOffset != (WORD)offMatch &&
                pird != (ICONFILERESDIR *)(pnhBase + 1); pird--);

        pcres = (PCURSORRESOURCE)&(pf->abIcon[offMatch]);

        x = pcres->xHotspot;
        y = pcres->yHotspot;
        *pxHotSave = pcres->xHotspot = pird->xHotspot;
        *pyHotSave = pcres->yHotspot = pird->yHotspot;
    }

    hicon = CreateIconFromResource( (PBYTE)pcres,
            pf->rtag.ckSize - offMatch, fIcon, 0x00030000);

    if(!fIcon) {
        pcres->xHotspot = x;
        pcres->yHotspot = y;
    }

    return hicon;
}




/****************************************************************************\
*
*     FUNCTION: PFRAME ReadIconFromFile(HWND hwnd, HANDLE hf, DWORD ckSize)
*
*     PURPOSE:  Reads the icon info out of a file,
*               and creates a frame for that icon.
*
*
* History:
*   22-Apr-1993 JonPa   Created it
*
\****************************************************************************/
PFRAME ReadIconFromFile(HWND hwnd, HANDLE hf, DWORD ckSize) {
    PFRAME pf = AllocMem( sizeof( FRAME ) + ckSize );
    DWORD cbRead;
    PFRAME pfList;

    if (pf != NULL) {
        pf->cRef = 0;

        if (ReadFile(hf, pf->abIcon, ckSize, &cbRead, NULL) &&
                cbRead == ckSize) {
            /* got the data, now set up the rest of the frame and link it in */
            pf->dwCheckSum = CalcCheckSum( pf->abIcon, ckSize );
            pf->rtag.ckID = FOURCC_icon;
            pf->rtag.ckSize = ckSize;

            /* Check if this fram is already in the list */
            for (pfList = gpfrmFrames; pfList != NULL;
                    pfList = pfList->pfrmNext ) {
                if (pf->dwCheckSum == pfList->dwCheckSum &&
                        pf->rtag.ckSize == pfList->rtag.ckSize &&
                        memcmp( pf->abIcon, pfList->abIcon, ckSize ) == 0) {
                    /*
                     * These frames are the same, coalesce them into a
                     * sequence.
                     */
                    FreeMem(pf);
                    pf = pfList;
                    break;
                }
            }

            if (pfList == NULL) {
                /*
                 * Did not find a dup, create an icon for this frame
                 */
                pf->hcur = ConvertDataToIcon( pf, &(pf->xHotSpot),
                        &(pf->yHotSpot) );

                pf->pfrmNext = gpfrmFrames;
                gpfrmFrames = pf;
            }

        } else {
            /* File Error */
            FreeMem(pf);
            pf = NULL;
        }

    }

    return pf;
}

/****************************************************************************\
*
*     FUNCTION: HANDLE PromptAndOpenFile( )
*
*     PURPOSE:  Pust up the standard open dialog and then opens the file
*
*
*
*
* History:
*   21-Apr-1993 JonPa   Created it
*
\****************************************************************************/
HANDLE PromptAndOpenFile(
    HWND hwnd,
    DWORD  cchFileTitle,
    LPTSTR pszFileTitle,
    DWORD  cchFileName,
    LPTSTR pszFileName,
    LPTSTR pszFilter
    )
{
    HANDLE hf = INVALID_HANDLE_VALUE;

    if (PromptForFile( hwnd, cchFileTitle, pszFileTitle, cchFileName,
                pszFileName, pszFilter, NULL, FALSE )) {

        /* Open the file. */

        hf = CreateFile(pszFileName, GENERIC_READ,
                0, NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL);

        if (hf == INVALID_HANDLE_VALUE) {
            FmtMessageBox( hwnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP, TRUE,
                MSG_CANTOPENFILE, pszFileName );
        }
    }

    return hf;
}


/****************************************************************************\
*
*     FUNCTION: HANDLE PromptForFile( )
*
*     PURPOSE:  Pust up the standard open dialog
*
*
*
*
* History:
*   28-Apr-1993 JonPa   Created it from PromptAndOpenFile
*
\****************************************************************************/
BOOL PromptForFile(
    HWND hwnd,
    DWORD  cchFileTitle,
    LPTSTR pszFileTitle,
    DWORD  cchFileName,
    LPTSTR pszFile,
    LPTSTR pszFilter,
    LPTSTR pszDlgTitle,
    BOOL fSave
    )
{
    OPENFILENAME ofn;

    ZeroMemory(&ofn, sizeof(ofn));

    /* Set the members of the OPENFILENAME structure. */

    ofn.lStructSize = sizeof(OPENFILENAME);
    ofn.hwndOwner = hwnd;

    ofn.lpstrFilter = pszFilter;
    ofn.nFilterIndex = 0;

    ofn.lpstrFile = pszFile;
    ofn.nMaxFile = cchFileName;

    ofn.lpstrFileTitle = pszFileTitle;
    ofn.nMaxFileTitle = cchFileTitle;

    ofn.lpstrTitle = pszDlgTitle;

    ofn.lpstrDefExt = gpszANI;

    if (fSave) {
        ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
    } else {
        ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    }

    /* Display the SaveAs or Open dialog box. */

    return fSave ? GetSaveFileName(&ofn) : GetOpenFileName(&ofn);

}

/****************************************************************************\
*
*     FUNCTION: BOOL ReadAniFile( HWND hwnd, HANDLE hf ) {
*
*     PURPOSE:
&
*   Loads an animatied cursor from a RIFF file.  The RIFF file format for
*   animated cursors looks like this:
*
*   RIFF( 'ACON'
*       LIST( 'INFO'
*           INAM( <name> )
*           IART( <artist> )
*       )
*       anih( <anihdr> )
*       [rate( <rateinfo> )  ]
*       ['seq '( <seq_info> )]
*       LIST( 'fram' icon( <icon_file> ) )
*   )
*
*
* History:
*   02-Oct-1991 DarrinM     Created. (in Win32 user)
*   17-Mar-1993 JonPa       Rewrote to use RIFF format instead of RAD
*   21-Apr-1993 JonPa       Copied it to anifile.c and tweeked it.
*
\****************************************************************************/
BOOL ReadAniFile( HWND hwnd, HANDLE hf ) {

    RTAG tag;
    DWORD cbRead;
    BOOL fSuccess = FALSE;
    JIF *pjifRate = NULL;
    DWORD *pseq = NULL;
    PFRAME *ppfram = NULL;
    int iFrame = 0;
    int i;

    if (!ReadTag(hf, &tag))
        return FALSE;

    /*
     * Make sure it's a RIFF ANI file
     */
    if (tag.ckID != FOURCC_RIFF)
        goto laiFileErr;

    /* read the chunk type */
    if(!ReadFile(hf, &tag.ckID, sizeof(tag.ckID), &cbRead, NULL) ||
            cbRead < sizeof(tag.ckID)) {
        goto laiFileErr;
    }

    if (tag.ckID != FOURCC_ACON)
        goto laiFileErr;

    /* look for 'anih', 'rate', 'seq ', and 'icon' chunks */
    while( ReadTag(hf, &tag)) {

        switch( tag.ckID ) {
        case FOURCC_anih:
            if (!ReadChunk(hf, &tag, &ganiAcon.anih))
                goto laiFileErr;

            if (!(ganiAcon.anih.fl & AF_ICON) || (ganiAcon.anih.cFrames == 0))
                goto laiFileErr;

            /*
             * Allocate space for the ANIHEADER, and a seq and
             * rate table (in case we run into one later).
             */

            pjifRate = AllocMem( ganiAcon.anih.cSteps * sizeof(JIF) +
                    ganiAcon.anih.cSteps * sizeof(DWORD) +
                    ganiAcon.anih.cSteps * sizeof(PFRAME));


            if (pjifRate == NULL)
                goto laiFileErr;

            pseq = (DWORD *)(pjifRate + ganiAcon.anih.cSteps);
            ppfram = (PFRAME *)(pseq + ganiAcon.anih.cSteps);

            for( i = 0; i < (int)ganiAcon.anih.cSteps; i++ ) {
                pjifRate[i] = ganiAcon.anih.jifRate;
                pseq[i] = i;
                ppfram[i] = NULL;
            }
            break;


        case FOURCC_rate:
            /*
             * If we find a rate chunk, read it into its preallocated
             * space.
             */
            if(!ReadChunk(hf, &tag, (PBYTE)pjifRate))
                goto laiFileErr;
            break;


        case FOURCC_seq:
            /*
             * If we find a seq chunk, read it into its preallocated
             * space.
             */
            if(!ReadChunk(hf, &tag, (PBYTE)pseq))
                goto laiFileErr;
            break;


        case FOURCC_LIST: {
            DWORD cbChunk = PADUP(tag.ckSize);

            /*
             * See if this list is the 'fram' list of icon chunks
             */
            if(!ReadFile(hf, &tag.ckID, sizeof(tag.ckID), &cbRead, NULL) ||
                    cbRead < sizeof(tag.ckID)) {
                goto laiFileErr;
            }

            cbChunk -= cbRead;

            if (tag.ckID == FOURCC_fram) {

                while(cbChunk >= sizeof(tag)) {
                    if (!ReadTag(hf, &tag))
                        goto laiFileErr;

                    cbChunk -= sizeof(tag);

                    if(tag.ckID == FOURCC_icon) {
                        PFRAME pfrm;

                        /*
                         * Ok, load the icon/cursor bits,
                         */
                        pfrm = ReadIconFromFile(hwnd, hf, tag.ckSize);

                        if (pfrm == NULL) {
                            goto laiFileErr;
                        }

                        for( i = 0; i < (int)ganiAcon.anih.cSteps; i++ ) {
                            if (pseq[i] == (DWORD)iFrame) {
                                ppfram[i] = pfrm;
                            }
                        }

                        iFrame++;

                    } else {
                        /*
                         * Unknown chunk in fram list, just ignore it
                         */
                        SkipChunk(hf, &tag);
                    }

                    cbChunk -= PADUP(tag.ckSize);
                }
            } else if (tag.ckID == FOURCC_INFO) {
                /* now look for INAM and IART chunks */

                while( cbChunk >= sizeof(tag) ) {

                    if (!ReadTag(hf, &tag))
                        goto laiFileErr;

                    cbChunk -= sizeof(tag);

                    switch( tag.ckID ) {
                    case FOURCC_INAM:
                        if (cbChunk < tag.ckSize ||
                                !ReadChunkN(hf, &tag, ganiAcon.azTitle,
                                                sizeof(ganiAcon.azTitle)))
                            goto laiFileErr;

                        cbChunk -= PADUP(tag.ckSize);
                        break;

                    case FOURCC_IART:
                        if (cbChunk < tag.ckSize ||
                                !ReadChunkN(hf, &tag, ganiAcon.azCreator,
                                        sizeof(ganiAcon.azCreator)))
                            goto laiFileErr;

                        cbChunk -= PADUP(tag.ckSize);
                        break;

                    default:
                        if (!SkipChunk( hf, &tag ))
                            goto laiFileErr;

                        cbChunk -= PADUP(tag.ckSize);
                        break;
                    }
                }

            } else {
                /*
                 * Not the fram list or the INFO list.  Skip
                 * the rest of this chunk.  (Don't forget that we have
                 * already skipped one dword!)
                 */
                tag.ckSize = cbChunk;
                SkipChunk(hf, &tag);
                break;
            }

            break;
        }



        default:
            /*
             * We're not interested in this chunk, skip it.
             */
            if(!SkipChunk(hf, &tag))
                goto laiFileErr;
            break;

        }

    }

    /*
     * Update the frame count incase we coalesced some frames while reading
     * in the file.
     */
    ganiAcon.anih.cFrames = iFrame;

    /*
     * Now build up the listbox
     */

    for( i = 0; i < (int)ganiAcon.anih.cSteps; i++ ) {
        PSTEP ps;

        ps = NewStep();
        if (ps == NULL)
            goto laiFileErr;

        ps->jif = pjifRate[i];
        LinkStepFrame(ps, ppfram[i]);

        SendDlgItemMessage(hwnd, DLG_MAIN_FRAMELIST, LB_INSERTSTRING, i,
                (LPARAM)ps);
    }

    SetDlgItemText(hwnd, DLG_MAIN_TITLE, ganiAcon.azTitle);
    SetDlgItemText(hwnd, DLG_MAIN_AUTHOR, ganiAcon.azCreator);

    SendDlgItemMessage(hwnd, DLG_MAIN_PREVIEW, PM_NEWCURSOR, 0, 0);
    fSuccess = TRUE;

laiFileErr:

    if (pjifRate != NULL)
        FreeMem(pjifRate);

    if (!fSuccess)
        NewAniCursor(hwnd);

    CloseHandle(hf);

    return fSuccess;
}



/***************************************************************************\
* DWORD CalcCheckSum( PBYTE pb );
*
*
* History:
*
* 23-Apr-1993 JonPa     Created.
\***************************************************************************/
DWORD CalcCheckSum( PBYTE pb, DWORD cb ) {
    DWORD dw = 0;

    while(cb--)
        dw += (DWORD)*pb++;

    return dw;
}

/***************************************************************************\
* ReadTag, ReadChunk, SkipChunk
*
* Some handy functions for reading RIFF files.
*
* History:
* 10-02-91 DarrinM      Created.
* 03-25-93 Jonpa        Changed to use RIFF format instead of ASDF
* 23-Apr-1993 JonPa     Copied from Win NT USER.
\***************************************************************************/
BOOL ReadTag(
    HANDLE hf,
    PRTAG ptag)
{
    DWORD cbActual;

    ptag->ckID = ptag->ckSize = 0L;

    if (!ReadFile(hf, ptag, sizeof(RTAG), &cbActual, NULL) ||
            (cbActual != sizeof(RTAG)))
        return FALSE;

    /* no need to align file pointer since RTAG is already word aligned */
    return TRUE;
}


BOOL ReadChunk(
    HANDLE hf,
    PRTAG ptag,
    PVOID pv)
{
    DWORD cbActual;

    if (!ReadFile(hf, pv, ptag->ckSize, &cbActual, NULL) ||
            (cbActual != ptag->ckSize))
        return FALSE;

    /* WORD align file pointer */
    if( ptag->ckSize & 1 )
        SetFilePointer(hf, 1, NULL, FILE_CURRENT);

    return TRUE;
}


BOOL ReadChunkN(
    HANDLE hf,
    PRTAG ptag,
    PVOID pv,
    DWORD cbMax)
{
    DWORD cbActual;
    DWORD cbRead = min( cbMax, ptag->ckSize );

    if (!ReadFile(hf, pv, ptag->ckSize, &cbActual, NULL) ||
            (cbActual != cbRead))
        return FALSE;

    /* WORD align file pointer */

    cbRead = ptag->ckSize - cbActual;

    if( ptag->ckSize & 1 )
        cbRead++;

    return SetFilePointer(hf, cbRead, NULL, FILE_CURRENT) != 0xFFFFFFFF;
}

BOOL SkipChunk(
    HANDLE hf,
    PRTAG ptag)
{
    /* Round ptag->ckSize up to nearest word boundary to maintain alignment */
    return SetFilePointer(hf, PADUP(ptag->ckSize), NULL, FILE_CURRENT) !=
            0xFFFFFFFFL;
}

/****************************************************************************\
*
*     FUNCTION: VOID GetTempCursorFileName( szFileName );
*
*     PURPOSE:  Create a temporary .cur filename
*
*
* History:
*   22-Apr-1993 JonPa   Created it
*
\****************************************************************************/
BOOL GetTempCursorFileName( LPTSTR pszName ) {
    TCHAR szPath[MAX_PATH];

    if( GetTempPath( MAX_PATH, szPath ) >= MAX_PATH )
        lstrcpy( pszName, TEXT(".") );


    return GetTempFileName(szPath, TEXT("ae"), 0, pszName) != 0;
}

/****************************************************************************\
*
*     FUNCTION: BOOL SaveAniFile( HWND hwnd, HANDLE hf )
*
*     PURPOSE:
&
*   Saves an animatied cursor to a RIFF file.  The RIFF file format for
*   animated cursors looks like this:
*
*   RIFF( 'ACON'
*       [LIST( 'INFO'
*           [INAM( <name> )]
*           [IART( <artist> )]
*       )]
*       anih( <anihdr> )
*       [rate( <rateinfo> )  ]
*       ['seq '( <seq_info> )]
*       LIST( 'fram' icon( <icon_file> ) )
*   )
*
*
* History:
*   29-Apr-1993 JonPa   Created it.
*
\****************************************************************************/
BOOL SaveAniFile( HWND hwnd, HANDLE hf ) {
    int cSteps, i;
    int cFrames;
    PFRAME pf;
    DWORD cbFile, cbFram, cbINFO, cbTitle, cbAuthor;
    BOOL fRate, fSeq;
    RTAG rtag;
    PJIF pjif;
    DWORD *pseq;
    PFRAME *pfrm;

    fRate = fSeq = FALSE;
    cbINFO = cbFram = cbFile = cbTitle = cbAuthor = 0;

    PausePreview(hwnd, DLG_MAIN_PREVIEW);

    cSteps = GetStepCount(hwnd);

    if( cSteps == LB_ERR ) {
        FmtMessageBox( ghwndMain, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP,
                TRUE, MSG_OUTOFRESOUCES );
        return FALSE;
    }

    cFrames = 0;
    for( pf = gpfrmFrames; pf != NULL; pf = pf->pfrmNext ) {
        pf->iFrame = -1;
        cFrames++;
    }

    ganiAcon.anih.cSteps = cSteps;

    pjif = AllocMem( (sizeof(JIF) + sizeof(DWORD) + sizeof(PFRAME)) * cSteps );

    if(pjif == NULL)
        return FALSE;

    pseq = (DWORD *)&pjif[cSteps];
    pfrm = (PFRAME *)&pseq[cSteps];

    cFrames = 0;

    for( i = 0; i < cSteps; i++ ) {
        PSTEP ps;

        ps = GetStep(hwnd, i);

        if( IsValidPS(ps) ) {

            if (ps->pfrmFrame->iFrame == -1) {

                cbFram += sizeof(RTAG);
                cbFram += PADUP(ps->pfrmFrame->rtag.ckSize);

                ps->pfrmFrame->iFrame = cFrames;
                pfrm[cFrames++] = ps->pfrmFrame;

            } else
                fSeq = TRUE;

            pseq[i] = ps->pfrmFrame->iFrame;

            if ((pjif[i] = ps->jif) != ganiAcon.anih.jifRate) {
                fRate = TRUE;
            }
        }
    }

    ganiAcon.anih.cbSizeof = sizeof(ganiAcon.anih);
    ganiAcon.anih.cFrames = cFrames;
    ganiAcon.anih.fl = AF_ICON | (fSeq ? AF_SEQUENCE : 0);

    cbTitle = GetDlgItemTextA(hwnd, DLG_MAIN_TITLE, ganiAcon.azTitle,
                            COUNTOF(ganiAcon.azTitle));

    cbAuthor = GetDlgItemTextA(hwnd, DLG_MAIN_AUTHOR, ganiAcon.azCreator,
                            COUNTOF(ganiAcon.azCreator));

    /*
     * At this point, cbFram == the size required by all the frames,
     * add in the rate, seq, anih, and INFO list sizes as well as
     * all the other overhead.
     */

    cbFram += sizeof(FOURCC);     //fram type

    cbFile = cbFram;

    cbFile +=   sizeof(RTAG) +      //RIFF tag
                sizeof(FOURCC) +    //ACON type
                    sizeof(RTAG) +      //anih tag
                        PADUP(sizeof(ANIHEADER)) +
                    sizeof(RTAG);       //LIST tag (for fram list)


    if( cbTitle || cbAuthor) {
        /*
         * Remember, azCreator, and azTitle are ANSI strings!
         */
        if( cbTitle ) {
            cbTitle += 1; //add in ASCIIZ terminator
            cbINFO +=   sizeof(RTAG) +     //INAM tag
                        PADUP( cbTitle * sizeof(char));
        }

        if (cbAuthor) {
            cbAuthor += 1; //add in ASCIIZ terminator
            cbINFO +=   sizeof(RTAG) +     //IART tag
                        PADUP(cbAuthor * sizeof(char));
        }

        cbINFO +=  sizeof(FOURCC);      //INFO type

        cbFile +=   sizeof(RTAG) +      //LIST tag
                    cbINFO;
    }


    if (fSeq) {
        cbFile += sizeof(RTAG) +    //seq tag
                    PADUP(cSteps * sizeof(DWORD));
    }

    if (fRate) {
        cbFile += sizeof(RTAG) +    //rate tag
                    PADUP(cSteps * sizeof(JIF));
    }

    /*
     * Now we have all the structures built in memory, it's time to
     * write them out in RIFF ACON format!
     */
    rtag.ckID = FOURCC_RIFF;
    rtag.ckSize = cbFile;

    RET_CLOSE_IF_ERR( WriteTag(hf, &rtag), hf );

    RET_CLOSE_IF_ERR( WriteType(hf, FOURCC_ACON), hf );

    if( cbTitle || cbAuthor) {
        rtag.ckID = FOURCC_LIST;
        rtag.ckSize = cbINFO;

        RET_CLOSE_IF_ERR( WriteTag(hf, &rtag), hf );

        RET_CLOSE_IF_ERR( WriteType(hf, FOURCC_INFO), hf );

        if (cbTitle) {
            rtag.ckID = FOURCC_INAM;
            rtag.ckSize = cbTitle;
            RET_CLOSE_IF_ERR( WriteTagData(hf, &rtag, ganiAcon.azTitle), hf );
        }

        if (cbAuthor) {
            rtag.ckID = FOURCC_IART;
            rtag.ckSize = cbAuthor;
            RET_CLOSE_IF_ERR( WriteTagData(hf, &rtag, ganiAcon.azCreator), hf );
        }
    }

    /* write anih */
    rtag.ckID = FOURCC_anih;
    rtag.ckSize = sizeof(ganiAcon.anih);

    RET_CLOSE_IF_ERR( WriteTagData(hf, &rtag, &(ganiAcon.anih)), hf );

    /* if rate then write it */
    if (fRate) {
        rtag.ckID = FOURCC_rate;
        rtag.ckSize = cSteps * sizeof(JIF);

        RET_CLOSE_IF_ERR( WriteTagData(hf, &rtag, pjif), hf );
    }

    /* if seq, then write it */
    if (fSeq) {
        rtag.ckID = FOURCC_seq;
        rtag.ckSize = cSteps * sizeof(DWORD);

        RET_CLOSE_IF_ERR( WriteTagData(hf, &rtag, pseq), hf );
    }

    /* write the fram list */
    rtag.ckID = FOURCC_LIST;
    rtag.ckSize = cbFram;

    RET_CLOSE_IF_ERR( WriteTag(hf, &rtag), hf );
    RET_CLOSE_IF_ERR( WriteType(hf, FOURCC_fram), hf );

    for( i = 0; i < cFrames; i++ ) {
        RET_CLOSE_IF_ERR( WriteTagData(hf, &(pfrm[i]->rtag), pfrm[i]->abIcon),
                hf);
    }

    /* Close the file */
    CloseHandle(hf);

    return TRUE;
}


/***************************************************************************\
* WriteTag, WriteType, WriteTagData
*
* Some handy functions for writing RIFF files.
*
* History:
*   30-Apr-1993 JonPa   Created them.
\***************************************************************************/
BOOL WriteTag(HANDLE hf, PRTAG prtag) {
    DWORD cbWritten;

    return (WriteFile(hf, prtag, sizeof(RTAG), &cbWritten, NULL) &&
            cbWritten == sizeof(RTAG));
}

BOOL WriteType(HANDLE hf, FOURCC ckID ) {
    DWORD cbWritten;

    return (WriteFile(hf, &ckID, sizeof(FOURCC), &cbWritten, NULL) &&
            cbWritten == sizeof(FOURCC));
}

BOOL WriteTagData(HANDLE hf, PRTAG prtag, VOID *pvData ) {
    DWORD cbWritten;
    DWORD cbWrite = PADUP(prtag->ckSize);

    return  WriteTag(hf, prtag) && WriteFile(hf, pvData, cbWrite,
            &cbWritten, NULL) && cbWritten == cbWrite;
}




/***************************************************************************\
* VOID SaveFile(HWND hwnd, BOOL fPrompt)
*
* Conditionally Prompt the user for a name and then save the file
*
* History:
*   04-May-1993 JonPa   It
\***************************************************************************/
VOID SaveFile(HWND hwnd, BOOL fPrompt) {
    TCHAR szFileTitle[MAX_PATH];
    HANDLE hf;

    szFileTitle[0] = TEXT('\0');

    if (fPrompt || ganiAcon.szFile[0] == TEXT('\0')) {
tryagain:
        if (!PromptForFile(hwnd, COUNTOF(szFileTitle), szFileTitle,
                COUNTOF(ganiAcon.szFile), ganiAcon.szFile, gpszAniFilter,
                NULL, TRUE)) {
            return;
        }
    }

    hf = CreateFile( ganiAcon.szFile, GENERIC_WRITE, 0, NULL,
            CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

    if (hf == INVALID_HANDLE_VALUE) {
        FmtMessageBox(hwnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP, TRUE,
                MSG_CANTCREATEFILE, ganiAcon.szFile);

        goto tryagain;
    }

    if( !SaveAniFile(hwnd, hf) ) {
        FmtMessageBox(hwnd, TITL_ERROR, NULL, MB_OK | MB_ICONSTOP, TRUE,
                MSG_FILEWRITEERR, ganiAcon.szFile);
        return;
    }

    if (szFileTitle[0] != TEXT('\0'))
        SetWindowFileTitle(hwnd, szFileTitle);

    ganiAcon.fDirty = FALSE;
}
