/****************************************************************************
 *
 *  AVIFILE.C
 *
 *  routines for reading Standard AVI files
 *
 *  Copyright (c) 1992  - 1995 Microsoft Corporation.  All Rights Reserved.
 *
 *  You have a royalty-free right to use, modify, reproduce and
 *  distribute the Sample Files (and/or any modified version) in
 *  any way you find useful, provided that you agree that
 *  Microsoft has no warranty obligations or liability for any
 *  Sample Application Files which are modified.
 *
 ***************************************************************************/

#include <win32.h>
#include <ole2.h>
#include <vfw.h>
#include "avifilei.h"
#include "avifile.rc"
#include <checkbmi.h>

#include "debug.h"

#if !defined NUMELMS
 #define NUMELMS(aa) (sizeof(aa)/sizeof((aa)[0]))
#endif

#ifdef _WIN32
// We need a structure to read in the AVIStreamHeader from persistent
// storage.  This structure contains a RECT element, which in 16 bit land
// contains 4 16-bit values.  Hence when 16 bit AVIFILE.DLL writes the
// file out, it uses a RECT composed of 4 16-bit values.  On WIN32 we have
// to map this "short rect" to a WIN32 RECT of 4 32-bit elements.  In order
// that 16 bit code can continue to read/write files interchangeably with
// 32 bit code we map the two structures on read/write.

// The AVIStreamHeader structure exists on disk.  Hence the RECT struct
// is the same size for all systems, i.e. SHORT, 16 bit values.  The code
// that reads/writes files, and returns INFO will map these 16 bit values
// to the 32 bit RECT structure as appropriate.
typedef struct tagSRECT {
    SHORT    left;
    SHORT    top;
    SHORT    right;
    SHORT    bottom;
} SRECT;

typedef struct {
    FOURCC              fccType;
    FOURCC              fccHandler;
    DWORD               dwFlags;        /* Contains AVITF_* flags */
    WORD                wPriority;
    WORD                wLanguage;
    DWORD               dwInitialFrames;
    DWORD               dwScale;
    DWORD               dwRate; /* dwRate / dwScale == samples/second */
    DWORD               dwStart;
    DWORD               dwLength; /* In units above... */

    // new....
    DWORD               dwSuggestedBufferSize;
    DWORD               dwQuality;
    DWORD               dwSampleSize;
    SRECT               rcFrame;    /* does each frame need this? */

    /* additional type-specific data goes in StreamInfo chunk */

    /* For video: position within rectangle... */
    /* For audio: volume?  stereo channel? */
} AVIStreamHeaderShort;
#else
// Map one name to the other for 16 bit code...
#define AVIStreamHeaderShort AVIStreamHeader
#endif


#if defined _WIN32 && !defined UNICODE
//
// This is Win 95 code.  Explicit unicode stuff is needed.
//

int LoadUnicodeString(HINSTANCE hinst, UINT wID, LPWSTR lpBuffer, int cchBuffer)
{
    char    ach[256];
    int     i;

    i = LoadString(hinst, wID, ach, NUMELMS(ach));

    if (i > 0)
        MultiByteToWideChar(CP_ACP, 0, ach, -1, lpBuffer, cchBuffer);

    return i;
}

#define lstrlenW    lstrlenUnicode

int
WINAPI
lstrlenUnicode(
    LPCWSTR lpString
    )
{
    int count = 0;

    while (*lpString++)
        count++;

    return count;
}


#else
#define LoadUnicodeString   LoadString
#endif

extern "C" {
LPSTR FAR lstrzcpyA (LPSTR pszTarget, LPCSTR pszSource, size_t cchMax)
{
        lstrcpynA (pszTarget, pszSource, cchMax -1);
        pszTarget[ cchMax -1 ] = TEXT('\0');
        return pszTarget;
}


LPWSTR FAR lstrzcpyW (LPWSTR pszTarget, LPCWSTR pszSource, size_t cchMax)
{
        lstrcpynW (pszTarget, pszSource, cchMax -1);
        pszTarget[ cchMax -1 ] = TEXT('\0');
        return pszTarget;
}


LPTSTR FAR lstrzcpy (LPTSTR pszTarget, LPCTSTR pszSource, size_t cchMax)
{
        lstrcpyn (pszTarget, pszSource, cchMax -1);
        pszTarget[ cchMax -1 ] = TEXT('\0');
        return pszTarget;
}


LPWSTR FAR lstrzcpyAtoW (LPWSTR pszTarget, LPCSTR pszSourceA, size_t cchMax)
{
        LPWSTR pszSourceW;

        pszSourceW = (LPWSTR)GlobalAllocPtr(GMEM_MOVEABLE,sizeof(WCHAR)*cchMax);
        if (pszSourceW != NULL) {
            mbstowcs(pszSourceW, pszSourceA, cchMax);
            lstrcpynW (pszTarget, pszSourceW, cchMax -1);
            pszTarget[ cchMax -1 ] = TEXT('\0');
            GlobalFreePtr(pszSourceW);
        }
        return pszTarget;
}


LPSTR FAR lstrzcpyWtoA (LPSTR pszTarget, LPCWSTR pszSourceW, size_t cchMax)
{
        LPSTR pszSourceA;

        pszSourceA = (LPSTR)GlobalAllocPtr (GMEM_MOVEABLE, cchMax);
        if (pszSourceA != NULL) {
            wcstombs(pszSourceA, pszSourceW, cchMax);
            lstrcpynA (pszTarget, pszSourceA, cchMax -1);
            pszTarget[ cchMax -1 ] = TEXT('\0');
            GlobalFreePtr(pszSourceA);
        }
        return pszTarget;
}
} // extern "C"




extern "C" STDAPI CalculateFileDataRate(PAVIFILE pf, LONG FAR *plMaxBytesPerSec);

//#undef StreamFromFOURCC
//#define StreamFromFOURCC(fcc) (UINT)(HIBYTE(LOWORD(fcc)) - (BYTE)'0')

BOOL AddToIndex(CAVIFile FAR * pfile, DWORD ckid, DWORD cksize, LONG off, DWORD dwFlags);

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

EXTERN_C void DecodeRle(LPBITMAPINFOHEADER lpbi, BYTE _huge *pb, BYTE _huge *prle, DWORD dwInSize);
EXTERN_C HINSTANCE ghMod;

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

#define comptypeNONE            mmioFOURCC('N','O','N','E')
#define comptypeRLE0            mmioFOURCC('R','L','E','0')
#define comptypeRLE             mmioFOURCC('R','L','E',' ')

#define WIDTHBYTES(i)       ((UINT)((i+31)&(~31))/8)
#define DIBWIDTHBYTES(lpbi) (UINT)WIDTHBYTES((UINT)(lpbi)->biWidth * (UINT)(lpbi)->biBitCount)

LONG    lBufferSize = 0;
int     nBuffers = 0;

#define ckidSTREAMNAME         mmioFOURCC('s', 't', 'r', 'n')

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


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

EXTERN_C LONG FAR PASCAL shfileReadProc(HANDLE hsf, LONG lSeek, LONG lRead, LPVOID lpBuffer)
{
    if (shfileSeek((HSHFILE)hsf, lSeek, SEEK_SET) == -1)
        return -1;

    if (shfileRead((HSHFILE)hsf, (HPSTR)lpBuffer, lRead) != lRead)
        return -1;

    return lRead;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

#define INDEX_WRITE_SIZE    32l*1024
#define INDEX_READ_SIZE     32l*1024

static BOOL WriteOutIndex(CAVIFile FAR *pfile, DWORD dwOffsetMovie)
{
    MMCKINFO ck;
    PAVIINDEXENTRY pIndex;
    LONG cnt = INDEX_WRITE_SIZE / sizeof(*pIndex);
    LONG l;
    BOOL f=FALSE;
#ifdef DEBUG
    DWORD time;
#endif

    /*
    ** Now write index out!
    */
    ck.ckid = ckidAVINEWINDEX;
    ck.cksize = sizeof(AVIINDEXENTRY) * pfile->px->nIndex;

    if (shfileCreateChunk(pfile->hshfile, &ck, 0))
        return FALSE;

    DPF("Writing Index", time=timeGetTime());

    pIndex = (PAVIINDEXENTRY)GlobalAllocPtr(GHND,INDEX_WRITE_SIZE);

    if (pIndex) {

        for (l=0; l < pfile->px->nIndex; ) {

            cnt = IndexGetFileIndex(pfile->px, l, cnt, pIndex, -(LONG)dwOffsetMovie);
            if (cnt == 0)
                break;

            //l = cnt * sizeof(AVIINDEXENTRY);
            if (shfileWrite(pfile->hshfile, (HPSTR)pIndex,
                        cnt * sizeof(AVIINDEXENTRY))
                != cnt * (LONG)sizeof(AVIINDEXENTRY))
                 goto exit;

            l += cnt;

            DPF("!.");
        }

        DPF("!Done (%ldms)\n", timeGetTime()-time);

        if (!shfileAscend(pfile->hshfile, &ck, 0))
            f = TRUE;

      exit:
        GlobalFreePtr(pIndex);
    }

    return f;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

static BOOL ReadInIndex(CAVIFile FAR *pfile, DWORD size, DWORD dwOffsetMovie, BOOL fRle)
{
    PAVIINDEXENTRY pIndex;
    LONG cnt;
    LONG lIndexAdjust;
    BOOL f = FALSE;
#ifdef DEBUG
    DWORD time;
#endif

    pIndex = (PAVIINDEXENTRY)GlobalAllocPtr(GHND,INDEX_READ_SIZE);

    if (pIndex == NULL)
        goto exit;

    pfile->px = IndexCreate();

    if (pfile->px == 0)
        goto exit;

    DPF("Reading index.", time = timeGetTime());

    if (pfile->avihdr.dwFlags & AVIF_MUSTUSEINDEX)
        lIndexAdjust = dwOffsetMovie;
    else
        lIndexAdjust = -1;      // set when we read first index entry.

    while (size > 0) {

        cnt = min(INDEX_READ_SIZE, size);

        if (shfileRead(pfile->hshfile,(HPSTR)pIndex,cnt) != cnt)
            goto exit;

        size -= cnt;
        cnt /= sizeof(AVIINDEXENTRY);

        //
        // fix up the index
        //
        if (lIndexAdjust == -1) {
            lIndexAdjust = (LONG)(dwOffsetMovie + sizeof(DWORD)) -
                (LONG)pIndex->dwChunkOffset;
        }

        pfile->px = IndexAddFileIndex(pfile->px, pIndex, cnt, lIndexAdjust, fRle);

        if (pfile->px == NULL)
            goto exit;

        DPF("!.");
    }

    DPF("!Done (%ldms)\n", timeGetTime() - time);

    f = TRUE;

exit:
    if (pIndex)
        GlobalFreePtr(pIndex);

    return f;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

HRESULT SaveChanges(CAVIFile FAR * pfile, BOOL fRelease)
{
    CAVIStream FAR *            pavi;
    int                         stream;
    MMCKINFO                    ck;
    MMCKINFO                    ckRIFF;
    MMCKINFO                    ckLIST;
    MMCKINFO                    ckStream;
    LONG                        lCur;
    HRESULT                     hr = AVIERR_OK;
    AVIStreamHeaderShort        strhdr;

    // Clean up interleaving
    if (pfile->fInRecord) {
        if (pfile->px->nIndex > pfile->lRecordIndex + 1) {
            AVIFileEndRecord((PAVIFILE) pfile);
        }

        // back out of last record....
        --pfile->px->nIndex;
        pfile->lWriteLoc -= 3 * sizeof(DWORD);
        shfileSeek(pfile->hshfile, pfile->lWriteLoc, SEEK_SET);
        pfile->fInRecord = FALSE;
    }

    // Go back and write out the header

    lCur = shfileSeek(pfile->hshfile, 0, SEEK_CUR);
    shfileSeek(pfile->hshfile, 0, SEEK_SET);

    /* Create RIFF chunk */
    ckRIFF.cksize = 0;
    ckRIFF.fccType = formtypeAVI;
    if (shfileCreateChunk(pfile->hshfile, &ckRIFF, MMIO_CREATERIFF)) {
        goto FileError;
    }

    /* Create header list */
    ckLIST.cksize = 0;
    ckLIST.fccType = listtypeAVIHEADER;
    if (shfileCreateChunk(pfile->hshfile, &ckLIST, MMIO_CREATELIST)) {
        goto FileError;
    }

    /* Create AVI header chunk */
    ck.cksize = sizeof(pfile->avihdr);
    ck.ckid = ckidAVIMAINHDR;
    if (shfileCreateChunk(pfile->hshfile, &ck, 0)) {
        goto FileError;
    }

    CalculateFileDataRate(&pfile->m_AVIFile, (LONG FAR *) &pfile->avihdr.dwMaxBytesPerSec);

    // !!! CalculateFileDataRate may have seeked us to the wrong place....
    shfileSeek(pfile->hshfile, ck.dwDataOffset, SEEK_SET);

    /* Write AVI header info */
    if (shfileWrite(pfile->hshfile,
                  (HPSTR)&pfile->avihdr,
                  sizeof(pfile->avihdr)) != sizeof(pfile->avihdr)) {
        goto FileError;
    }

    if (shfileAscend(pfile->hshfile, &ck, 0)) {
        goto FileError;
    }


#if 0
    for (l = 0;
         l < muldiv32(pfile->avihdr.dwTotalFrames,
                             pfile->avihdr.dwMicroSecPerFrame,
                             1000000L);
         l++) {

             for (stream = 0; stream < (int) pfile->avihdr.dwStreams; stream++) {
             }
    }
#endif

    for (stream = 0; stream < (int) pfile->avihdr.dwStreams; stream++) {
        pavi = pfile->ps[stream];

        /* Create stream header list */
        ckStream.cksize = 0;
        ckStream.fccType = listtypeSTREAMHEADER;
        if (shfileCreateChunk(pfile->hshfile,&ckStream,MMIO_CREATELIST)) {
            goto FileError;
        }

        ck.ckid = ckidSTREAMHEADER;
        if (shfileCreateChunk(pfile->hshfile, &ck, 0)) {
            goto FileError;
        }


        // Make an AVIStreamHeader from the AVISTREAMINFO
        strhdr.fccType =                pavi->avistream.fccType;
        strhdr.fccHandler =             pavi->avistream.fccHandler;
        strhdr.dwFlags =                pavi->avistream.dwFlags;
        strhdr.wPriority =              pavi->avistream.wPriority;
        strhdr.wLanguage =              pavi->avistream.wLanguage;
        strhdr.dwRate =                 pavi->avistream.dwRate;
        strhdr.dwScale =                pavi->avistream.dwScale;
        strhdr.dwStart =                pavi->avistream.dwStart;
        strhdr.dwLength =               pavi->avistream.dwLength;
        strhdr.dwSuggestedBufferSize =  pavi->avistream.dwSuggestedBufferSize;
        strhdr.dwQuality =              pavi->avistream.dwQuality;
        strhdr.dwSampleSize =           pavi->avistream.dwSampleSize;

#ifdef _WIN32
        // Write out the Short rectangle format
        strhdr.rcFrame.left   =         (SHORT)pavi->avistream.rcFrame.left   ;
        strhdr.rcFrame.right  =         (SHORT)pavi->avistream.rcFrame.right  ;
        strhdr.rcFrame.top    =         (SHORT)pavi->avistream.rcFrame.top    ;
        strhdr.rcFrame.bottom =         (SHORT)pavi->avistream.rcFrame.bottom ;
#else
        strhdr.rcFrame =                pavi->avistream.rcFrame;
#endif

        strhdr.dwInitialFrames =        pavi->avistream.dwInitialFrames;

        if (shfileWrite(pfile->hshfile, (HPSTR) &strhdr, sizeof(strhdr)) !=
                                                            sizeof(strhdr)) {
            goto FileError;
        }

        if (shfileAscend(pfile->hshfile, &ck, 0)) {
            goto FileError;
        }


        ck.cksize = pavi->cbFormat;
        ck.ckid = ckidSTREAMFORMAT;

        if (shfileCreateChunk(pfile->hshfile, &ck, 0))
            goto FileError;

        if (shfileWrite(pfile->hshfile, (HPSTR) pavi->lpFormat, ck.cksize) !=
                        (LONG) ck.cksize)
            goto FileError;

        if (shfileAscend(pfile->hshfile, &ck, 0))
            goto FileError;

        if (pavi->avistream.szName[0]) {
            long sz = lstrlenW(pavi->avistream.szName)+1;
            ck.cksize = sz;
            ck.ckid = ckidSTREAMNAME;

            if (shfileCreateChunk(pfile->hshfile, &ck, 0))
                goto FileError;

#ifdef _WIN32
            // the file format expects ANSI names!
            LPSTR  pA = (LPSTR) GlobalAllocPtr(GPTR, sz);
            if (pA == 0) {
                DPF(("memory allocation failed for Unicode conversion"));
                goto FileError;
            }
            WideCharToMultiByte(CP_ACP, 0, pavi->avistream.szName, -1,
                        pA, sz, NULL, NULL);

            sz = shfileWrite(pfile->hshfile, (HPSTR) pA, ck.cksize);

            GlobalFreePtr(pA);

            if (sz != (LONG) ck.cksize)
                goto FileError;
#else
            if (shfileWrite(pfile->hshfile, (HPSTR) pavi->avistream.szName, ck.cksize) !=
                            (LONG) ck.cksize)
                goto FileError;
#endif

            if (shfileAscend(pfile->hshfile, &ck, 0))
                goto FileError;
        }

        if (pavi->extra.cb) {
            DPF2("Writing %ld bytes of extra stream data.\n", pavi->extra.cb);
            if (shfileWrite(pfile->hshfile, (HPSTR) pavi->extra.lp, pavi->extra.cb) !=
                        (LONG) pavi->extra.cb)
                goto FileError;
        }

        /* Ascend out of stream's header */
        if (shfileAscend(pfile->hshfile, &ckStream, 0)) {
            goto FileError;
        }
    }

    /* ascend from the Header list */
    if (shfileAscend(pfile->hshfile, &ckLIST, 0)) {
        goto FileError;
    }

    lCur = shfileSeek(pfile->hshfile, 0, SEEK_CUR);

    DPF("Data list start = %ld, current pos = %ld\n", pfile->lDataListStart, lCur);

    if (lCur + 8 > pfile->lDataListStart) {
        // !!! Ack: we didn't leave enough space for the header.
        // !!! How can we avoid this?
        DPF("Header was too big!  Failing!\n");
        goto FileError;
    }

    /* Pad this header out so that the real data will start on a 2K
    ** boundary by writing a JUNK chunk.
    */
    ck.ckid = ckidAVIPADDING;
    ck.cksize = pfile->lDataListStart - lCur - 8;
    if (shfileCreateChunk(pfile->hshfile,&ck,0)) {
        goto FileError;
    }

    shfileZero(pfile->hshfile, pfile->lDataListStart - lCur - 8);

    if (shfileAscend(pfile->hshfile, &ck, 0)) {
        goto FileError;
    }

    /* Start the 'movi' list, where all of the actual data will be. */
    ckLIST.cksize = pfile->lWriteLoc - pfile->lDataListStart - 8;
    ckLIST.fccType = listtypeAVIMOVIE;
    if (shfileCreateChunk(pfile->hshfile, &ckLIST, MMIO_CREATELIST)) {
        goto FileError;
    }

    shfileSeek(pfile->hshfile, pfile->lWriteLoc, SEEK_SET);

    if (shfileAscend(pfile->hshfile, &ckLIST, 0))
        goto FileError;

    if (!WriteOutIndex(pfile, ckLIST.dwDataOffset))
        goto FileError;

    //
    // Write out any extra data around
    //
    if (pfile->extra.cb) {
        DPF2("Writing %ld bytes of extra file data.\n", pfile->extra.cb);

        if (shfileWrite(pfile->hshfile,
                      (HPSTR) pfile->extra.lp,
                      pfile->extra.cb) !=
                 (LONG) pfile->extra.cb)
            goto FileError;
    }

FinishUp:
    if (shfileAscend(pfile->hshfile, &ckRIFF, 0)) {
        hr = ResultFromScode(AVIERR_FILEWRITE);
    }


    //
    // Always flush to be sure that the data really made it to the disk....
    //
    if (shfileFlush(pfile->hshfile, 0)) {
        hr = ResultFromScode(AVIERR_FILEWRITE);
    }

    return hr;

FileError:
    DPF("SaveChanges returning Error! This error will be ignored!\n");
    hr = ResultFromScode(AVIERR_FILEWRITE);
    goto FinishUp;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP_(ULONG) CAVIFile::CUnknownImpl::Release()
{
    CAVIFile FAR * pfile = m_pAVIFile;
    CAVIStream FAR * pavi;
    int iStream;
    CLock tlock(pfile);

    DPF2("File   %p: Usage--=%lx\n", (DWORD_PTR) (LPVOID) this, m_refs - 1);

    uUseCount--;
    if (!--m_refs) {

        if (pfile->fDirty) {
            ++m_refs;
            SaveChanges(pfile, TRUE);
            --m_refs;

            // Unfortunately, it's too late to tell about any errors....
        }

        for (iStream = 0; iStream < (int)pfile->avihdr.dwStreams; iStream++) {
            pavi = pfile->ps[iStream];

            if (!pavi)
                continue;

            delete pavi;
        }

        if (pfile->hshfile) {
            shfileRelease(pfile->hshfile);
            shfileClose(pfile->hshfile, 0);
        }

        if (pfile->px)
            FreeIndex(pfile->px);

        if (pfile->extra.lp) {
            DPF2("Freeing %ld bytes of extra file data.\n", pfile->extra.cb);
            GlobalFreePtr(pfile->extra.lp);
        }

        if (pfile->pb)
            EndBuffered(pfile->pb);

        pfile->hshfile = NULL;

        pfile->px = NULL;

        // done with critsec now - no-one else has any refs to it
        tlock.Exit();
#ifdef _WIN32
        DeleteCriticalSection(&pfile->m_critsec);
#endif
        delete pfile;
        return 0;
    } else {
        if (pfile->hshfile)
            shfileRelease(pfile->hshfile);
    }

    return m_refs;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

#ifndef _WIN32
STDMETHODIMP CAVIFile::CAVIFileImpl::Save(
                                   LPCTSTR szFile,
                                   AVICOMPRESSOPTIONS FAR *lpOptions,
                                   AVISAVECALLBACK lpfnCallback)
{
    CAVIFile FAR * pfile = m_pAVIFile;
    HRESULT hr = ResultFromScode(AVIERR_OK);

    CLock tlock(pfile);

    if (pfile->fDirty) {
        hr = SaveChanges(pfile, FALSE);
    }

    return hr;
}

#endif
#define SLASH(c)     ((c) == TEXT('/') || (c) == TEXT('\\'))

/*--------------------------------------------------------------+
| FileName  - return a pointer to the filename part of szPath   |
|             with no preceding path.                           |
+--------------------------------------------------------------*/
LPTSTR FAR FileName(LPCTSTR lszPath)
{
    LPCTSTR   lszCur;
#ifdef _WIN32
    // We really should be using GetFileTitle API as this will provide
    // better validation on the input name.
#endif

    for (lszCur = lszPath + lstrlen(lszPath); lszCur > lszPath && !SLASH(*lszCur) && *lszCur != ':';) {
#ifdef _WIN32
        lszCur = CharPrev(lszPath, lszCur);
#else
        lszCur = AnsiPrev(lszPath, lszCur);
#endif
    }
    if (lszCur == lszPath)
        return (LPTSTR)lszCur;
    else
        return (LPTSTR)(lszCur + 1);
}

// We do not currently use the last defined parameter for IsRectBogus
// Use a macro to remove it.  (It can be quickly restored.)
#define ISRECTBOGUS(lprc, dwW, dwH, lpbi) IsRectBogus(lprc, dwW, dwH)

INLINE BOOL IsRectBogus(LPRECT lprc, DWORD dwFrameWidth, DWORD dwFrameHeight)
                 // unused LPBITMAPINFOHEADER lpbi)
{
    if (IsRectEmpty(lprc))
        return TRUE;

    if (lprc->right - lprc->left > (int) dwFrameWidth)
        return TRUE;

    if (lprc->bottom - lprc->top > (int) dwFrameHeight)
        return TRUE;

    // !!!! Check that rectangle matches lpbi?

    // We've run out of things to check, so it's OK....
    return FALSE;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::OpenInternal(DWORD mode)
{
    CAVIFile FAR * pfile = this;
    CAVIStream FAR *        pavi;
    MMCKINFO        ck;
    MMCKINFO        ckRIFF;
    MMCKINFO        ckLIST;
    MMCKINFO        ckStream;
    DWORD           dwSize;
    BOOL            fRle=FALSE;
    LONG            l;
    int             iStream;
    int             i;
    HRESULT         hr = ResultFromScode(AVIERR_OK);
    IUnknown FAR *  pUnk;
    AVIStreamHeaderShort strhdr;
    TCHAR            ach[20];
    TCHAR            ach2[20];
    int             iStreamNumber;
#ifdef DEBUG
    DWORD time;
#endif

    CLock tlock(pfile);


    if (!pfile->hshfile) {
        hr = ResultFromScode(AVIERR_FILEOPEN);
        goto error;
    }

    if (mode & OF_CREATE) {
        // make a empty index.
        pfile->px = IndexCreate();

        if (pfile->px == 0)
            goto memerror;

        pfile->lWriteLoc = 0;
        pfile->lHeaderSize = sizeof(MainAVIHeader) + 11 * sizeof(DWORD);

#ifndef AVIF_TRUSTCKTYPE
#define AVIF_TRUSTCKTYPE        0x00000800      // Use CKType to find key frames?
#endif

        pfile->avihdr.dwFlags = AVIF_HASINDEX | AVIF_TRUSTCKTYPE;
    } else {

        /* Read RIFF chunk */
        if (shfileDescend(pfile->hshfile, &ckRIFF, NULL, 0) != 0)
            goto readerror;

        /*
         * check for a 'QuickTime AVI' file, a QuickTime AVI file is a
         * QuickTime public movie with a AVI file in the 'mdat' atom.
         */
        if (ckRIFF.cksize == mmioFOURCC('m','d','a','t'))
        {
            /*
             * now the 'mdat' atom better be a RIFF/AVI or we cant handle it.
             */
            if (shfileDescend(pfile->hshfile, &ckRIFF, NULL, 0) != 0)
                goto formaterror;
        }

        if (ckRIFF.ckid != FOURCC_RIFF)
            goto formaterror;

        if (ckRIFF.fccType != formtypeAVI)
            goto formaterror;

        /* Read header list */
        ckLIST.fccType = listtypeAVIHEADER;
        if (FindChunkAndKeepExtras(&pfile->extra, pfile->hshfile, &ckLIST, &ckRIFF, MMIO_FINDLIST))
            goto error;

        pfile->lHeaderSize = ckLIST.cksize + 8 * sizeof(DWORD);

        /* Read AVI header chunk */
        ck.ckid = ckidAVIMAINHDR;
        if (FindChunkAndKeepExtras(&pfile->extra, pfile->hshfile, &ck, &ckLIST, MMIO_FINDCHUNK))
            goto error;

        dwSize = min(ck.cksize, sizeof(MainAVIHeader));

        /* Read AVI header info */
        if (shfileRead(pfile->hshfile, (HPSTR)&pfile->avihdr, dwSize) != (LONG)dwSize)
            goto readerror;

        if (shfileAscend(pfile->hshfile, &ck, 0))
            goto readerror;

        /*  Check there aren't more streams than we can handle */
        if (pfile->avihdr.dwStreams > MAXSTREAMS) {
            /*  Make sure we don't crash deleteing non-existent streams */
            pfile->avihdr.dwStreams = 0;
            goto error;
        }

        for (iStream = 0; iStream < (int)pfile->avihdr.dwStreams; iStream++) {
            pfile->ps[iStream] = NULL;
        }

        /* Allocate stream data stuff, read streams */
        for (iStream = 0; iStream < (int)pfile->avihdr.dwStreams; ) {

            if (shfileDescend(pfile->hshfile, &ckStream, &ckLIST, 0) != 0)
                goto readerror;

            //
            //  found a non-stream header skip
            //
            if (ckStream.fccType != listtypeSTREAMHEADER ||
                    ckStream.ckid != FOURCC_LIST) {
                if ((hr = ReadIntoExtra(&pfile->extra,
                                  pfile->hshfile,
                                  &ckStream)) != ResultFromScode(AVIERR_OK))
                    goto error;

                if (shfileAscend(pfile->hshfile, &ckStream, 0) != 0)
                    goto readerror;

                continue;
            }

            pfile->ps[iStream] = new FAR CAVIStream(NULL, &pUnk);
            if (!pfile->ps[iStream])
                goto memerror;

            pavi = pfile->ps[iStream];
            pavi->pfile = pfile;
            pavi->iStream = iStream;

            //
            //  walk every chunk in this stream header, until we are done.
            //
            while (shfileDescend(pfile->hshfile, &ck, &ckStream, 0) == 0) {
                switch (ck.ckid) {
                    case ckidSTREAMHEADER:
                        //
                        // set these to sane default's incase the file
                        // header is not big enough
                        //
                        // NOTE the stream rectangle is set to NULL, if
                        // this is a video stream it will be corrected
                        // when we process the format.
                        //
                        strhdr.dwQuality = (DWORD) ICQUALITY_DEFAULT;

#ifdef _WIN32
                        // Set the 16 bit rectangle values to 0
                        strhdr.rcFrame.left =
                        strhdr.rcFrame.right=
                        strhdr.rcFrame.top  =
                        strhdr.rcFrame.bottom = 0;
#else
                        SetRectEmpty(&strhdr.rcFrame);
#endif

                        l = min(ck.cksize, sizeof(strhdr));

                        if (shfileRead(pfile->hshfile, (HPSTR)&strhdr, l) != l)
                            goto readerror;

                        // Copy fields from strhdr into StreamInfo
                        pavi->avistream.fccType =       strhdr.fccType;
                        pavi->avistream.fccHandler =    strhdr.fccHandler;
                        pavi->avistream.dwFlags =       strhdr.dwFlags; //!!!
                        pavi->avistream.dwCaps =        0; // !!!
                        pavi->avistream.wPriority =     strhdr.wPriority;
                        pavi->avistream.wLanguage =     strhdr.wLanguage;
                        pavi->avistream.dwRate =        strhdr.dwRate;
                        pavi->avistream.dwScale =       strhdr.dwScale;
                        pavi->avistream.dwStart =       strhdr.dwStart;
                        pavi->avistream.dwLength =      strhdr.dwLength;
                        pavi->avistream.dwSuggestedBufferSize = strhdr.dwSuggestedBufferSize;
                        pavi->avistream.dwInitialFrames = strhdr.dwInitialFrames;
                        pavi->avistream.dwQuality =     strhdr.dwQuality;
                        pavi->avistream.dwSampleSize =  strhdr.dwSampleSize;

#ifdef _WIN32
                        // Copy the 16 bit rectangle we have read from
                        // persistent storage into a WIN32 RECT (32 bit)
                        // structure.  There is no operator= defined for
                        // this "short rect" to RECT assignment.
                        pavi->avistream.rcFrame.left  = strhdr.rcFrame.left;
                        pavi->avistream.rcFrame.top   = strhdr.rcFrame.top;
                        pavi->avistream.rcFrame.right = strhdr.rcFrame.right;
                        pavi->avistream.rcFrame.bottom= strhdr.rcFrame.bottom;
#else
                        pavi->avistream.rcFrame = strhdr.rcFrame;
#endif

                        pavi->avistream.dwEditCount =   0;
                        pavi->avistream.dwFormatChangeCount =   0;

                        // Make up a stream name out of the filename, stream
                        // type, and stream number.
                        if (pavi->avistream.fccType == streamtypeVIDEO)
                            LoadString(ghMod, IDS_VIDEO, ach, NUMELMS(ach));
                        else if (pavi->avistream.fccType == streamtypeAUDIO)
                            LoadString(ghMod, IDS_AUDIO, ach, NUMELMS(ach));
                        else
                            wsprintf(ach, TEXT("'%4.4hs'"),
                                (LPSTR)&(pavi->avistream.fccType));

                        // figure out what # stream of this type this is....
                        iStreamNumber = 1;
                        for (i = 0; i < iStream; i++) {
                            if (pfile->ps[i]->avistream.fccType ==
                                        pavi->avistream.fccType)
                                ++iStreamNumber;
                        }
                        LoadString(ghMod, IDS_SSSTREAMD, ach2, NUMELMS(ach2));

                        {
                           TCHAR achTemp[MAX_PATH];

                           wsprintf(achTemp,
                               (LPTSTR)ach2,
                               (LPTSTR)FileName(pfile->achFile),
                               (LPTSTR)ach,
                               iStreamNumber);

#if defined _WIN32 && !defined UNICODE
                           lstrzcpyAtoW (pavi->avistream.szName,
                                         achTemp,
                                         NUMELMS (pavi->avistream.szName));
#else
                           lstrzcpy (pavi->avistream.szName,
                                     achTemp,
                                     NUMELMS (pavi->avistream.szName));
#endif
                        }
                        break;

                    case ckidSTREAMFORMAT:

                        if (pavi->lpFormat == NULL) {
                            pavi->cbFormat = ck.cksize;
                            pavi->lpFormat = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
                                                            ck.cksize);

                            if (pavi->lpFormat == NULL)
                                goto memerror;

                            if (shfileRead(pfile->hshfile, (HPSTR) pavi->lpFormat, (LONG)ck.cksize) != (LONG)ck.cksize)
                                goto readerror;

                            #define lpbi ((LPBITMAPINFOHEADER)pavi->lpFormat)

                            if (pavi->avistream.fccType != streamtypeVIDEO)
                                break;

                            if (!ValidateBitmapInfoHeader(lpbi, ck.cksize)) {
                                break;
                            }
                            //
                            // make sure this is set
                            //
                            if (lpbi->biClrUsed == 0 && lpbi->biBitCount <= 8)
                                lpbi->biClrUsed = (1 << (int)lpbi->biBitCount);

                            //
                            // fix up bogus stream rectangles.
                            //
                            if (ISRECTBOGUS(&pavi->avistream.rcFrame,
                                            pfile->avihdr.dwWidth,
                                            pfile->avihdr.dwHeight,
                                            lpbi)) {
                                SetRect(&pavi->avistream.rcFrame, 0, 0,
                                    (int)lpbi->biWidth, (int)lpbi->biHeight);
                            }

                            //
                            // make sure the biCompression is right for
                            // RLE files.
                            //
                            if (lpbi->biCompression == 0 && lpbi->biBitCount == 8) {
                                if (pavi->avistream.fccHandler == comptypeRLE0 ||
                                    pavi->avistream.fccHandler == comptypeRLE)
                                    lpbi->biCompression = BI_RLE8;
                            }

                            if (pavi->avistream.fccHandler == comptypeNONE &&
                                lpbi->biCompression == 0)
                                pavi->avistream.fccHandler = comptypeDIB;

                            if (pavi->avistream.fccHandler == 0 &&
                                lpbi->biCompression == 0)
                                pavi->avistream.fccHandler = comptypeDIB;

                            // Assuming a DIB handler has RGB data will blow up
                            // if it has RLE data, and VidEdit et. al write out
                            // confusing files like this.
                            //if (pavi->avistream.fccHandler == comptypeDIB)
                            //    lpbi->biCompression = BI_RGB;

                            if (lpbi->biCompression <= BI_RLE8)
                                fRle = TRUE;

                            #undef lpbi
                        }
                        break;

                    case ckidSTREAMHANDLERDATA:

                        if (pavi->lpData == NULL) {
                            pavi->cbData = ck.cksize;
                            pavi->lpData = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
                                                          ck.cksize);

                            if (pavi->lpData == NULL)
                                goto memerror;

                            if (shfileRead(pfile->hshfile, (HPSTR)pavi->lpData,
                                         (LONG)ck.cksize) != (LONG)ck.cksize)
                                goto readerror;
                        }
                        break;

                    case ckidSTREAMNAME:
                    {
#ifdef _WIN32
                        char achTemp[MAX_PATH];

                        l = (LONG)min((LONG) ck.cksize, NUMELMS(achTemp));

                        if (shfileRead(pfile->hshfile, (LPSTR)achTemp, l) != l) {
                            goto readerror;
                        }

                        MultiByteToWideChar(CP_ACP, 0, achTemp, -1,
                                            pavi->avistream.szName,
                                            NUMELMS(pavi->avistream.szName));
#else
                        l = min((LONG) ck.cksize, NUMELMS(pavi->avistream.szName));

                        if (shfileRead(pfile->hshfile,
                                       (HPSTR)pavi->avistream.szName, l) != l)
                            goto readerror;
#endif
                    }
                        break;


                    case ckidAVIPADDING:
                    case mmioFOURCC('p','a','d','d'):
                        break;

                    default:
                        if ((hr = ReadIntoExtra(&pavi->extra,
                                          pfile->hshfile,
                                          &ck)) != ResultFromScode(AVIERR_OK))
                            goto error;

                        break;
                }

                if (shfileAscend(pfile->hshfile, &ck, 0) != 0)
                    goto readerror;
            }

            /* Ascend out of stream header */
            if (shfileAscend(pfile->hshfile, &ckStream, 0) != 0)
                goto readerror;

            if (pavi->avistream.fccType == 0)
                goto formaterror;

            if (pavi->lpFormat == NULL)
                goto formaterror;

            //
            // make sure the sample size is set right
            //
            switch(pavi->avistream.fccType) {
                case streamtypeAUDIO:
                    /* Hack for backward compatibility with audio */
                    pavi->avistream.dwSampleSize =
                        ((LPWAVEFORMAT)pavi->lpFormat)->nBlockAlign;

                    // For audio, this number isn't useful when reading.
                    // !!!pavi->avistream.dwInitialFrames = 0;
                    // !!! We should let people read what the header says....
                    break;

                case streamtypeVIDEO:
                    // !!! But what if the samples are all the right size?
                    pavi->avistream.dwSampleSize = 0;
                    break;

                default:
                    // !!! ??? pavi->avistream.dwInitialFrames = 0;
                    // !!! ??? pavi->avistream.dwSampleSize = 0;
                    break;
            }


            l = NUMELMS(pavi->avistream.szName) - 1;
            pavi->avistream.szName[l] = TEXT('\0');

            // next stream
            iStream++;
        }

        // Read extra data at end of header list....
        FindChunkAndKeepExtras(&pfile->extra, pfile->hshfile, &ck, &ckLIST, 0);

        if (shfileAscend(pfile->hshfile, &ckLIST, 0))
            goto readerror;

        /* Find big data chunk */
        ckLIST.fccType = listtypeAVIMOVIE;
        if (FindChunkAndKeepExtras(&pfile->extra, pfile->hshfile, &ckLIST, &ckRIFF, MMIO_FINDLIST))
            goto error;

        pfile->lDataListStart = ckLIST.dwDataOffset - 2 * sizeof(DWORD);

        if (shfileAscend(pfile->hshfile, &ckLIST, 0))
            goto readerror;

        // Keep track of where data can be written
        pfile->lWriteLoc = ckLIST.dwDataOffset + ckLIST.cksize;

        //
        // read in or create a index, we only want the index entries for the
        // stream we are interested in!
        //
        ck.ckid = ckidAVINEWINDEX;
        if (FindChunkAndKeepExtras(&pfile->extra, pfile->hshfile, &ck, &ckRIFF, MMIO_FINDCHUNK) == 0 && ck.cksize != 0) {

            if (!ReadInIndex(pfile, ck.cksize, ckLIST.dwDataOffset, fRle))
                goto formaterror;

        } else {
            /* Seek back to beginning of list, so we can scan */
            shfileSeek(pfile->hshfile, ckLIST.dwDataOffset + sizeof(DWORD), SEEK_SET);

            //!!! should we really scan big files, or give a error?

            pfile->px = IndexCreate();

            if (pfile->px == 0)
                goto formaterror;

            DPF("Scanning index", time = timeGetTime());

            /* Scan through chunks... */
            while (shfileDescend(pfile->hshfile, &ck, &ckLIST, 0) == 0) {

                AddToIndex(pfile,ck.ckid,ck.cksize,ck.dwDataOffset-8,0);

                /* Hack: don't ascend from LISTs */
                if (ck.ckid != FOURCC_LIST) {
                    if (shfileAscend(pfile->hshfile, &ck, 0) != 0)
                        goto readerror;
                }

                if (pfile->px->nIndex % 512 == 0) {
                    DPF("!.");
                }
            }

            DPF("!Done (%ldms)\n", timeGetTime() - time);
        }

        if (pfile->px->nIndex == 0)
            goto formaterror;

        // Read extra data at end of file....
        FindChunkAndKeepExtras(&pfile->extra, pfile->hshfile, &ck, &ckRIFF, 0);

        // shfileSetBuffer(pfile->hshfile, NULL, 0L, 0);

        //
        //  compute dwSuggestedBufferSize
        //
        if (pfile->avihdr.dwFlags & AVIF_ISINTERLEAVED) {

            LONG l;
            LONG lLen;

            pfile->avihdr.dwSuggestedBufferSize = 0;

            for (l=IndexFirst(pfile->px, STREAM_REC);
                 l != -1;
                 l = IndexNext(pfile->px, l, 0)) {

                lLen = IndexLength(pfile->px, l);

                if (pfile->avihdr.dwSuggestedBufferSize < (DWORD)lLen)
                    pfile->avihdr.dwSuggestedBufferSize = (DWORD)lLen;
            }
        }

#ifdef USE_DIRECTIO
        // don't use additional buffering if we're using direct io
        if (shfileIsDirect(pfile->hshfile)) {
            pfile->pb = NULL;
        } else
#endif
        {

            if ((pfile->avihdr.dwFlags & AVIF_ISINTERLEAVED) &&
                    pfile->avihdr.dwInitialFrames) {

            pfile->pb = InitBuffered((int) pfile->avihdr.dwInitialFrames * 2,
                                         pfile->avihdr.dwSuggestedBufferSize,
                                     pfile->hshfile,
                                         pfile->px);
            }
            else /* if (pfile->avihdr.dwSuggestedBufferSize > 0 &&
                     pfile->avihdr.dwSuggestedBufferSize < 32l*1024) */ {

                int  nBuffers = GetProfileIntA("avifile", "buffers", 5);

                pfile->pb = InitBuffered(nBuffers,
                                         min(pfile->avihdr.dwSuggestedBufferSize * 2, 32768L),
                                     pfile->hshfile,
                                         pfile->px);
            }
        }
    }

    return ResultFromScode(AVIERR_OK);

readerror:
    return ResultFromScode(AVIERR_FILEREAD);

memerror:
    return ResultFromScode(AVIERR_MEMORY);

formaterror:
    return ResultFromScode(AVIERR_BADFORMAT);

error:
    if (hr == ResultFromScode(AVIERR_OK))
        return ResultFromScode(AVIERR_ERROR);
    return hr;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

#ifndef _WIN32
STDMETHODIMP CAVIFile::CAVIFileImpl::Open(LPCTSTR szFile, UINT mode)
{
    CAVIFile FAR * pfile = m_pAVIFile;
    UINT            ui;

    CLock tlock(pfile);

    if (pfile->achFile[0])
        return ResultFromScode(-1);

    pfile->mode = mode;
    lstrcpy(pfile->achFile, szFile);

    // Assumptions about avilib.cpp:
    // We're assuming that if CREATE is set, WRITE is set too.
    // We're assuming that we'll always see READWRITE instead of just WRITE.

// If it ain't broke, don't fix it - who do I look like, the share flag
// standards enforcing committee?
#if 0
    // force the share flags to the 'correct' values
    if (mode & OF_READWRITE) {
        pfile->mode = (mode & ~(MMIO_SHAREMODE)) | OF_SHARE_EXCLUSIVE;
    } else {
        pfile->mode = (mode & ~(MMIO_SHAREMODE)) | OF_SHARE_DENY_WRITE;
    }
#endif

    // try to open the actual file
    // If the first attempt fails, no system error box, please.
    ui = SetErrorMode(SEM_NOOPENFILEERRORBOX);

    pfile->hshfile = shfileOpen(pfile->achFile, NULL, pfile->mode);

    if (!pfile->hshfile && ((mode & MMIO_RWMODE) == OF_READ)) {
        // if the open fails, try again without the share flags.
        pfile->mode &= ~(MMIO_SHAREMODE);

        pfile->hshfile = shfileOpen(pfile->achFile, NULL, pfile->mode);
    }

    if (pfile->hshfile)
        shfileAddRef(pfile->hshfile);  // compensate for later rel of IUnknown

    SetErrorMode(ui);

    return pfile->OpenInternal(mode);
}
#endif

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIFileImpl::GetStream(PAVISTREAM FAR *ppavi, DWORD fccType, LONG lParam)
{
    CAVIFile FAR * pfile = m_pAVIFile;

    CAVIStream FAR *pavi;
    int             iStreamCur;
    int             iStreamWant;
    int             iStream;
    LONG            lLength;

    // thread locking
    CLock tlock(pfile);

    *ppavi = NULL;

    iStreamWant = (int)lParam;

    if (iStreamWant < 0 || iStreamWant >= (int)pfile->avihdr.dwStreams)
        return ResultFromScode(AVIERR_NODATA);

    /* Allocate stream data stuff, read streams */
    for (iStreamCur = -1, iStream = 0;
            iStream < (int)pfile->avihdr.dwStreams;
            iStream++) {
        if (fccType == 0 || pfile->ps[iStream]->avistream.fccType == fccType)
            iStreamCur++;

        if (iStreamCur == iStreamWant)
            break;
    }

    if (iStreamCur != iStreamWant)
        return ResultFromScode(AVIERR_NODATA);

    pavi = pfile->ps[iStream];

    if (pavi->fInit)
        goto returnnow;

    pavi->fInit = TRUE;

#if 0
    if ((pf->mode & (OF_WRITE | OF_READWRITE)) == 0) {
        pavi->hshfile = shfileOpen(pfile->achFile, NULL, MMIO_ALLOCBUF | pfile->mode);

        if (!pavi->hshfile)
            goto error;
    } else
#endif
    pavi->hshfile = pfile->hshfile;

    pavi->lPal = -4242;

    pavi->psx = MakeStreamIndex(pfile->px, iStream,
        (LONG)pavi->avistream.dwStart - pavi->avistream.dwInitialFrames,
        (LONG)pavi->avistream.dwSampleSize,
        pfile->hshfile, shfileReadProc, NULL);

    if (pavi->psx == NULL) {
        pavi->fInit = FALSE;  // sigh; failed.
        return ResultFromScode(AVIERR_MEMORY);
    }

    AddRef();   // Now safe.  We only ever return AVIERR_OK after this.

    pavi->avistream.dwSuggestedBufferSize = pavi->psx->lMaxSampleSize;

    if (pavi->psx->lPalFrames == 0)
        pavi->avistream.dwFlags &= ~AVISF_VIDEO_PALCHANGES;
    else
        pavi->avistream.dwFlags |= AVISF_VIDEO_PALCHANGES;

    pavi->pb = pavi->pfile->pb;

    if (!pavi->pb) {

#ifdef USE_DIRECTIO
        if (!shfileIsDirect(pavi->hshfile))
#endif
        {

            lBufferSize = GetProfileIntA("avifile", "buffersize", 0) * 1024L;
            nBuffers = GetProfileIntA("avifile", "buffers", 0);

            if (lBufferSize && nBuffers && !(pavi->pfile->mode & OF_CREATE)) {
                pavi->pb = InitBuffered(nBuffers, lBufferSize,
                                    pavi->hshfile, NULL);
            }
        }
    }

    //
    //  use ReadBuffered() to read data!
    //
    if (pavi->pb) {
        pavi->psx->hFile = (HANDLE)pavi->pb;
        pavi->psx->Read  = (STREAMIOPROC)BufferedRead;
    }

    lLength = pavi->psx->lEnd - pavi->psx->lStart;

    if (lLength != (LONG)pavi->avistream.dwLength +
                            (LONG)pavi->avistream.dwInitialFrames) {
#ifdef DEBUG
        DPF("Stream %d: Length is %ld, header says %ld.\n",
            iStream, lLength,
            pavi->avistream.dwLength + pavi->avistream.dwInitialFrames);
#endif
        //!!! should we correct the header!!!
    }

returnnow:
    pavi->m_AVIStream.QueryInterface(IID_IAVIStream, (LPVOID FAR *) ppavi);
    Assert(*ppavi);  // We had better return an interface pointer

    //
    // all done return success.
    //
    return ResultFromScode(AVIERR_OK); // success
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIFileImpl::CreateStream(
                                   PAVISTREAM FAR *ppavi,
                                   AVISTREAMINFOW FAR *psi)
{
    CAVIFile FAR * pf = m_pAVIFile;
    CAVIStream FAR * pavi;
    int         iStream = (int) pf->avihdr.dwStreams;
    IUnknown FAR *  pUnk;

    CLock tlock(m_pAVIFile);

    // !!! If we are writing to an existing file, and not to a new file, we have
    // a limitation where we cannot grow the size of the header.
    // Check to see if the header will take up too much room!
    if (pf->lWriteLoc > 0) {
        LONG lHeader = sizeof(AVIStreamHeader) +
                       pf->lHeaderSize +
                       8 * sizeof(DWORD) +
                       lstrlenW(psi->szName);
        if (lHeader > pf->lDataListStart) {
            DPF("Header will be too big with this new stream!\n");
            return ResultFromScode(AVIERR_UNSUPPORTED);
        }
    }
    pf->lHeaderSize += sizeof(AVIStreamHeader) + 8 * sizeof(DWORD) +
                       lstrlenW(psi->szName);

    if (iStream >= MAXSTREAMS) {
        DPF("Ack: Too many streams: we only support %ld.\n", (LONG) MAXSTREAMS);

        return ResultFromScode(AVIERR_UNSUPPORTED);
    }

    if ((pf->mode & (OF_WRITE | OF_READWRITE)) == 0)
        return ResultFromScode(AVIERR_READONLY);

    pf->ps[iStream] = new FAR CAVIStream(NULL, &pUnk);

    if (!pf->ps[iStream])
        return ResultFromScode(AVIERR_MEMORY);

    pavi = pf->ps[iStream];
    pavi->iStream = iStream;
    pavi->pfile = pf;
    pavi->avistream = *psi;
    pavi->avistream.dwLength = 0;       // no data initially
    pavi->avistream.dwSuggestedBufferSize = 0;
    pavi->hshfile = pf->hshfile;
    pavi->m_AVIStream.AddRef();
    AddRef();

    pavi->lpFormat = NULL;      // This will be set leater with a SetFormat
    pavi->cbFormat = 0;

    if (pavi->avistream.fccType == streamtypeAUDIO) {
        SetRectEmpty(&pavi->avistream.rcFrame);
    }

    pf->avihdr.dwStreams++;

    if (pavi->iStream == 0) {
        pavi->pfile->avihdr.dwMicroSecPerFrame =
                max(1000L, muldiv32(1000000L,
                                     pavi->avistream.dwScale,
                                     pavi->avistream.dwRate));
    }

    /* Make sure the width and height of the created file are right.... */
    pf->avihdr.dwWidth = max(pf->avihdr.dwWidth,
                             (DWORD) pavi->avistream.rcFrame.right);
    pf->avihdr.dwHeight = max(pf->avihdr.dwHeight,
                             (DWORD) pavi->avistream.rcFrame.bottom);

    // Only if interleaved?
    pf->avihdr.dwInitialFrames = max(pf->avihdr.dwInitialFrames,
                                     pavi->avistream.dwInitialFrames);

    *ppavi = &pavi->m_AVIStream;

    return ResultFromScode(AVIERR_OK);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

#if 0
STDMETHODIMP CAVIFile::CAVIFileImpl::AddStream(
                                PAVISTREAM pavi,
                                PAVISTREAM FAR *ppaviNew)
{
    CAVIFile FAR * pf = m_pAVIFile;
    CAVIStream FAR * paviNew;
    int         iStream = (int) pf->avihdr.dwStreams;
    HRESULT     hr;
    IUnknown FAR *  pUnk;

    if ((pf->mode & (OF_WRITE | OF_READWRITE)) == 0)
        return ResultFromScode(AVIERR_READONLY);

    pfile->ps[iStream] = new FAR CAVIStream(NULL, &pUnk);

    if (!pfile->ps[iStream])
        return ResultFromScode(AVIERR_MEMORY);

    paviNew = pf->ps[iStream];
    paviNew->iStream = iStream;
    paviNew->pfile = pf;
    AVIStreamInfo(pavi, &paviNew->avistream, sizeof(paviNew->avistream));
    paviNew->hshfile = pf->hshfile;
    paviNew->m_AVIStream.AddRef();
    paviNew->paviBase = pavi;
    AVIStreamAddRef(pavi);

    paviNew->cbFormat = AVIStreamFormatSize(pavi, 0);
    paviNew->lpFormat = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, paviNew->cbFormat);
    if (!paviNew->lpFormat) {
        AVIStreamClose((PAVISTREAM) pf->ps[iStream]);
        return ResultFromScode(AVIERR_MEMORY);
    }

    AVIStreamReadFormat(pavi, 0, paviNew->lpFormat, &paviNew->cbFormat);

    pf->avihdr.dwStreams++;

    AddRef();

    if (paviNew->iStream == 0) {
        pf->avihdr.dwMicroSecPerFrame =
                muldiv32(1000000L,
                         paviNew->avistream.dwScale,
                         paviNew->avistream.dwRate);
    }

    *ppaviNew = (PAVISTREAM) paviNew;

    return ResultFromScode(AVIERR_OK);
}
#endif

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIFileImpl::WriteData(
                       DWORD ckid,
                       LPVOID lpData,
                       LONG cbData)
{
    CAVIFile FAR *      pf = m_pAVIFile;
    CLock tlock(m_pAVIFile);

    // !!! Anything else we can check?
    if (lpData == NULL || cbData == 0)
        return ResultFromScode(AVIERR_BADPARAM);

    if ((pf->mode & (OF_WRITE | OF_READWRITE)) == 0)
        return ResultFromScode(AVIERR_READONLY);

    pf->fDirty = TRUE;

    return WriteExtra(&pf->extra, ckid, lpData, cbData);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIFileImpl::ReadData(
                      DWORD ckid,
                      LPVOID lpData,
                      LONG FAR *lpcbData)
{
    CAVIFile FAR * pf = m_pAVIFile;

    CLock tlock(m_pAVIFile);

    return ReadExtra(&pf->extra, ckid, lpData, lpcbData);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIFileImpl::EndRecord()
{
    CAVIFile FAR * pf = m_pAVIFile;
    CLock tlock(m_pAVIFile);

    if ((pf->mode & (OF_WRITE | OF_READWRITE)) == 0)
        return ResultFromScode(AVIERR_READONLY);

    pf->fDirty = TRUE;
    pf->avihdr.dwFlags |= AVIF_ISINTERLEAVED;

    if (pf->lWriteLoc == 0) {
        pf->lWriteLoc = (pf->lHeaderSize + 1024 + 2047) & ~(2047);

        // Leave room for start of first 'rec' chunk....
        pf->lWriteLoc -= 3 * sizeof(DWORD);

        pf->lDataListStart = pf->lWriteLoc - 3 * sizeof(DWORD);
        DPF("Writing first chunk at position %lu\n", pf->lWriteLoc);
    }

    shfileSeek(pf->hshfile, pf->lWriteLoc, SEEK_SET);

    if (pf->fInRecord) {

#ifndef NOPADDING
        {
            DWORD       dwCurOffset;
            DWORD       dwPadNeeded;
            MMCKINFO ck;

            dwCurOffset = shfileSeek(pf->hshfile, 0, SEEK_CUR);

            // want to start next record at 2K-12 byte boundary
            dwCurOffset = (dwCurOffset + 12) % 2048;

            if (dwCurOffset != 0) {
                // we need to pad....
                dwPadNeeded = 4096 - dwCurOffset - 8;
                if (dwPadNeeded >= 2048)
                    dwPadNeeded -= 2048;

                ck.ckid = mmioFOURCC('J','U','N','K');
                ck.cksize = dwPadNeeded;

                if (shfileCreateChunk(pf->hshfile, &ck, 0)) {
                    return ResultFromScode(AVIERR_FILEWRITE);
                }

                shfileZero(pf->hshfile, dwPadNeeded);

                if (shfileAscend(pf->hshfile, &ck, 0))
                    return ResultFromScode(AVIERR_FILEWRITE);
            }
        }
#endif

        if (shfileAscend(pf->hshfile, (MMCKINFO FAR *) &pf->ckRecord, 0))
            return ResultFromScode(AVIERR_FILEWRITE);

        IndexSetLength(pf->px, pf->lRecordIndex, pf->ckRecord.cksize);

        //
        // Keep the main suggested buffer size as big as the biggest
        // record....
        //
        if (pf->ckRecord.cksize + 3 * sizeof(DWORD) >
                                            pf->avihdr.dwSuggestedBufferSize)
            pf->avihdr.dwSuggestedBufferSize = pf->ckRecord.cksize +
                                                           3 * sizeof(DWORD);
    }

    /* Start the next 'rec' list */
    pf->ckRecord.cksize = 0;
    pf->ckRecord.fccType = listtypeAVIRECORD;
    pf->fInRecord = TRUE;
    if (shfileCreateChunk(pf->hshfile, (MMCKINFO FAR *) &pf->ckRecord, MMIO_CREATELIST)) {
        return ResultFromScode(AVIERR_FILEWRITE);
    }

    pf->lWriteLoc = shfileSeek(pf->hshfile, 0, SEEK_CUR);

    pf->lRecordIndex = pf->px->nIndex;

    if (!AddToIndex(pf, pf->ckRecord.fccType, 0,
        pf->ckRecord.dwDataOffset - 2 * sizeof(DWORD), AVIIF_LIST)) {
        return ResultFromScode(AVIERR_MEMORY);
    }

    return ResultFromScode(AVIERR_OK);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIFileImpl::Info(
                  AVIFILEINFOW FAR * pfi,
                  LONG lSize)
{
    CAVIFile FAR * pf = m_pAVIFile;

    if (pfi == NULL)
        return ResultFromScode(AVIERR_BADPARAM);

    if (lSize < sizeof(AVIFILEINFOW))
        return ResultFromScode(AVIERR_BUFFERTOOSMALL);

    CLock tlock(m_pAVIFile);

    pfi->dwMaxBytesPerSec =       pf->avihdr.dwMaxBytesPerSec;
    pfi->dwFlags =                (pf->avihdr.dwFlags & AVIF_ISINTERLEAVED);
    pfi->dwCaps  =              AVIFILECAPS_CANREAD | AVIFILECAPS_CANWRITE;
    pfi->dwStreams =              pf->avihdr.dwStreams;
    pfi->dwSuggestedBufferSize =  pf->avihdr.dwSuggestedBufferSize;
    pfi->dwWidth =                pf->avihdr.dwWidth;
    pfi->dwHeight =               pf->avihdr.dwHeight;
    pfi->dwScale =                pf->avihdr.dwMicroSecPerFrame;
    pfi->dwRate =                 1000000L;
    pfi->dwLength =               pf->avihdr.dwTotalFrames;
    pfi->dwEditCount =            0;

    LoadUnicodeString(ghMod, IDS_AVIFILE, pfi->szFileType, NUMELMS(pfi->szFileType));

    return AVIERR_OK;
}


///////////////////////////////////////////////////////////////////////////
//
//  AVIFileClose()
//
//      close a AVIFile stream
//
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP_(ULONG) CAVIStream::CUnknownImpl::Release()
{
    CAVIStream FAR * pavi = m_pAVIStream;

    CLock tlock(pavi->pfile);

    uUseCount--;

    if (m_refs < 20) {
        DPF2("Stream %p: Usage--=%lx\n", (DWORD_PTR) (LPVOID) this, m_refs - 1);
    }

    shfileRelease(pavi->hshfile);

    if (!--m_refs) {
        if (pavi->hshfile != pavi->pfile->hshfile) {
            shfileClose(pavi->hshfile, 0);
            pavi->hshfile = 0;
        }

        if (pavi->pb && pavi->pb != pavi->pfile->pb) {
            EndBuffered(pavi->pb);
            pavi->pb = 0;
        }

        if (pavi->psx) {
            FreeStreamIndex(pavi->psx);
            pavi->psx = NULL;
        }

        pavi->fInit = FALSE;

        // this call can cause the AVIFile object to be deleted, and
        // thus we must release the critical section first. There is no
        // danger in this, as nothing unsafe can happen to us between
        // releasing it here and getting it again when we enter
        // the file Release() call.
        tlock.Exit();

        pavi->pfile->m_AVIFile.Release();
        return 0;
    }
    return m_refs;
}

/*      -       -       -       -       -       -       -       -       */

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

void CAVIStream::CAVIStreamImpl::ReadPalette(LONG lPos, LONG lPal, LPRGBQUAD prgb)
{
    CAVIStream FAR * pavi = m_pAVIStream;

    CLock tlock(pavi->pfile);

    LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER) pavi->lpFormat;
    LONG l;
    int i,n;

    static struct {
        BYTE                bFirstEntry;    /* first entry to change */
        BYTE                bNumEntries;    /* # entries to change (0 if 256) */
        WORD                wFlags;         /* Mostly to preserve alignment... */
        PALETTEENTRY        peNew[256];     /* New color specifications */
    } pc;

    DPF("Reading palette: lPos = %ld, lPal = %ld\n", lPos, lPal);

    if (lPal > lPos)
        lPal = 0;

    //
    //  get the palette colors in the initial format header
    //
    if (lPal <= 0) {
        hmemcpy(prgb,(LPBYTE)lpbi+(int)lpbi->biSize, lpbi->biClrUsed * sizeof(RGBQUAD));
        lPal = -1;
    }

    for (;;) {
        //
        //  search index forward for next palette change
        //
        l = StreamFindSample(pavi->psx, lPal+1, FIND_FORMAT|FIND_NEXT);

        if (l < 0 || l > lPos || l == lPal)
            break;

        lPal = l;

        if (l <= (LONG) pavi->avistream.dwStart)
            continue;

        LONG off = StreamFindSample(pavi->psx, lPal, FIND_FORMAT|FIND_OFFSET);
        LONG len = StreamFindSample(pavi->psx, lPal, FIND_FORMAT|FIND_LENGTH);

#ifdef DEBUG
        DWORD adw[2];
        shfileSeek(pavi->hshfile, off-8, SEEK_SET);
        shfileRead(pavi->hshfile, (HPSTR)adw, sizeof(adw));
        Assert(TWOCCFromFOURCC(adw[0]) == cktypePALchange);
        Assert(adw[1] == (DWORD) len);
#endif

        if (len > (LONG)(sizeof(AVIPALCHANGE) + (LONG)lpbi->biClrUsed * sizeof(PALETTEENTRY) * 2)) {
            DPF("Palette chunk obviously too large!\n");
            break;
        }

        //
        //  read palchange from file and apply it
        //
        shfileSeek(pavi->hshfile, off, SEEK_SET);

        while (len >= (LONG)sizeof(AVIPALCHANGE)) {

            if (shfileRead(pavi->hshfile, (HPSTR)&pc, sizeof(AVIPALCHANGE)) !=
                        sizeof(AVIPALCHANGE)) {
                DPF("Error reading palette change\n");
                break;
            }

            n = pc.bNumEntries == 0 ? 256 : (int)pc.bNumEntries;

            if ((DWORD) n > lpbi->biClrUsed) {
                DPF("%d colors in palette change, only %lu in movie!\n", n, lpbi->biClrUsed);
                break;
            }

            if (pc.bFirstEntry + n > (int)lpbi->biClrUsed) {
                DPF("%d colors in palette change, only %lu in movie!\n", n, lpbi->biClrUsed);
                break;
            }

            if (shfileRead(pavi->hshfile,
                           (HPSTR)&pc.peNew,
                           n * sizeof(PALETTEENTRY)) !=
                   (LONG) (n * sizeof(PALETTEENTRY))) {
                DPF("Error reading palette change entries\n");
                break;
            }

            for (i=0; i<n; i++) {
                pavi->argbq[pc.bFirstEntry+i].rgbRed       = pc.peNew[i].peRed;
                pavi->argbq[pc.bFirstEntry+i].rgbGreen     = pc.peNew[i].peGreen;
                pavi->argbq[pc.bFirstEntry+i].rgbBlue      = pc.peNew[i].peBlue;
                pavi->argbq[pc.bFirstEntry+i].rgbReserved  = 0;
            }

            len -= n * sizeof(PALETTEENTRY) + sizeof(AVIPALCHANGE);
        }
    }
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::ReadFormat(LONG lPos, LPVOID lpFormat, LONG FAR *lpcbFormat)
{
    CAVIStream FAR * pavi = m_pAVIStream;
    CLock tlock(pavi->pfile);

    LONG lPal;
    LPBITMAPINFOHEADER lpbi;

    if (lpcbFormat == NULL)
        return ResultFromScode(AVIERR_BADPARAM);

    if (lpFormat == NULL || *lpcbFormat == 0) {
        *lpcbFormat = pavi->cbFormat;
        return AVIERR_OK;
    }

    if (pavi->avistream.dwFlags & AVISF_VIDEO_PALCHANGES) {

        Assert(pavi->psx);

        //
        //  now go find the nearest palette change
        //
        lPal = StreamFindSample(pavi->psx, lPos, FIND_FORMAT|FIND_PREV);

        if (lPal < 0)
            lPal = 0;

        if (lPal != pavi->lPal) {
            ReadPalette(lPal, pavi->lPal, pavi->argbq);
            pavi->lPal = lPal;
        }

        lpbi = (LPBITMAPINFOHEADER) pavi->lpFormat;

        hmemcpy(lpFormat, lpbi, min((LONG) lpbi->biSize, *lpcbFormat));

        if (*lpcbFormat > (LONG) lpbi->biSize) {
            hmemcpy((LPBYTE)lpFormat + (int)lpbi->biSize, pavi->argbq,
                    min(lpbi->biClrUsed * sizeof(RGBQUAD),
                        *lpcbFormat - lpbi->biSize));
        }
    }
    else {
        hmemcpy(lpFormat, pavi->lpFormat, min(*lpcbFormat, pavi->cbFormat));
    }

    if (*lpcbFormat < pavi->cbFormat) {
        *lpcbFormat = pavi->cbFormat;
        return ResultFromScode(AVIERR_BUFFERTOOSMALL);
    }

    *lpcbFormat = pavi->cbFormat;

    return AVIERR_OK;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::Create(LPARAM lParam1, LPARAM lParam2)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::Info(AVISTREAMINFOW FAR * psi, LONG lSize)
{
    CAVIStream FAR * pavi = m_pAVIStream;

    CLock tlock(pavi->pfile);

    if (psi == NULL)
        return ResultFromScode(AVIERR_BADPARAM);

    if (lSize < sizeof(pavi->avistream))
        return ResultFromScode(AVIERR_BUFFERTOOSMALL);

    hmemcpy(psi, &pavi->avistream, sizeof(pavi->avistream));

    return AVIERR_OK;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP_(LONG) CAVIStream::CAVIStreamImpl::FindSample(LONG lPos, LONG lFlags)
{
    CAVIStream FAR * pavi = m_pAVIStream;
    CLock tlock(pavi->pfile);

    if (pavi->paviBase) {
        // If we haven't copied over the data yet, delegate.
        return AVIStreamFindSample(pavi->paviBase, lPos, lFlags);
    }

#ifdef _WIN32
    if (!lPos && (lFlags & FIND_FROM_START)) {
        lPos = pavi->avistream.dwStart;
    } else
#endif
    if (lPos < (LONG)pavi->avistream.dwStart)
        return -1;

    if (lPos >= (LONG)(pavi->avistream.dwStart + pavi->avistream.dwLength))
        return -1;

    lPos = StreamFindSample(pavi->psx, lPos, (UINT)lFlags);

    return lPos < 0 ? -1 : lPos;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::Read(
                 LONG       lStart,
                 LONG       lSamples,
                 LPVOID     lpBuffer,
                 LONG       cbBuffer,
                 LONG FAR * plBytes,
                 LONG FAR * plSamples)
{
    CAVIStream FAR *    pavi = m_pAVIStream;
    LONG                lBytes;
    CLock tlock(pavi->pfile);

    if (pavi->paviBase) {
        // If we haven't copied over the data yet, delegate.
        return AVIStreamRead(pavi->paviBase, lStart, lSamples,
                             lpBuffer, cbBuffer, plBytes, plSamples);
    }

    Assert(pavi->psx);

    // !!! What if start too big? Length too long?

    if (lStart < (LONG) pavi->avistream.dwStart) {
        DPF("Read before start!\n");
        return ResultFromScode(AVIERR_BADPARAM);
    }

    // Handle one of the sillier aspects of AVI files:
    // Certain RLE-encoded files have their first frames split
    // up into lots of small pieces.  This code puts all of those
    // pieces back together again if necessary.

    if ((lStart == (LONG) pavi->avistream.dwStart) &&
                    (pavi->avistream.fccType == streamtypeVIDEO) &&
                    (pavi->avistream.dwInitialFrames > 0)) {

        LPBITMAPINFOHEADER  lpbi = (LPBITMAPINFOHEADER) pavi->lpFormat;
        LPVOID              lp;

        lStart -= (LONG) pavi->avistream.dwInitialFrames;
        lBytes = (DWORD)(WORD)DIBWIDTHBYTES(lpbi) * (DWORD)(WORD)lpbi->biHeight;

        //
        // a NULL buffer means return the size buffer needed to read
        // the given sample.
        //
        if (lpBuffer == NULL || cbBuffer == 0) {

            if (plBytes)
                *plBytes = lBytes;

            return AVIERR_OK;
        }

        if (cbBuffer < lBytes) {
            if (plBytes)
                *plBytes = lBytes;
            DPF("ReadFirst: Buffer is %ld bytes, needed %ld\n", cbBuffer, lBytes);
            return ResultFromScode(AVIERR_BUFFERTOOSMALL);
        }

        lp = GlobalAllocPtr(GMEM_MOVEABLE, lBytes);

        if (!lp)
            return ResultFromScode(AVIERR_MEMORY);

        while (lStart <= (LONG)pavi->avistream.dwStart) {

            if (StreamRead(pavi->psx, lStart, 1, lp, lBytes) < 0) {
                GlobalFreePtr(lp);
                return ResultFromScode(AVIERR_FILEREAD);
            }

            // We probably shouldn't assume RLE here....
            DecodeRle(lpbi, (BYTE _huge *) lpBuffer, (BYTE _huge *) lp, lBytes);
            lStart++;
        }

        GlobalFreePtr(lp);
        goto done;
    }

    //
    // do the read
    //
    lBytes = StreamRead(pavi->psx,lStart,lSamples,lpBuffer,cbBuffer);

    //
    // check for error
    //
    if (lBytes < 0) {

        if (plBytes)
            *plBytes = 0;

        if (plSamples)
            *plSamples = 0;

        if (cbBuffer == 0)
            return ResultFromScode(AVIERR_ERROR);

        //
        // the error may have been buffer too small, check this.
        //
        if (cbBuffer < pavi->psx->lSampleSize)
            return ResultFromScode(AVIERR_BUFFERTOOSMALL);

        lBytes = StreamFindSample(pavi->psx,lStart,FIND_PREV|FIND_LENGTH);

        if (cbBuffer < lBytes) {

            if (plBytes)
                *plBytes = lBytes;

            return ResultFromScode(AVIERR_BUFFERTOOSMALL);
        }
        else
            return ResultFromScode(AVIERR_FILEREAD);
    }

done:
    if (plBytes)
        *plBytes = lBytes;

    if (plSamples) {
        LONG lSampleSize = pavi->psx->lSampleSize;

        if (lSampleSize)
            *plSamples = lBytes / lSampleSize;
        else
            *plSamples = 1;
    }

    return AVIERR_OK;
}

/**************************************************************************
* @doc  INTERNAL DRAWDIB
*
* @api BOOL | DibEq | This function compares two dibs.
*
* @parm LPBITMAPINFOHEADER lpbi1 | Pointer to one bitmap.
*       this DIB is assumed to have the colors after the BITMAPINFOHEADER
*
* @parm LPBITMAPINFOHEADER | lpbi2 | Pointer to second bitmap.
*       this DIB is assumed to have the colors after biSize bytes.
*
* @rdesc Returns TRUE if bitmaps are identical, FALSE otherwise.
*
**************************************************************************/
inline BOOL DibEq(LPBITMAPINFOHEADER lpbi1, LPBITMAPINFOHEADER lpbi2)
{
    return
        lpbi1->biCompression == lpbi2->biCompression   &&
        lpbi1->biSize        == lpbi2->biSize          &&
        lpbi1->biWidth       == lpbi2->biWidth         &&
        lpbi1->biHeight      == lpbi2->biHeight        &&
        lpbi1->biBitCount    == lpbi2->biBitCount;
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::SetFormat(LONG lPos,LPVOID lpFormat,LONG cbFormat)
{
    CAVIStream FAR *    pavi = m_pAVIStream;
    CLock tlock(pavi->pfile);
    LPBITMAPINFOHEADER  lpbi = (LPBITMAPINFOHEADER) lpFormat;
    int                 i;
    RGBQUAD FAR *       lprgb;
    struct {
        BYTE            bFirstEntry;    /* first entry to change */
        BYTE            bNumEntries;    /* # entries to change (0 if 256) */
        WORD            wFlags;         /* Mostly to preserve alignment... */
        PALETTEENTRY    pe[256];
    } s;


    //
    // Make sure the stream isn't read-only
    //
    if ((pavi->pfile->mode & (OF_WRITE | OF_READWRITE)) == 0)
        return ResultFromScode(AVIERR_READONLY);

    if (pavi->lpFormat == NULL) {
        // !!! If we are writing to an existing file, and not to a new file, we
        // have a limitation where we cannot grow the size of the header.
        // Check to see if the header will take up too much room!
        if (pavi->pfile->lWriteLoc > 0) {
            LONG lHeader = pavi->cbFormat +
                           pavi->pfile->lHeaderSize +
                           2 * sizeof(DWORD);
            if (lHeader > pavi->pfile->lDataListStart) {
                DPF("Header will be too big with this format!\n");
                return ResultFromScode(AVIERR_UNSUPPORTED);
            }
        }
        pavi->pfile->lHeaderSize += cbFormat + 2 * sizeof(DWORD);

        // This is a new stream, whose format hasn't been set.
        pavi->lpFormat = GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE, cbFormat);
        if (!pavi->lpFormat) {
            return ResultFromScode(AVIERR_MEMORY);
        }

        hmemcpy(pavi->lpFormat, lpFormat, cbFormat);
        pavi->cbFormat = cbFormat;

        if (pavi->avistream.fccType == streamtypeVIDEO) {

            if (ISRECTBOGUS(&pavi->avistream.rcFrame,
                            pavi->pfile->avihdr.dwWidth,
                            pavi->pfile->avihdr.dwHeight,
                            lpbi)) {
                DPF("Resetting stream rectangle....\n");
                SetRect(&pavi->avistream.rcFrame, 0, 0,
                    (int)lpbi->biWidth, (int)lpbi->biHeight);
            }

            if (lpbi->biClrUsed > 0) {
                // Get the right colors, so that we can detect palette changes
                hmemcpy(pavi->argbq,
                        (LPBYTE) lpbi + lpbi->biSize,
                        lpbi->biClrUsed * sizeof(RGBQUAD));
            }

            /* Make sure the width and height of the created file are right.... */
            pavi->pfile->avihdr.dwWidth = max(pavi->pfile->avihdr.dwWidth,
                                 (DWORD) pavi->avistream.rcFrame.right);
            pavi->pfile->avihdr.dwHeight = max(pavi->pfile->avihdr.dwHeight,
                                 (DWORD) pavi->avistream.rcFrame.bottom);
        }

        return ResultFromScode(AVIERR_OK);
    }

    //
    // First, check if the format is actually different....
    //
    if (cbFormat == pavi->cbFormat &&
                (_fmemcmp(pavi->lpFormat, lpFormat, (int) cbFormat) == 0))
        return ResultFromScode(AVIERR_OK);

    //
    // We really only support format changes if they're palette changes...
    //
    if (pavi->avistream.fccType != streamtypeVIDEO) {
        return ResultFromScode(AVIERR_UNSUPPORTED);
    }

    //
    // Can only currently set the palette at the end of the file
    //
    if (lPos < (LONG) (pavi->avistream.dwStart + pavi->avistream.dwLength))
        return ResultFromScode(AVIERR_UNSUPPORTED);

    //
    // We can only change the palette for things with palettes....
    //
    if (lpbi->biBitCount > 8 || lpbi->biClrUsed == 0)
        return ResultFromScode(AVIERR_UNSUPPORTED);

    //
    // Be sure only the palette is changing, nothing else....
    //
    if (cbFormat != pavi->cbFormat)
        return ResultFromScode(AVIERR_UNSUPPORTED);

    if (!DibEq((LPBITMAPINFOHEADER) lpFormat,
               (LPBITMAPINFOHEADER) pavi->lpFormat))
        return ResultFromScode(AVIERR_UNSUPPORTED);

    // !!! Need to do here:
    // Get the correct palette for this point in the file, and check
    // that the new palette is in fact different.
    lprgb = (RGBQUAD FAR *) ((LPBYTE) lpbi + lpbi->biSize);

    if (_fmemcmp(pavi->argbq, lprgb, (UINT) lpbi->biClrUsed * sizeof(RGBQUAD)) == 0)
        return ResultFromScode(AVIERR_OK);

    //
    // Make the new format the current one....
    //
    hmemcpy(pavi->argbq, lprgb, lpbi->biClrUsed * sizeof(RGBQUAD));
    pavi->lPal = lPos;

    //
    // And be sure the stream is marked as having changes...
    //
    pavi->avistream.dwFlags |= AVISF_VIDEO_PALCHANGES;

    s.bFirstEntry = 0;
    s.bNumEntries = (BYTE) lpbi->biClrUsed;
    s.wFlags = 0;
    for (i = 0; i < (int) lpbi->biClrUsed; i++, lprgb++) {
        s.pe[i].peRed = lprgb->rgbRed;
        s.pe[i].peGreen = lprgb->rgbGreen;
        s.pe[i].peBlue = lprgb->rgbBlue;
    }

    // !!! Hack: use Write to write the palette change....

    return Write(lPos,
                 0,
                 &s,
                 sizeof(AVIPALCHANGE) + lpbi->biClrUsed * sizeof(PALETTEENTRY),
                 AVIIF_NOTIME, NULL, NULL);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::Write(LONG lStart,
                                               LONG lSamples,
                                               LPVOID lpData,
                                               LONG cbData,
                                               DWORD dwFlags,
                                               LONG FAR *plSampWritten,
                                               LONG FAR *plBytesWritten)
{
    CAVIStream FAR *        pavi = m_pAVIStream;
    CLock tlock(pavi->pfile);
    MMCKINFO        ck;
    WORD            cktype;
    HRESULT         hr;
    DWORD           dwmsec;

    // !!! Idea: if it's audio-like data, and everything else matches the
    // last chunk written out, then merge the new data in with the old
    // data, rather than making a new chunk....

    if ((pavi->pfile->mode & (OF_WRITE | OF_READWRITE)) == 0)
        return ResultFromScode(AVIERR_READONLY);

    if (!pavi->lpFormat) {
        // The format must be set before any write calls
        // are made....
        return ResultFromScode(E_UNEXPECTED);
    }

    if (pavi->avistream.fccType == streamtypeAUDIO)
        cktype = aviTWOCC('w', 'b');
    else if (pavi->avistream.fccType == streamtypeVIDEO) {
        if (dwFlags & AVIIF_NOTIME)
            cktype = aviTWOCC('p', 'c');
        else {
            LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER) pavi->lpFormat;

            if ((dwFlags & AVIIF_KEYFRAME) ||
                (lpbi->biCompression <= BI_RLE8 && cbData == (LONG) lpbi->biSizeImage))
                cktype = aviTWOCC('d', 'b');
            else
                cktype = aviTWOCC('d', 'c');
            // !!! 00dx ack!
        }
    } else {
        cktype = aviTWOCC('d', 'c');
    }

    ck.ckid = MAKEAVICKID(cktype, pavi->iStream);
    ck.cksize = cbData;

    if (lStart < 0)
        lStart = pavi->avistream.dwStart + pavi->avistream.dwLength;

    if (lStart > (LONG) (pavi->avistream.dwStart + pavi->avistream.dwLength)) {
        if (pavi->avistream.dwSampleSize == 0) {
            // !!! hack--insert lots of blank index entries....

            while (lStart > (LONG) (pavi->avistream.dwStart + pavi->avistream.dwLength)) {
#if 1
                hr = Write(pavi->avistream.dwStart + pavi->avistream.dwLength,
                            1,
                            NULL,
                            0,
                            0,
                            NULL,
                            NULL);

                if (FAILED(hr))
                    return hr;
#else
                if (!AddToIndex(pavi->pfile, ck.ckid, 0, 0, 0))
                    return ResultFromScode(AVIERR_MEMORY);

                ++pavi->avistream.dwLength;
                pavi->pfile->avihdr.dwFlags |= AVIF_MUSTUSEINDEX;
#endif
            }
        } else
            return ResultFromScode(AVIERR_BADPARAM);
    }

    if (lStart < (LONG) (pavi->avistream.dwStart + pavi->avistream.dwLength))
        return ResultFromScode(AVIERR_UNSUPPORTED);

    pavi->pfile->fDirty = TRUE;

    if (pavi->pfile->lWriteLoc == 0) {
        pavi->pfile->lWriteLoc = (pavi->pfile->lHeaderSize + 1024 + 2047) & ~(2047);
        pavi->pfile->lDataListStart = pavi->pfile->lWriteLoc - 3 * sizeof(DWORD);
        DPF("Writing first chunk at position %lu\n", pavi->pfile->lWriteLoc);
    }

#if 0
    if ((lStart == (LONG) (pavi->avistream.dwStart + pavi->avistream.dwLength)) &&
        (pavi->avistream.fccType == streamtypeAUDIO) &&
        (pavi->pfile->lIndex > 0)) {
            AVIINDEXENTRY   idx = pavi->pfile->pIndex[pavi->pfile->lIndex - 1];

            if ((idx.ckid == ckid) &&
                (idx.dwChunkOffset +
                 2 * sizeof(DWORD) +
                 idx.dwChunkLength == lWriteLoc)) {

                // We could append to the previous chunk here....

            }
    }

#endif

#ifdef DONTWRITEZEROLENGTH
    if (cbData == 0) {
        ck.dwDataOffset = 0;
        pavi->pfile->avihdr.dwFlags |= AVIF_MUSTUSEINDEX;
    } else
#endif
    {
        shfileSeek(pavi->hshfile, pavi->pfile->lWriteLoc, SEEK_SET);
        shfileCreateChunk(pavi->hshfile, &ck, 0);

        if (cbData) {
            if (shfileWrite(pavi->hshfile, (HPSTR) lpData, cbData) != cbData)
                return ResultFromScode(AVIERR_FILEWRITE);
        }

        if (shfileAscend(pavi->hshfile, &ck, 0) != 0)
            return ResultFromScode(AVIERR_FILEWRITE);

        pavi->pfile->lWriteLoc = shfileSeek(pavi->hshfile, 0, SEEK_CUR);
    }

    if (!AddToIndex(pavi->pfile, ck.ckid, cbData,
                    ck.dwDataOffset - 2 * sizeof(DWORD), dwFlags))
        return ResultFromScode(AVIERR_MEMORY);

    //
    // if we dont have a stream index now is a good time to make one.
    //
    if (pavi->psx == NULL) {

        pavi->psx = MakeStreamIndex(pavi->pfile->px, pavi->iStream,
            (LONG)pavi->avistream.dwStart - pavi->avistream.dwInitialFrames,
            (LONG)pavi->avistream.dwSampleSize,
            pavi->pfile->hshfile, shfileReadProc, NULL);

        //!!! what about pavi->pb

        if (!(dwFlags & AVIIF_NOTIME))
            pavi->psx->lEnd -= lSamples;    // correct for the decrement below
    }

    if (pavi->psx == NULL) {
        DPF("CAVIStream::Write no stream index!\n");
        return ResultFromScode(AVIERR_MEMORY);
    }

    if (!(dwFlags & AVIIF_NOTIME)) {
        pavi->avistream.dwLength += lSamples;

        if (pavi->psx)
            pavi->psx->lEnd += lSamples;
    }

    if (cbData > (LONG) pavi->avistream.dwSuggestedBufferSize)
        pavi->avistream.dwSuggestedBufferSize = cbData;

    if (cbData > (LONG) pavi->pfile->avihdr.dwSuggestedBufferSize)
        pavi->pfile->avihdr.dwSuggestedBufferSize = cbData;

    // Recalculate the overall file length....
    dwmsec = muldiv32(pavi->avistream.dwLength,
                             pavi->avistream.dwScale * 1000L,
                             pavi->avistream.dwRate);
    pavi->pfile->avihdr.dwTotalFrames =
        max(pavi->pfile->avihdr.dwTotalFrames,
            (DWORD) muldiv32(dwmsec, 1000L,
                             pavi->pfile->avihdr.dwMicroSecPerFrame));
    // !!! The above calculation could easily overflow.
    // !!! NEEDS TO BE REORGANIZED!

    if (plBytesWritten)
        *plBytesWritten = cbData;

    if (plSampWritten)
        *plSampWritten = lSamples;

    return ResultFromScode(AVIERR_OK);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::Delete(LONG lStart,LONG lSamples)
{
    CAVIStream FAR *      pavi = m_pAVIStream;
    CLock tlock(pavi->pfile);

    if ((pavi->pfile->mode & (OF_WRITE | OF_READWRITE)) == 0)
        return ResultFromScode(AVIERR_READONLY);

    // go through and kill things from the index?

    // !!! what about keyframe boundaries?

    return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::ReadData(DWORD ckid, LPVOID lp, LONG FAR *lpcb)
{
    CAVIStream FAR *      pavi = m_pAVIStream;
    CLock tlock(pavi->pfile);

    return ReadExtra(&pavi->extra, ckid, lp, lpcb);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::WriteData(DWORD ckid, LPVOID lp, LONG cb)
{
    CAVIStream FAR *      pavi = m_pAVIStream;
    CLock tlock(pavi->pfile);

    DPF("WriteData asked to write %ld bytes\n", cb);

    if ((pavi->pfile->mode & (OF_WRITE | OF_READWRITE)) == 0)
        return ResultFromScode(AVIERR_READONLY);

    // !!! If we are writing to an existing file, and not to a new file, we have
    // a limitation where we cannot grow the size of the header.
    // Check to see if the header will take up too much room.
    if (pavi->pfile->lWriteLoc > 0) {
        LONG lHeader = cb + pavi->pfile->lHeaderSize + 2 * sizeof(DWORD);
        if (lHeader > pavi->pfile->lDataListStart) {
            DPF("Header will be too big with this extra data!\n");
            return ResultFromScode(AVIERR_UNSUPPORTED);
        }
    }

    pavi->pfile->lHeaderSize += cb + 3 * sizeof(DWORD);

    pavi->pfile->fDirty = TRUE;

    return WriteExtra(&pavi->extra, ckid, lp, cb);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

#if 0
STDMETHODIMP CAVIStream::CAVIStreamImpl::Clone(PAVISTREAM FAR * ppaviNew)
{
    CAVIStream FAR *      pavi = m_pAVIStream;

    return ResultFromScode(AVIERR_UNSUPPORTED);
}
#endif


STDMETHODIMP CAVIStream::CStreamingImpl::Begin(LONG  lStart,
                                               LONG  lEnd,
                                               LONG  lRate)
{
    CAVIStream FAR *      pavi = m_pAVIStream;
    CLock tlock(pavi->pfile);

#ifdef USE_DIRECTIO
    if (shfileIsDirect(pavi->hshfile)) {
        shfileStreamStart(pavi->hshfile);
    } else
#endif
        if (pavi->pb)
            BeginBufferedStreaming(pavi->pb, lRate > 0);

    return ResultFromScode(AVIERR_OK);
}


STDMETHODIMP CAVIStream::CStreamingImpl::End()
{
    CAVIStream FAR *      pavi = m_pAVIStream;
    CLock tlock(pavi->pfile);

#ifdef USE_DIRECTIO
    if (shfileIsDirect(pavi->hshfile)) {
        shfileStreamStop(pavi->hshfile);
    } else
#endif
        if (pavi->pb)
            EndBufferedStreaming(pavi->pb);

    return ResultFromScode(AVIERR_OK);
}



///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

static
BOOL AddToIndex(CAVIFile FAR * pfile, DWORD ckid, DWORD cksize, LONG off, DWORD dwFlags)
{
    PAVIINDEX px;
    AVIINDEXENTRY idx;

    idx.ckid          = ckid;
    idx.dwChunkOffset = off;
    idx.dwChunkLength = cksize;
    idx.dwFlags       = dwFlags;

    px = IndexAddFileIndex(pfile->px, &idx, 1, 0, FALSE);

    if (px == NULL)
        return FALSE;

    //
    // GlobalReAlloc may have moved our pointer, we need to patch all
    // places we use it!
    //
    if (px != pfile->px) {

        DPF("Index pointer has changed!\n");

        pfile->px = px;

        for (int i=0; i<(int)pfile->avihdr.dwStreams; i++) {

            CAVIStream FAR *ps = pfile->ps[i];

            if (ps->psx)
                ps->psx->px = px;
        }

        if (pfile->pb)
            pfile->pb->px = px;
    }

    return TRUE;
}

#ifdef _WIN32
STDMETHODIMP CAVIStream::CAVIStreamImpl::SetInfo(AVISTREAMINFOW FAR *lpInfo, LONG cbInfo)
{
    CAVIStream FAR * pavi = m_pAVIStream;

    CLock tlock(pavi->pfile);

    if ((pavi->pfile->mode & (OF_WRITE | OF_READWRITE)) == 0)
        return ResultFromScode(AVIERR_READONLY);

    if ((cbInfo < sizeof(AVISTREAMINFOW)) ||
        (IsBadReadPtr(lpInfo, sizeof(AVISTREAMINFOW))))
        return ResultFromScode(AVIERR_BADPARAM);

    // Things we don't copy:
    // fccType
    // fccHandler
    // dwFlags
    // dwCaps
    // dwLength
    // dwInitialFrames
    // dwSuggestedBufferSize
    // dwSampleSize
    // dwEditCount
    // dwFormatChangeCount

    pavi->avistream.wPriority = lpInfo->wPriority;
    pavi->avistream.wLanguage = lpInfo->wLanguage;
    pavi->avistream.dwScale   = lpInfo->dwScale;
    pavi->avistream.dwRate    = lpInfo->dwRate;
    pavi->avistream.dwStart   = lpInfo->dwStart;  // !!! ???
    pavi->avistream.dwQuality = lpInfo->dwQuality;
    pavi->avistream.rcFrame   = lpInfo->rcFrame;

    if (lpInfo->szName[0])
        _fmemcpy(pavi->avistream.szName, lpInfo->szName, sizeof(pavi->avistream.szName));

    pavi->pfile->fDirty = TRUE;

    return NOERROR;
}

STDMETHODIMP CAVIFile::CAVIFileImpl::DeleteStream(DWORD fccType, LONG lParam)
{
    CAVIFile FAR * pfile = m_pAVIFile;

    CAVIStream FAR *pavi;
    int             iStreamCur;
    int             iStreamWant;
    int             iStream;

    // thread locking
    CLock tlock(pfile);

    iStreamWant = (int)lParam;

    if (iStreamWant < 0 || iStreamWant >= (int)pfile->avihdr.dwStreams)
        return ResultFromScode(AVIERR_NODATA);

    /* Allocate stream data stuff, read streams */
    for (iStreamCur = -1, iStream = 0;
            iStream < (int)pfile->avihdr.dwStreams;
            iStream++) {
        if (fccType == 0 || pfile->ps[iStream]->avistream.fccType == fccType)
            iStreamCur++;

        if (iStreamCur == iStreamWant)
            break;
    }

    if (iStreamCur != iStreamWant)
        return ResultFromScode(AVIERR_NODATA);

    pavi = pfile->ps[iStream];

    // Is somebody using this stream?
    if (pavi->fInit)
        return ResultFromScode(AVIERR_UNSUPPORTED);

    pfile->avihdr.dwStreams++;

    pfile->lHeaderSize -= sizeof(AVIStreamHeader) + 8 * sizeof(DWORD) +
                       lstrlenW(pavi->avistream.szName);

    while (iStream < (int) pfile->avihdr.dwStreams) {
        pfile->ps[iStream] = pfile->ps[iStream + 1];
        iStream++;
    }

    delete pavi;

    return ResultFromScode(AVIERR_UNSUPPORTED);
}
#else
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIFileImpl::Reserved1(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIFileImpl::Reserved2(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIFileImpl::Reserved3(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIFileImpl::Reserved4(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIFile::CAVIFileImpl::Reserved5(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::Reserved1(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::Reserved2(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::Reserved3(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::Reserved4(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

STDMETHODIMP CAVIStream::CAVIStreamImpl::Reserved5(void)
{
    return ResultFromScode(AVIERR_UNSUPPORTED);
}

#endif

#ifdef CUSTOMMARSHAL
CAVIFile::CMarshalImpl::CMarshalImpl(
        CAVIFile FAR*   pAVIFile)
{
        m_pAVIFile = pAVIFile;
}


STDMETHODIMP CAVIFile::CMarshalImpl::QueryInterface(
        const IID FAR&  iid,
        void FAR* FAR*  ppv)
{
        return m_pAVIFile->m_pUnknownOuter->QueryInterface(iid, ppv);
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CMarshalImpl::AddRef()
{
        return m_pAVIFile->m_pUnknownOuter->AddRef();
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CMarshalImpl::Release()
{
        return m_pAVIFile->m_pUnknownOuter->Release();
}

/*      -       -       -       -       -       -       -       -       */



// *** IMarshal methods ***
STDMETHODIMP CAVIFile::CMarshalImpl::GetUnmarshalClass (THIS_ REFIID riid, LPVOID pv,
                    DWORD dwDestContext, LPVOID pvDestContext,
                    DWORD mshlflags, LPCLSID pCid)
{
    HRESULT hr = NOERROR;
    IUnknown FAR * pUnk = &m_pAVIFile->m_Unknown;

    DPF("(F) UnMarshalClass called (context = %lx)\n", dwDestContext);

    if (dwDestContext != MSHCTX_LOCAL) {
        LPMARSHAL    pMarshal;

        DPF("Marshal context is %lu: delegating...\n", dwDestContext);

        hr = CoGetStandardMarshal(riid, NULL,
                                  dwDestContext, pvDestContext,
                                  mshlflags, &pMarshal);

        if (hr != NOERROR)
            return hr;

        hr = pMarshal->GetUnmarshalClass(riid, pv,
                                         dwDestContext, pvDestContext,
                                         mshlflags, pCid);

        pMarshal->Release();

        return hr;
    }
    *pCid = CLSID_AVISimpleUnMarshal;
    return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::GetMarshalSizeMax (THIS_ REFIID riid, LPVOID pv,
                    DWORD dwDestContext, LPVOID pvDestContext,
                    DWORD mshlflags, LPDWORD pSize)
{
    HRESULT hr = NOERROR;
    IUnknown FAR * pUnk = &m_pAVIFile->m_Unknown;

    if (dwDestContext != MSHCTX_LOCAL) {
        LPMARSHAL    pMarshal;

        hr = CoGetStandardMarshal(riid, NULL, dwDestContext, pvDestContext,
                                  mshlflags, &pMarshal);

        if (hr != NOERROR)
            return hr;

        hr = pMarshal->GetMarshalSizeMax(riid, pv,
                                         dwDestContext, pvDestContext,
                                         mshlflags, pSize);

        pMarshal->Release();

        return hr;
    }
    *pSize = sizeof(pUnk);

    return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::MarshalInterface (THIS_ LPSTREAM pStm, REFIID riid,
                    LPVOID pv, DWORD dwDestContext, LPVOID pvDestContext,
                    DWORD mshlflags)
{
    HRESULT hr = NOERROR;
    IUnknown FAR * pUnk = &m_pAVIFile->m_Unknown;

    DPF("MarshalInterface (F) called\n");

    if (dwDestContext != MSHCTX_LOCAL) {
        LPMARSHAL    pMarshal;

        DPF("Marshal context is %lu: delegating...\n", dwDestContext);

        hr = CoGetStandardMarshal(riid, NULL,
                                  dwDestContext, pvDestContext,
                                  mshlflags, &pMarshal);

        if (hr != NOERROR)
            return hr;

        hr = pMarshal->MarshalInterface(pStm, riid, pv,
                                        dwDestContext, pvDestContext,
                                        mshlflags);

        pMarshal->Release();

        return hr;
    }

    if ((riid != IID_IAVIStream && riid != IID_IAVIFile && riid != IID_IUnknown))
        return ResultFromScode(E_INVALIDARG);

    if ((hr = pStm->Write(&pUnk, sizeof(pUnk), NULL)) == NOERROR)
        AddRef();

    DPF("Returns %lx\n", (DWORD) (LPVOID) hr);
    return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::UnmarshalInterface (THIS_ LPSTREAM pStm, REFIID riid,
                    LPVOID FAR* ppv)
{
    HRESULT hr = ResultFromScode(E_FAIL);

    DPF("(F) UnMarshalInterface called!!!\n");
    return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::ReleaseMarshalData (THIS_ LPSTREAM pStm)
{
    HRESULT hr = NOERROR;
    IUnknown FAR * pUnk;

    hr = pStm->Read(&pUnk,sizeof(pUnk),NULL);
    DPF("(F) ReleaseMarshalData\n");
    if (hr == NOERROR)
        pUnk->Release();

    return hr;
}

STDMETHODIMP CAVIFile::CMarshalImpl::DisconnectObject (THIS_ DWORD dwReserved)
{
    HRESULT hr = NOERROR;

    return hr;
}


CAVIStream::CMarshalImpl::CMarshalImpl(
        CAVIStream FAR* pAVIStream)
{
        m_pAVIStream = pAVIStream;
}


STDMETHODIMP CAVIStream::CMarshalImpl::QueryInterface(
        const IID FAR&  iid,
        void FAR* FAR*  ppv)
{
        return m_pAVIStream->m_pUnknownOuter->QueryInterface(iid, ppv);
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIStream::CMarshalImpl::AddRef()
{
        return m_pAVIStream->m_pUnknownOuter->AddRef();
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIStream::CMarshalImpl::Release()
{
        return m_pAVIStream->m_pUnknownOuter->Release();
}

/*      -       -       -       -       -       -       -       -       */



// *** IMarshal methods ***
STDMETHODIMP CAVIStream::CMarshalImpl::GetUnmarshalClass (THIS_ REFIID riid, LPVOID pv,
                    DWORD dwDestContext, LPVOID pvDestContext,
                    DWORD mshlflags, LPCLSID pCid)
{
    HRESULT hr = NOERROR;
    IUnknown FAR * pUnk = &m_pAVIStream->m_Unknown;

    DPF("(S) UnMarshalClass called (context = %lx)\n", dwDestContext);
    *pCid = CLSID_AVISimpleUnMarshal;
    return hr;
}

STDMETHODIMP CAVIStream::CMarshalImpl::GetMarshalSizeMax (THIS_ REFIID riid, LPVOID pv,
                    DWORD dwDestContext, LPVOID pvDestContext,
                    DWORD mshlflags, LPDWORD pSize)
{
    HRESULT hr = NOERROR;
    IUnknown FAR * pUnk = &m_pAVIStream->m_Unknown;

    *pSize = sizeof(pUnk);

    return hr;
}

STDMETHODIMP CAVIStream::CMarshalImpl::MarshalInterface (THIS_ LPSTREAM pStm, REFIID riid,
                    LPVOID pv, DWORD dwDestContext, LPVOID pvDestContext,
                    DWORD mshlflags)
{
    HRESULT hr = NOERROR;
    IUnknown FAR * pUnk = &m_pAVIStream->m_Unknown;

    DPF("MarshalInterface (S) called\n");
    if ((riid != IID_IAVIStream && riid != IID_IAVIStream && riid != IID_IUnknown))
        return ResultFromScode(E_INVALIDARG);

    if ((hr = pStm->Write(&pUnk, sizeof(pUnk), NULL)) == NOERROR)
        AddRef();

    DPF("Returns %lx\n", (DWORD) (LPVOID) hr);
    return hr;
}

STDMETHODIMP CAVIStream::CMarshalImpl::UnmarshalInterface (THIS_ LPSTREAM pStm, REFIID riid,
                    LPVOID FAR* ppv)
{
    HRESULT hr = ResultFromScode(E_FAIL);

    DPF("(S) UnMarshalInterface called!!!\n");
    return hr;
}

STDMETHODIMP CAVIStream::CMarshalImpl::ReleaseMarshalData (THIS_ LPSTREAM pStm)
{
    HRESULT hr = NOERROR;
    IUnknown FAR * pUnk;

    hr = pStm->Read(&pUnk,sizeof(pUnk),NULL);
    DPF("(S) ReleaseMarshalData\n");
    if (hr == NOERROR)
        pUnk->Release();

    return hr;
}

STDMETHODIMP CAVIStream::CMarshalImpl::DisconnectObject (THIS_ DWORD dwReserved)
{
    HRESULT hr = NOERROR;

    return hr;
}
#endif  // CUSTOMMARSHAL only


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

  DecodeRle   - 'C' version

  Play back a RLE buffer into a DIB buffer

  returns
      none

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

void DecodeRle(LPBITMAPINFOHEADER lpbi, BYTE _huge *pb, BYTE _huge *prle, DWORD dwInSize)
{
    BYTE    cnt;
    BYTE    b;
    WORD    x;
    WORD    dx,dy;
    DWORD   wWidthBytes;
    DWORD   dwOutSize;
    DWORD   dwJump;

    #define RLE_ESCAPE  0
    #define RLE_EOL     0
    #define RLE_EOF     1
    #define RLE_JMP     2
    #define RLE_RUN     3

#if 0
#ifndef _WIN32
    //
    // this uses ASM code found in RLEA.ASM
    //
    if (!(WinFlags & WF_CPU286))
        DecodeRle386(lpbi, pb, prle);
    else if (lpbi->biSizeImage < 65536l)
        DecodeRle286(lpbi, pb, prle);
    else
#endif
#endif
#define EatOutput(_x_) \
    {                            \
        if (dwOutSize < (_x_)) { \
            return;              \
        }                        \
        dwOutSize -= (_x_);      \
    }
#define EatInput(_x_) \
    {                            \
        if (dwInSize < (_x_)) {  \
            return;              \
        }                        \
        dwInSize -= (_x_);       \
    }

    if (lpbi->biHeight <= 0) {
        return;
    }
    {
        wWidthBytes = (WORD)lpbi->biWidth+3 & ~3;
        dwOutSize = wWidthBytes * (DWORD)lpbi->biHeight;

        x = 0;

        for(;;)
        {
            EatInput(2);
            cnt = *prle++;
            b   = *prle++;

            if (cnt == RLE_ESCAPE)
            {
                switch (b)
                {
                    case RLE_EOF:
                        return;

                    case RLE_EOL:
                        EatOutput(wWidthBytes - x);
                        pb += wWidthBytes - x;
                        x = 0;
                        break;

                    case RLE_JMP:
                        EatInput(2);
                        dx = (WORD)*prle++;
                        dy = (WORD)*prle++;

                        dwJump = (DWORD)wWidthBytes * dy + dx;
                        EatOutput(dwJump);
			pb += dwJump;
                        x  += dx;

                        break;

                    default:
                        cnt = b;
                        EatOutput(cnt);
                        EatInput(cnt);
                        x  += cnt;
                        while (cnt-- > 0)
                            *pb++ = *prle++;

                        if (b & 1) {
                            EatInput(1);
                            prle++;
                        }

                        break;
                }
            }
            else
            {
                x += cnt;
                EatOutput(cnt);

                while (cnt-- > 0)
                    *pb++ = b;
            }
        }
    }
}





CAVIFile::CPersistStorageImpl::CPersistStorageImpl(
        CAVIFile FAR*   pAVIFile)
{
        m_pAVIFile = pAVIFile;
}


STDMETHODIMP CAVIFile::CPersistStorageImpl::QueryInterface(
        const IID FAR&  iid,
        void FAR* FAR*  ppv)
{
        return m_pAVIFile->m_pUnknownOuter->QueryInterface(iid, ppv);
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CPersistStorageImpl::AddRef()
{
        return m_pAVIFile->m_pUnknownOuter->AddRef();
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CPersistStorageImpl::Release()
{
        return m_pAVIFile->m_pUnknownOuter->Release();
}

/*      -       -       -       -       -       -       -       -       */

        // *** IPersist methods ***
STDMETHODIMP CAVIFile::CPersistStorageImpl::GetClassID (LPCLSID lpClassID)
{
    CAVIFile FAR * pfile = m_pAVIFile;

    return NOERROR;
}

        // *** IPersistStorage methods ***
STDMETHODIMP CAVIFile::CPersistStorageImpl::IsDirty ()
{
    CAVIFile FAR * pfile = m_pAVIFile;

    if (pfile->fDirty)
        return NOERROR;
    else
        return ResultFromScode(S_FALSE);
}

STDMETHODIMP CAVIFile::CPersistStorageImpl::InitNew (LPSTORAGE pStg)
{
    CAVIFile FAR * pfile = m_pAVIFile;

    return NOERROR;
}

STDMETHODIMP CAVIFile::CPersistStorageImpl::Load (LPSTORAGE pStg)
{
    CAVIFile FAR * pfile = m_pAVIFile;

    return NOERROR;
}

STDMETHODIMP CAVIFile::CPersistStorageImpl::Save (LPSTORAGE pStgSave, BOOL fSameAsLoad)
{
    CAVIFile FAR * pfile = m_pAVIFile;

    return NOERROR;
}

STDMETHODIMP CAVIFile::CPersistStorageImpl::SaveCompleted (LPSTORAGE pStgNew)
{
    CAVIFile FAR * pfile = m_pAVIFile;


    return NOERROR;
}

STDMETHODIMP CAVIFile::CPersistStorageImpl::HandsOffStorage ()
{
    CAVIFile FAR * pfile = m_pAVIFile;

    return NOERROR;
}




CAVIFile::CPersistFileImpl::CPersistFileImpl(
        CAVIFile FAR*   pAVIFile)
{
        m_pAVIFile = pAVIFile;
}


STDMETHODIMP CAVIFile::CPersistFileImpl::QueryInterface(
        const IID FAR&  iid,
        void FAR* FAR*  ppv)
{
        return m_pAVIFile->m_pUnknownOuter->QueryInterface(iid, ppv);
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CPersistFileImpl::AddRef()
{
        return m_pAVIFile->m_pUnknownOuter->AddRef();
}

/*      -       -       -       -       -       -       -       -       */

STDMETHODIMP_(ULONG) CAVIFile::CPersistFileImpl::Release()
{
        return m_pAVIFile->m_pUnknownOuter->Release();
}

/*      -       -       -       -       -       -       -       -       */

        // *** IPersist methods ***
STDMETHODIMP CAVIFile::CPersistFileImpl::GetClassID (LPCLSID lpClassID)
{
    CAVIFile FAR * pfile = m_pAVIFile;

    return NOERROR;
}

        // *** IPersistFile methods ***
STDMETHODIMP CAVIFile::CPersistFileImpl::IsDirty ()
{
    CAVIFile FAR * pfile = m_pAVIFile;

    if (pfile->fDirty)
        return NOERROR;
    else
        return ResultFromScode(S_FALSE);
}

STDMETHODIMP CAVIFile::CPersistFileImpl::Load (LPCOLESTR lpszFileName, DWORD grfMode)
{
    CAVIFile FAR * pfile = m_pAVIFile;
    UINT            ui;

    CLock tlock(pfile);

    if (pfile->achFile[0])
        return ResultFromScode(-1);

    pfile->mode = grfMode;
#if defined _WIN32 && !defined UNICODE
    WideCharToMultiByte(CP_ACP, 0, lpszFileName, -1,
                        pfile->achFile, NUMELMS(pfile->achFile), NULL, NULL);
#else
    lstrcpy(pfile->achFile, lpszFileName);
#endif
    // Assumptions about avilib.cpp:
    // We're assuming that if CREATE is set, WRITE is set too.
    // We're assuming that we'll always see READWRITE instead of just WRITE.

// If it ain't broke, don't fix it - who do I look like, the share flag
// standards enforcing committee?
#if 0
    // force the share flags to the 'correct' values
    if (grfMode & OF_READWRITE) {
        pfile->mode = (grfMode & ~(MMIO_SHAREMODE)) | OF_SHARE_EXCLUSIVE;
    } else {
        pfile->mode = (grfMode & ~(MMIO_SHAREMODE)) | OF_SHARE_DENY_WRITE;
    }
#endif

    // try to open the actual file
    // If the first attempt fails, no system error box, please.
    ui = SetErrorMode(SEM_NOOPENFILEERRORBOX);

    pfile->hshfile = shfileOpen(pfile->achFile, NULL, pfile->mode);

    if (!pfile->hshfile && ((grfMode & MMIO_RWMODE) == OF_READ)) {
        // if the open fails, try again without the share flags.
        pfile->mode &= ~(MMIO_SHAREMODE);

        pfile->hshfile = shfileOpen(pfile->achFile, NULL, pfile->mode);
    }
    SetErrorMode(ui);

    if (pfile->hshfile) {
        shfileAddRef(pfile->hshfile);  // compensate for later release of IPersistFile
        shfileAddRef(pfile->hshfile);  // compensate for later release of IPersistFile
    }

    return pfile->OpenInternal(grfMode);
}

STDMETHODIMP CAVIFile::CPersistFileImpl::Save (LPCOLESTR lpszFileName, BOOL fRemember)
{
    CAVIFile FAR * pfile = m_pAVIFile;

    return NOERROR;
}

STDMETHODIMP CAVIFile::CPersistFileImpl::SaveCompleted (LPCOLESTR lpszFileName)
{
    CAVIFile FAR * pfile = m_pAVIFile;

    return NOERROR;
}

STDMETHODIMP CAVIFile::CPersistFileImpl::GetCurFile (LPOLESTR FAR * lplpszFileName)
{
    CAVIFile FAR * pfile = m_pAVIFile;

    return NOERROR;
}


