Fixed crash in winamp reported by Andreas Mohr.
[wine] / dlls / avifil32 / avifile.c
1 /*
2  * Copyright 1999 Marcus Meissner
3  * Copyright 2002 Michael Günnewig
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19
20 /* TODO:
21  *  - IAVIFile_fnEndRecord: a stub -- needed for creating interleaved AVIs.
22  *  - IAVIStreaming interface is missing for the IAVIStreamImpl
23  *  - IAVIStream_fnFindSample: FIND_INDEX isn't supported.
24  *  - IAVIStream_fnReadFormat: formatchanges aren't read in.
25  *  - IAVIStream_fnDelete: a stub.
26  *  - IAVIStream_fnSetInfo: a stub.
27  */
28
29 #define COM_NO_WINDOWS_H
30 #include <assert.h>
31
32 #include "winbase.h"
33 #include "winuser.h"
34 #include "winnls.h"
35 #include "winerror.h"
36 #include "windowsx.h"
37 #include "mmsystem.h"
38 #include "vfw.h"
39
40 #include "avifile_private.h"
41 #include "extrachunk.h"
42
43 #include "wine/debug.h"
44
45 WINE_DEFAULT_DEBUG_CHANNEL(avifile);
46
47 #ifndef IDX_PER_BLOCK
48 #define IDX_PER_BLOCK 2730
49 #endif
50
51 /***********************************************************************/
52
53 static HRESULT WINAPI IAVIFile_fnQueryInterface(IAVIFile* iface,REFIID refiid,LPVOID *obj);
54 static ULONG   WINAPI IAVIFile_fnAddRef(IAVIFile* iface);
55 static ULONG   WINAPI IAVIFile_fnRelease(IAVIFile* iface);
56 static HRESULT WINAPI IAVIFile_fnInfo(IAVIFile*iface,AVIFILEINFOW*afi,LONG size);
57 static HRESULT WINAPI IAVIFile_fnGetStream(IAVIFile*iface,PAVISTREAM*avis,DWORD fccType,LONG lParam);
58 static HRESULT WINAPI IAVIFile_fnCreateStream(IAVIFile*iface,PAVISTREAM*avis,AVISTREAMINFOW*asi);
59 static HRESULT WINAPI IAVIFile_fnWriteData(IAVIFile*iface,DWORD ckid,LPVOID lpData,LONG size);
60 static HRESULT WINAPI IAVIFile_fnReadData(IAVIFile*iface,DWORD ckid,LPVOID lpData,LONG *size);
61 static HRESULT WINAPI IAVIFile_fnEndRecord(IAVIFile*iface);
62 static HRESULT WINAPI IAVIFile_fnDeleteStream(IAVIFile*iface,DWORD fccType,LONG lParam);
63
64 struct ICOM_VTABLE(IAVIFile) iavift = {
65   ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
66   IAVIFile_fnQueryInterface,
67   IAVIFile_fnAddRef,
68   IAVIFile_fnRelease,
69   IAVIFile_fnInfo,
70   IAVIFile_fnGetStream,
71   IAVIFile_fnCreateStream,
72   IAVIFile_fnWriteData,
73   IAVIFile_fnReadData,
74   IAVIFile_fnEndRecord,
75   IAVIFile_fnDeleteStream
76 };
77
78 static HRESULT WINAPI IPersistFile_fnQueryInterface(IPersistFile*iface,REFIID refiid,LPVOID*obj);
79 static ULONG   WINAPI IPersistFile_fnAddRef(IPersistFile*iface);
80 static ULONG   WINAPI IPersistFile_fnRelease(IPersistFile*iface);
81 static HRESULT WINAPI IPersistFile_fnGetClassID(IPersistFile*iface,CLSID*pClassID);
82 static HRESULT WINAPI IPersistFile_fnIsDirty(IPersistFile*iface);
83 static HRESULT WINAPI IPersistFile_fnLoad(IPersistFile*iface,LPCOLESTR pszFileName,DWORD dwMode);
84 static HRESULT WINAPI IPersistFile_fnSave(IPersistFile*iface,LPCOLESTR pszFileName,BOOL fRemember);
85 static HRESULT WINAPI IPersistFile_fnSaveCompleted(IPersistFile*iface,LPCOLESTR pszFileName);
86 static HRESULT WINAPI IPersistFile_fnGetCurFile(IPersistFile*iface,LPOLESTR*ppszFileName);
87
88 struct ICOM_VTABLE(IPersistFile) ipersistft = {
89   ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
90   IPersistFile_fnQueryInterface,
91   IPersistFile_fnAddRef,
92   IPersistFile_fnRelease,
93   IPersistFile_fnGetClassID,
94   IPersistFile_fnIsDirty,
95   IPersistFile_fnLoad,
96   IPersistFile_fnSave,
97   IPersistFile_fnSaveCompleted,
98   IPersistFile_fnGetCurFile
99 };
100
101 static HRESULT WINAPI IAVIStream_fnQueryInterface(IAVIStream*iface,REFIID refiid,LPVOID *obj);
102 static ULONG   WINAPI IAVIStream_fnAddRef(IAVIStream*iface);
103 static ULONG   WINAPI IAVIStream_fnRelease(IAVIStream* iface);
104 static HRESULT WINAPI IAVIStream_fnCreate(IAVIStream*iface,LPARAM lParam1,LPARAM lParam2);
105 static HRESULT WINAPI IAVIStream_fnInfo(IAVIStream*iface,AVISTREAMINFOW *psi,LONG size);
106 static LONG    WINAPI IAVIStream_fnFindSample(IAVIStream*iface,LONG pos,LONG flags);
107 static HRESULT WINAPI IAVIStream_fnReadFormat(IAVIStream*iface,LONG pos,LPVOID format,LONG *formatsize);
108 static HRESULT WINAPI IAVIStream_fnSetFormat(IAVIStream*iface,LONG pos,LPVOID format,LONG formatsize);
109 static HRESULT WINAPI IAVIStream_fnRead(IAVIStream*iface,LONG start,LONG samples,LPVOID buffer,LONG buffersize,LONG *bytesread,LONG *samplesread);
110 static HRESULT WINAPI IAVIStream_fnWrite(IAVIStream*iface,LONG start,LONG samples,LPVOID buffer,LONG buffersize,DWORD flags,LONG *sampwritten,LONG *byteswritten);
111 static HRESULT WINAPI IAVIStream_fnDelete(IAVIStream*iface,LONG start,LONG samples);
112 static HRESULT WINAPI IAVIStream_fnReadData(IAVIStream*iface,DWORD fcc,LPVOID lp,LONG *lpread);
113 static HRESULT WINAPI IAVIStream_fnWriteData(IAVIStream*iface,DWORD fcc,LPVOID lp,LONG size);
114 static HRESULT WINAPI IAVIStream_fnSetInfo(IAVIStream*iface,AVISTREAMINFOW*info,LONG infolen);
115
116 struct ICOM_VTABLE(IAVIStream) iavist = {
117   ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
118   IAVIStream_fnQueryInterface,
119   IAVIStream_fnAddRef,
120   IAVIStream_fnRelease,
121   IAVIStream_fnCreate,
122   IAVIStream_fnInfo,
123   IAVIStream_fnFindSample,
124   IAVIStream_fnReadFormat,
125   IAVIStream_fnSetFormat,
126   IAVIStream_fnRead,
127   IAVIStream_fnWrite,
128   IAVIStream_fnDelete,
129   IAVIStream_fnReadData,
130   IAVIStream_fnWriteData,
131   IAVIStream_fnSetInfo
132 };
133
134 typedef struct _IAVIFileImpl IAVIFileImpl;
135
136 typedef struct _IPersistFileImpl {
137   /* IUnknown stuff */
138   ICOM_VFIELD(IPersistFile);
139
140   /* IPersistFile stuff */
141   IAVIFileImpl     *paf;
142 } IPersistFileImpl;
143
144 typedef struct _IAVIStreamImpl {
145   /* IUnknown stuff */
146   ICOM_VFIELD(IAVIStream);
147   DWORD             ref;
148
149   /* IAVIStream stuff */
150   IAVIFileImpl     *paf;
151   DWORD             nStream;       /* the n-th stream in file */
152   AVISTREAMINFOW    sInfo;
153
154   LPVOID            lpFormat;
155   DWORD             cbFormat;
156
157   LPVOID            lpHandlerData;
158   DWORD             cbHandlerData;
159
160   EXTRACHUNKS       extra;
161
162   LPDWORD           lpBuffer;
163   DWORD             cbBuffer;       /* size of lpBuffer */
164   DWORD             dwCurrentFrame; /* frame/block currently in lpBuffer */
165
166   LONG              lLastFrame;    /* last correct index in idxFrames */
167   AVIINDEXENTRY    *idxFrames;
168   DWORD             nIdxFrames;     /* upper index limit of idxFrames */
169   AVIINDEXENTRY    *idxFmtChanges;
170   DWORD             nIdxFmtChanges; /* upper index limit of idxFmtChanges */
171 } IAVIStreamImpl;
172
173 struct _IAVIFileImpl {
174   /* IUnknown stuff */
175   ICOM_VFIELD(IAVIFile);
176   DWORD             ref;
177
178   /* IAVIFile stuff... */
179   IPersistFileImpl  iPersistFile;
180
181   AVIFILEINFOW      fInfo;
182   IAVIStreamImpl   *ppStreams[MAX_AVISTREAMS];
183
184   EXTRACHUNKS       fileextra;
185
186   DWORD             dwMoviChunkPos;  /* some stuff for saving ... */
187   DWORD             dwIdxChunkPos;
188   DWORD             dwNextFramePos;
189
190   AVIINDEXENTRY    *idxRecords;      /* won't be updated while loading */
191   DWORD             nIdxRecords;
192
193   /* IPersistFile stuff ... */
194   HMMIO             hmmio;
195   LPWSTR            szFileName;
196   UINT              uMode;
197   BOOL              fDirty;
198 };
199
200 /***********************************************************************/
201
202 static HRESULT AVIFILE_AddFrame(IAVIStreamImpl *This, DWORD ckid, DWORD size,
203                                 DWORD offset, DWORD flags);
204 static DWORD   AVIFILE_ComputeMoviStart(IAVIFileImpl *This);
205 static void    AVIFILE_ConstructAVIStream(IAVIFileImpl *paf, DWORD nr,
206                                           LPAVISTREAMINFOW asi);
207 static void    AVIFILE_DestructAVIStream(IAVIStreamImpl *This);
208 static HRESULT AVIFILE_LoadFile(IAVIFileImpl *This);
209 static HRESULT AVIFILE_LoadIndex(IAVIFileImpl *This, DWORD size, DWORD offset);
210 static HRESULT AVIFILE_ParseIndex(IAVIFileImpl *This, AVIINDEXENTRY *lp,
211                                   LONG count, DWORD pos, BOOL *bAbsolute);
212 static HRESULT AVIFILE_ReadBlock(IAVIStreamImpl *This, DWORD start,
213                                  LPVOID buffer, LONG size);
214 static void    AVIFILE_SamplesToBlock(IAVIStreamImpl *This, LPLONG pos,
215                                       LPLONG offset);
216 static HRESULT AVIFILE_SaveFile(IAVIFileImpl *This);
217 static HRESULT AVIFILE_SaveIndex(IAVIFileImpl *This);
218 static ULONG   AVIFILE_SearchStream(IAVIFileImpl *This, DWORD fccType,
219                                     LONG lSkip);
220 static void    AVIFILE_UpdateInfo(IAVIFileImpl *This);
221 static HRESULT AVIFILE_WriteBlock(IAVIStreamImpl *This, DWORD block,
222                                   FOURCC ckid, DWORD flags, LPVOID buffer,
223                                   LONG size);
224
225 HRESULT AVIFILE_CreateAVIFile(REFIID riid, LPVOID *ppv)
226 {
227   IAVIFileImpl *pfile;
228   HRESULT       hr;
229
230   assert(riid != NULL && ppv != NULL);
231
232   *ppv = NULL;
233
234   pfile = (IAVIFileImpl*)LocalAlloc(LPTR, sizeof(IAVIFileImpl));
235   if (pfile == NULL)
236     return AVIERR_MEMORY;
237
238   ICOM_VTBL(pfile)               = &iavift;
239   ICOM_VTBL(&pfile->iPersistFile) = &ipersistft;
240   pfile->ref = 0;
241   pfile->iPersistFile.paf = pfile;
242
243   hr = IUnknown_QueryInterface((IUnknown*)pfile, riid, ppv);
244   if (FAILED(hr))
245     LocalFree((HLOCAL)pfile);
246
247   return hr;
248 }
249
250 static HRESULT WINAPI IAVIFile_fnQueryInterface(IAVIFile *iface, REFIID refiid,
251                                                 LPVOID *obj)
252 {
253   ICOM_THIS(IAVIFileImpl,iface);
254
255   TRACE("(%p,%s,%p)\n", This, debugstr_guid(refiid), obj);
256
257   if (IsEqualGUID(&IID_IUnknown, refiid) ||
258       IsEqualGUID(&IID_IAVIFile, refiid)) {
259     *obj = iface;
260     IAVIFile_AddRef(iface);
261
262     return S_OK;
263   } else if (IsEqualGUID(&IID_IPersistFile, refiid)) {
264     *obj = &This->iPersistFile;
265     IAVIFile_AddRef(iface);
266
267     return S_OK;
268   }
269
270   return OLE_E_ENUM_NOMORE;
271 }
272
273 static ULONG WINAPI IAVIFile_fnAddRef(IAVIFile *iface)
274 {
275   ICOM_THIS(IAVIFileImpl,iface);
276
277   TRACE("(%p) -> %ld\n", iface, This->ref + 1);
278   return ++(This->ref);
279 }
280
281 static ULONG WINAPI IAVIFile_fnRelease(IAVIFile *iface)
282 {
283   ICOM_THIS(IAVIFileImpl,iface);
284   UINT i;
285
286   TRACE("(%p) -> %ld\n", iface, This->ref - 1);
287
288   if (!--(This->ref)) {
289     if (This->fDirty) {
290       /* need to write headers to file */
291       AVIFILE_SaveFile(This);
292     }
293
294     for (i = 0; i < This->fInfo.dwStreams; i++) {
295       if (This->ppStreams[i] != NULL) {
296         if (This->ppStreams[i]->ref != 0) {
297           ERR(": someone has still a reference to stream %u (%p)!\n",
298                i, This->ppStreams[i]);
299         }
300         AVIFILE_DestructAVIStream(This->ppStreams[i]);
301         LocalFree((HLOCAL)This->ppStreams[i]);
302         This->ppStreams[i] = NULL;
303       }
304     }
305
306     if (This->idxRecords != NULL) {
307       GlobalFreePtr(This->idxRecords);
308       This->idxRecords  = NULL;
309       This->nIdxRecords = 0;
310     }
311
312     if (This->fileextra.lp != NULL) {
313       GlobalFreePtr(This->fileextra.lp);
314       This->fileextra.lp = NULL;
315       This->fileextra.cb = 0;
316     }
317
318     if (This->szFileName != NULL) {
319       LocalFree((HLOCAL)This->szFileName);
320       This->szFileName = NULL;
321     }
322     if (This->hmmio != NULL) {
323       mmioClose(This->hmmio, 0);
324       This->hmmio = NULL;
325     }
326
327     LocalFree((HLOCAL)This);
328     return 0;
329   }
330   return This->ref;
331 }
332
333 static HRESULT WINAPI IAVIFile_fnInfo(IAVIFile *iface, LPAVIFILEINFOW afi,
334                                       LONG size)
335 {
336   ICOM_THIS(IAVIFileImpl,iface);
337
338   TRACE("(%p,%p,%ld)\n",iface,afi,size);
339
340   if (afi == NULL)
341     return AVIERR_BADPARAM;
342   if (size < 0)
343     return AVIERR_BADSIZE;
344
345   AVIFILE_UpdateInfo(This);
346
347   memcpy(afi, &This->fInfo, min(size, sizeof(This->fInfo)));
348
349   if (size < sizeof(This->fInfo))
350     return AVIERR_BUFFERTOOSMALL;
351   return AVIERR_OK;
352 }
353
354 static HRESULT WINAPI IAVIFile_fnGetStream(IAVIFile *iface, PAVISTREAM *avis,
355                                            DWORD fccType, LONG lParam)
356 {
357   ICOM_THIS(IAVIFileImpl,iface);
358
359   ULONG nStream;
360
361   TRACE("(%p,%p,0x%08lX,%ld)\n", iface, avis, fccType, lParam);
362
363   if (avis == NULL || lParam < 0)
364     return AVIERR_BADPARAM;
365
366   nStream = AVIFILE_SearchStream(This, fccType, lParam);
367
368   /* Does the requested stream exist? */
369   if (nStream < This->fInfo.dwStreams &&
370       This->ppStreams[nStream] != NULL) {
371     *avis = (PAVISTREAM)This->ppStreams[nStream];
372     IAVIStream_AddRef(*avis);
373
374     return AVIERR_OK;
375   }
376
377   /* Sorry, but the specified stream doesn't exist */
378   return AVIERR_NODATA;
379 }
380
381 static HRESULT WINAPI IAVIFile_fnCreateStream(IAVIFile *iface,PAVISTREAM *avis,
382                                               LPAVISTREAMINFOW asi)
383 {
384   ICOM_THIS(IAVIFileImpl,iface);
385
386   DWORD n;
387
388   TRACE("(%p,%p,%p)\n", iface, avis, asi);
389
390   /* check parameters */
391   if (avis == NULL || asi == NULL)
392     return AVIERR_BADPARAM;
393
394   *avis = NULL;
395
396   /* Does the user have write permission? */
397   if ((This->uMode & MMIO_RWMODE) == 0)
398     return AVIERR_READONLY;
399
400   /* Can we add another stream? */
401   n = This->fInfo.dwStreams;
402   if (n >= MAX_AVISTREAMS || This->dwMoviChunkPos != 0) {
403     /* already reached max nr of streams
404      * or have already written frames to disk */
405     return AVIERR_UNSUPPORTED;
406   }
407
408   /* check AVISTREAMINFO for some really needed things */
409   if (asi->fccType == 0 || asi->dwScale == 0 || asi->dwRate == 0)
410     return AVIERR_BADFORMAT;
411
412   /* now it seems to be save to add the stream */
413   assert(This->ppStreams[n] == NULL);
414   This->ppStreams[n] = (IAVIStreamImpl*)LocalAlloc(LPTR,
415                                                    sizeof(IAVIStreamImpl));
416   if (This->ppStreams[n] == NULL)
417     return AVIERR_MEMORY;
418
419   /* initialize the new allocated stream */
420   AVIFILE_ConstructAVIStream(This, n, asi);
421
422   This->fInfo.dwStreams++;
423   This->fDirty = TRUE;
424
425   /* update our AVIFILEINFO structure */
426   AVIFILE_UpdateInfo(This);
427
428   /* return it */
429   *avis = (PAVISTREAM)This->ppStreams[n];
430   IAVIStream_AddRef(*avis);
431
432   return AVIERR_OK;
433 }
434
435 static HRESULT WINAPI IAVIFile_fnWriteData(IAVIFile *iface, DWORD ckid,
436                                            LPVOID lpData, LONG size)
437 {
438   ICOM_THIS(IAVIFileImpl,iface);
439
440   TRACE("(%p,0x%08lX,%p,%ld)\n", iface, ckid, lpData, size);
441
442   /* check parameters */
443   if (lpData == NULL)
444     return AVIERR_BADPARAM;
445   if (size < 0)
446     return AVIERR_BADSIZE;
447
448   /* Do we have write permission? */
449   if ((This->uMode & MMIO_RWMODE) == 0)
450     return AVIERR_READONLY;
451
452   This->fDirty = TRUE;
453
454   return WriteExtraChunk(&This->fileextra, ckid, lpData, size);
455 }
456
457 static HRESULT WINAPI IAVIFile_fnReadData(IAVIFile *iface, DWORD ckid,
458                                           LPVOID lpData, LONG *size)
459 {
460   ICOM_THIS(IAVIFileImpl,iface);
461
462   TRACE("(%p,0x%08lX,%p,%p)\n", iface, ckid, lpData, size);
463
464   return ReadExtraChunk(&This->fileextra, ckid, lpData, size);
465 }
466
467 static HRESULT WINAPI IAVIFile_fnEndRecord(IAVIFile *iface)
468 {
469   ICOM_THIS(IAVIFileImpl,iface);
470
471   FIXME("(%p): stub\n",iface);
472
473   if ((This->uMode & MMIO_RWMODE) == 0)
474     return AVIERR_READONLY;
475
476   This->fDirty = TRUE;
477
478   /* FIXME: end record -- for interleaved files */
479
480   return E_FAIL;
481 }
482
483 static HRESULT WINAPI IAVIFile_fnDeleteStream(IAVIFile *iface, DWORD fccType,
484                                               LONG lParam)
485 {
486   ICOM_THIS(IAVIFileImpl,iface);
487
488   ULONG nStream;
489
490   TRACE("(%p,0x%08lX,%ld)\n", iface, fccType, lParam);
491
492   /* check parameter */
493   if (lParam < 0)
494     return AVIERR_BADPARAM;
495
496   /* Habe user write permissions? */
497   if ((This->uMode & MMIO_RWMODE) == 0)
498     return AVIERR_READONLY;
499
500   nStream = AVIFILE_SearchStream(This, fccType, lParam);
501
502   /* Does the requested stream exist? */
503   if (nStream < This->fInfo.dwStreams &&
504       This->ppStreams[nStream] != NULL) {
505     /* ... so delete it now */
506     LocalFree((HLOCAL)This->ppStreams[nStream]);
507
508     if (This->fInfo.dwStreams - nStream > 0)
509       memcpy(This->ppStreams + nStream, This->ppStreams + nStream + 1,
510              (This->fInfo.dwStreams - nStream) * sizeof(IAVIStreamImpl*));
511
512     This->ppStreams[This->fInfo.dwStreams] = NULL;
513     This->fInfo.dwStreams--;
514     This->fDirty = TRUE;
515
516     /* This->fInfo will be updated further when asked for */
517     return AVIERR_OK;
518   } else
519     return AVIERR_NODATA;
520 }
521
522 /***********************************************************************/
523
524 static HRESULT WINAPI IPersistFile_fnQueryInterface(IPersistFile *iface,
525                                                     REFIID refiid, LPVOID *obj)
526 {
527   ICOM_THIS(IPersistFileImpl,iface);
528
529   assert(This->paf != NULL);
530
531   return IAVIFile_QueryInterface((PAVIFILE)This->paf, refiid, obj);
532 }
533
534 static ULONG   WINAPI IPersistFile_fnAddRef(IPersistFile *iface)
535 {
536   ICOM_THIS(IPersistFileImpl,iface);
537
538   assert(This->paf != NULL);
539
540   return IAVIFile_AddRef((PAVIFILE)This->paf);
541 }
542
543 static ULONG   WINAPI IPersistFile_fnRelease(IPersistFile *iface)
544 {
545   ICOM_THIS(IPersistFileImpl,iface);
546
547   assert(This->paf != NULL);
548
549   return IAVIFile_Release((PAVIFILE)This->paf);
550 }
551
552 static HRESULT WINAPI IPersistFile_fnGetClassID(IPersistFile *iface,
553                                                 LPCLSID pClassID)
554 {
555   TRACE("(%p,%p)\n", iface, pClassID);
556
557   if (pClassID == NULL)
558     return AVIERR_BADPARAM;
559
560   memcpy(pClassID, &CLSID_AVIFile, sizeof(CLSID_AVIFile));
561
562   return AVIERR_OK;
563 }
564
565 static HRESULT WINAPI IPersistFile_fnIsDirty(IPersistFile *iface)
566 {
567   ICOM_THIS(IPersistFileImpl,iface);
568
569   TRACE("(%p)\n", iface);
570
571   assert(This->paf != NULL);
572
573   return (This->paf->fDirty ? S_OK : S_FALSE);
574 }
575
576 static HRESULT WINAPI IPersistFile_fnLoad(IPersistFile *iface,
577                                           LPCOLESTR pszFileName, DWORD dwMode)
578 {
579   ICOM_THIS(IPersistFileImpl,iface);
580
581   int len;
582
583   TRACE("(%p,%s,0x%08lX)\n", iface, debugstr_w(pszFileName), dwMode);
584
585   /* check parameter */
586   if (pszFileName == NULL)
587     return AVIERR_BADPARAM;
588
589   assert(This->paf != NULL);
590   if (This->paf->hmmio != NULL)
591     return AVIERR_ERROR; /* No reuse of this object for another file! */
592
593   /* remeber mode and name */
594   This->paf->uMode = dwMode;
595
596   len = lstrlenW(pszFileName) + 1;
597   This->paf->szFileName = (LPWSTR)LocalAlloc(LPTR, len * sizeof(WCHAR));
598   if (This->paf->szFileName == NULL)
599     return AVIERR_MEMORY;
600   lstrcpyW(This->paf->szFileName, pszFileName);
601
602   /* try to open the file */
603   This->paf->hmmio = mmioOpenW(This->paf->szFileName, NULL,
604                                MMIO_ALLOCBUF | dwMode);
605   if (This->paf->hmmio == NULL)
606     return AVIERR_FILEOPEN;
607
608   /* should we create a new file? */
609   if (dwMode & OF_CREATE) {
610     memset(& This->paf->fInfo, 0, sizeof(This->paf->fInfo));
611     This->paf->fInfo.dwFlags = AVIFILEINFO_HASINDEX | AVIFILEINFO_TRUSTCKTYPE;
612
613     return AVIERR_OK;
614   } else
615     return AVIFILE_LoadFile(This->paf);
616 }
617
618 static HRESULT WINAPI IPersistFile_fnSave(IPersistFile *iface,
619                                           LPCOLESTR pszFileName,BOOL fRemember)
620 {
621   TRACE("(%p,%s,%d)\n", iface, debugstr_w(pszFileName), fRemember);
622
623   /* We write directly to disk, so nothing to do. */
624
625   return AVIERR_OK;
626 }
627
628 static HRESULT WINAPI IPersistFile_fnSaveCompleted(IPersistFile *iface,
629                                                    LPCOLESTR pszFileName)
630 {
631   TRACE("(%p,%s)\n", iface, debugstr_w(pszFileName));
632
633   /* We write directly to disk, so nothing to do. */
634
635   return AVIERR_OK;
636 }
637
638 static HRESULT WINAPI IPersistFile_fnGetCurFile(IPersistFile *iface,
639                                                 LPOLESTR *ppszFileName)
640 {
641   ICOM_THIS(IPersistFileImpl,iface);
642
643   TRACE("(%p,%p)\n", iface, ppszFileName);
644
645   if (ppszFileName == NULL)
646     return AVIERR_BADPARAM;
647
648   *ppszFileName = NULL;
649
650   assert(This->paf != NULL);
651
652   if (This->paf->szFileName != NULL) {
653     int len = lstrlenW(This->paf->szFileName);
654
655     *ppszFileName = (LPOLESTR)GlobalAllocPtr(GHND, len * sizeof(WCHAR));
656     if (*ppszFileName == NULL)
657       return AVIERR_MEMORY;
658
659     memcpy(*ppszFileName, This->paf->szFileName, len * sizeof(WCHAR));
660   }
661
662   return AVIERR_OK;
663 }
664
665 /***********************************************************************/
666
667 static HRESULT WINAPI IAVIStream_fnQueryInterface(IAVIStream *iface,
668                                                   REFIID refiid, LPVOID *obj)
669 {
670   ICOM_THIS(IAVIStreamImpl,iface);
671
672   TRACE("(%p,%s,%p)\n", iface, debugstr_guid(refiid), obj);
673
674   if (IsEqualGUID(&IID_IUnknown, refiid) ||
675       IsEqualGUID(&IID_IAVIStream, refiid)) {
676     *obj = This;
677     IAVIStream_AddRef(iface);
678
679     return S_OK;
680   }
681   /* FIXME: IAVIStreaming interface */
682
683   return OLE_E_ENUM_NOMORE;
684 }
685
686 static ULONG WINAPI IAVIStream_fnAddRef(IAVIStream *iface)
687 {
688   ICOM_THIS(IAVIStreamImpl,iface);
689
690   TRACE("(%p) -> %ld\n", iface, This->ref + 1);
691
692   /* also add ref to parent, so that it doesn't kill us */
693   if (This->paf != NULL)
694     IAVIFile_AddRef((PAVIFILE)This->paf);
695
696   return ++(This->ref);
697 }
698
699 static ULONG WINAPI IAVIStream_fnRelease(IAVIStream* iface)
700 {
701   ICOM_THIS(IAVIStreamImpl,iface);
702
703   TRACE("(%p) -> %ld\n", iface, This->ref - 1);
704
705   /* we belong to the AVIFile, which must free us! */
706   if (This->ref == 0) {
707     ERR(": already released!\n");
708     return 0;
709   }
710
711   if (This->paf != NULL)
712     IAVIFile_Release((PAVIFILE)This->paf);
713
714   return --This->ref;
715 }
716
717 static HRESULT WINAPI IAVIStream_fnCreate(IAVIStream *iface, LPARAM lParam1,
718                                           LPARAM lParam2)
719 {
720   TRACE("(%p,0x%08lX,0x%08lX)\n", iface, lParam1, lParam2);
721
722   /* This IAVIStream interface needs an AVIFile */
723   return AVIERR_UNSUPPORTED;
724 }
725
726 static HRESULT WINAPI IAVIStream_fnInfo(IAVIStream *iface,LPAVISTREAMINFOW psi,
727                                         LONG size)
728 {
729   ICOM_THIS(IAVIStreamImpl,iface);
730
731   TRACE("(%p,%p,%ld)\n", iface, psi, size);
732
733   if (psi == NULL)
734     return AVIERR_BADPARAM;
735   if (size < 0)
736     return AVIERR_BADSIZE;
737
738   memcpy(psi, &This->sInfo, min(size, sizeof(This->sInfo)));
739
740   if (size < sizeof(This->sInfo))
741     return AVIERR_BUFFERTOOSMALL;
742   return AVIERR_OK;
743 }
744
745 static LONG WINAPI IAVIStream_fnFindSample(IAVIStream *iface, LONG pos,
746                                            LONG flags)
747 {
748   ICOM_THIS(IAVIStreamImpl,iface);
749
750   LONG offset = 0;
751
752   TRACE("(%p,%ld,0x%08lX)\n",iface,pos,flags);
753
754   if (flags & FIND_FROM_START) {
755     pos = This->sInfo.dwStart;
756     flags &= ~(FIND_FROM_START|FIND_PREV);
757     flags |= FIND_NEXT;
758   }
759
760   if (This->sInfo.dwSampleSize != 0) {
761     /* convert samples into block number with offset */
762     AVIFILE_SamplesToBlock(This, &pos, &offset);
763   }
764
765   if (flags & FIND_TYPE) {
766     if (flags & FIND_KEY) {
767       while (0 <= pos && pos <= This->lLastFrame) {
768         if (This->idxFrames[pos].dwFlags & AVIIF_KEYFRAME)
769           goto RETURN_FOUND;
770
771         if (flags & FIND_NEXT)
772           pos++;
773         else
774           pos--;
775       };
776     } else if (flags & FIND_ANY) {
777       while (0 <= pos && pos <= This->lLastFrame) {
778         if (This->idxFrames[pos].dwChunkLength > 0)
779           goto RETURN_FOUND;
780
781         if (flags & FIND_NEXT)
782           pos++;
783         else
784           pos--;
785
786       };
787     } else if ((flags & FIND_FORMAT) && This->idxFmtChanges != NULL &&
788                This->sInfo.fccType == streamtypeVIDEO) {
789       UINT n;
790
791       if (flags & FIND_NEXT) {
792         for (n = 0; n < This->sInfo.dwFormatChangeCount; n++)
793           if (This->idxFmtChanges[n].ckid >= pos)
794             goto RETURN_FOUND;
795       } else {
796         for (n = This->sInfo.dwFormatChangeCount; n >= 0; n--) {
797           if (This->idxFmtChanges[n].ckid <= pos)
798             goto RETURN_FOUND;
799         }
800
801         if (pos > This->sInfo.dwStart)
802           return 0; /* format changes always for first frame */
803       }
804     }
805
806     return -1;
807   }
808
809   if (flags & FIND_RET) {
810   RETURN_FOUND:
811     if (flags & FIND_LENGTH) {
812       /* logical size */
813       if (This->sInfo.dwSampleSize)
814         pos = This->sInfo.dwSampleSize;
815       else
816         pos = 1;
817     } else if (flags & FIND_OFFSET) {
818       /* physical position */
819       pos = This->idxFrames[pos].dwChunkOffset + offset * This->sInfo.dwSampleSize;
820     } else if (flags & FIND_SIZE) {
821       /* physical size */
822       pos = This->idxFrames[pos].dwChunkLength;
823     } else if (flags & FIND_INDEX) {
824       FIXME(": FIND_INDEX flag is not supported!");
825
826       pos = This->paf->dwIdxChunkPos;
827     } /* else logical position */
828
829     return pos;
830   }
831
832   return -1;
833 }
834
835 static HRESULT WINAPI IAVIStream_fnReadFormat(IAVIStream *iface, LONG pos,
836                                               LPVOID format, LONG *formatsize)
837 {
838   ICOM_THIS(IAVIStreamImpl,iface);
839
840   TRACE("(%p,%ld,%p,%p)\n", iface, pos, format, formatsize);
841
842   if (formatsize == NULL)
843     return AVIERR_BADPARAM;
844
845   /* only interested in needed buffersize? */
846   if (format == NULL || *formatsize <= 0) {
847     *formatsize = This->cbFormat;
848
849     return AVIERR_OK;
850   }
851
852   /* copy initial format (only as much as will fit) */
853   memcpy(format, This->lpFormat, min(*formatsize, This->cbFormat));
854   if (*formatsize < This->cbFormat) {
855     *formatsize = This->cbFormat;
856     return AVIERR_BUFFERTOOSMALL;
857   }
858
859   /* Could format change? When yes will it change? */
860   if ((This->sInfo.dwFlags & AVISTREAMINFO_FORMATCHANGES) &&
861       pos > This->sInfo.dwStart) {
862     LONG lLastFmt;
863
864     lLastFmt = IAVIStream_fnFindSample(iface, pos, FIND_FORMAT|FIND_PREV);
865     if (lLastFmt > 0) {
866       FIXME(": need to read formatchange for %ld -- unimplemented!\n",lLastFmt);
867     }
868   }
869
870   *formatsize = This->cbFormat;
871   return AVIERR_OK;
872 }
873
874 static HRESULT WINAPI IAVIStream_fnSetFormat(IAVIStream *iface, LONG pos,
875                                              LPVOID format, LONG formatsize)
876 {
877   ICOM_THIS(IAVIStreamImpl,iface);
878
879   LPBITMAPINFOHEADER lpbiNew = (LPBITMAPINFOHEADER)format;
880
881   TRACE("(%p,%ld,%p,%ld)\n", iface, pos, format, formatsize);
882
883   /* check parameters */
884   if (format == NULL || formatsize <= 0)
885     return AVIERR_BADPARAM;
886
887   /* Do we have write permission? */
888   if ((This->paf->uMode & MMIO_RWMODE) == 0)
889     return AVIERR_READONLY;
890
891   /* can only set format before frame is written! */
892   if (This->lLastFrame > pos)
893     return AVIERR_UNSUPPORTED;
894
895   /* initial format or a formatchange? */
896   if (This->lpFormat == NULL) {
897     /* initial format */
898     if (This->paf->dwMoviChunkPos != 0)
899       return AVIERR_ERROR; /* user has used API in wrong sequnece! */
900
901     This->lpFormat = GlobalAllocPtr(GMEM_MOVEABLE, formatsize);
902     if (This->lpFormat == NULL)
903       return AVIERR_MEMORY;
904     This->cbFormat = formatsize;
905
906     memcpy(This->lpFormat, format, formatsize);
907
908     /* update some infos about stream */
909     if (This->sInfo.fccType == streamtypeVIDEO) {
910       LONG lDim;
911
912       lDim = This->sInfo.rcFrame.right - This->sInfo.rcFrame.left;
913       if (lDim < lpbiNew->biWidth)
914         This->sInfo.rcFrame.right = This->sInfo.rcFrame.left + lpbiNew->biWidth;
915       lDim = This->sInfo.rcFrame.bottom - This->sInfo.rcFrame.top;
916       if (lDim < lpbiNew->biHeight)
917         This->sInfo.rcFrame.bottom = This->sInfo.rcFrame.top + lpbiNew->biHeight;
918     } else if (This->sInfo.fccType == streamtypeAUDIO)
919       This->sInfo.dwSampleSize = ((LPWAVEFORMATEX)This->lpFormat)->nBlockAlign;
920
921     return AVIERR_OK;
922   } else {
923     MMCKINFO           ck;
924     LPBITMAPINFOHEADER lpbiOld = (LPBITMAPINFOHEADER)This->lpFormat;
925     RGBQUAD           *rgbNew  = (RGBQUAD*)((LPBYTE)lpbiNew + lpbiNew->biSize);
926     AVIPALCHANGE      *lppc = NULL;
927     INT                n;
928
929     /* pherhaps formatchange, check it ... */
930     if (This->cbFormat != formatsize)
931       return AVIERR_UNSUPPORTED;
932
933     /* no formatchange, only the initial one */
934     if (memcmp(This->lpFormat, format, formatsize) == 0)
935       return AVIERR_OK;
936
937     /* check that's only the palette, which changes */
938     if (lpbiOld->biSize        != lpbiNew->biSize ||
939         lpbiOld->biWidth       != lpbiNew->biWidth ||
940         lpbiOld->biHeight      != lpbiNew->biHeight ||
941         lpbiOld->biPlanes      != lpbiNew->biPlanes ||
942         lpbiOld->biBitCount    != lpbiNew->biBitCount ||
943         lpbiOld->biCompression != lpbiNew->biCompression ||
944         lpbiOld->biClrUsed     != lpbiNew->biClrUsed)
945       return AVIERR_UNSUPPORTED;
946
947     This->sInfo.dwFlags |= AVISTREAMINFO_FORMATCHANGES;
948
949     /* simply say all colors have changed */
950     ck.ckid   = MAKEAVICKID(cktypePALchange, This->nStream);
951     ck.cksize = 2 * sizeof(WORD) + lpbiOld->biClrUsed * sizeof(PALETTEENTRY);
952     lppc = (AVIPALCHANGE*)GlobalAllocPtr(GMEM_MOVEABLE, ck.cksize);
953     if (lppc == NULL)
954       return AVIERR_MEMORY;
955
956     lppc->bFirstEntry = 0;
957     lppc->bNumEntries = (lpbiOld->biClrUsed < 256 ? lpbiOld->biClrUsed : 0);
958     lppc->wFlags      = 0;
959     for (n = 0; n < lpbiOld->biClrUsed; n++) {
960       lppc->peNew[n].peRed   = rgbNew[n].rgbRed;
961       lppc->peNew[n].peGreen = rgbNew[n].rgbGreen;
962       lppc->peNew[n].peBlue  = rgbNew[n].rgbBlue;
963       lppc->peNew[n].peFlags = 0;
964     }
965
966     if (mmioSeek(This->paf->hmmio, This->paf->dwNextFramePos, SEEK_SET) == -1)
967       return AVIERR_FILEWRITE;
968     if (mmioCreateChunk(This->paf->hmmio, &ck, 0) != S_OK)
969       return AVIERR_FILEWRITE;
970     if (mmioWrite(This->paf->hmmio, (HPSTR)lppc, ck.cksize) != ck.cksize)
971       return AVIERR_FILEWRITE;
972     if (mmioAscend(This->paf->hmmio, &ck, 0) != S_OK)
973       return AVIERR_FILEWRITE;
974     This->paf->dwNextFramePos += ck.cksize + 2 * sizeof(DWORD);
975
976     GlobalFreePtr(lppc);
977
978     return AVIFILE_AddFrame(This, cktypePALchange, n, ck.dwDataOffset, 0);
979   }
980 }
981
982 static HRESULT WINAPI IAVIStream_fnRead(IAVIStream *iface, LONG start,
983                                         LONG samples, LPVOID buffer,
984                                         LONG buffersize, LPLONG bytesread,
985                                         LPLONG samplesread)
986 {
987   ICOM_THIS(IAVIStreamImpl,iface);
988
989   DWORD    size;
990   HRESULT  hr;
991
992   TRACE("(%p,%ld,%ld,%p,%ld,%p,%p)\n", iface, start, samples, buffer,
993         buffersize, bytesread, samplesread);
994
995   /* clear return parameters if given */
996   if (bytesread != NULL)
997     *bytesread = 0;
998   if (samplesread != NULL)
999     *samplesread = 0;
1000
1001   /* check parameters */
1002   if (This->sInfo.dwStart > start)
1003     return AVIERR_NODATA; /* couldn't read before start of stream */
1004   if (This->sInfo.dwStart + This->sInfo.dwLength < start)
1005     return AVIERR_NODATA; /* start is past end of stream */
1006
1007   /* should we read as much as possible? */
1008   if (samples == -1) {
1009     /* User should know how much we have read */
1010     if (bytesread == NULL && samplesread == NULL)
1011       return AVIERR_BADPARAM;
1012
1013     if (This->sInfo.dwSampleSize != 0)
1014       samples = buffersize / This->sInfo.dwSampleSize;
1015     else
1016       samples = 1;
1017   }
1018
1019   /* limit to end of stream */
1020   if (This->sInfo.dwLength < samples)
1021     samples = This->sInfo.dwLength;
1022   if ((start - This->sInfo.dwStart) > (This->sInfo.dwLength - samples))
1023     samples = This->sInfo.dwLength - (start - This->sInfo.dwStart);
1024
1025   /* nothing to read? Then leave ... */
1026   if (samples == 0)
1027     return AVIERR_OK;
1028
1029   if (This->sInfo.dwSampleSize != 0) {
1030     /* fixed samplesize -- we can read over frame/block boundaries */
1031     LONG block  = start;
1032     LONG offset = 0;
1033
1034     /* convert start sample to block,offset pair */
1035     AVIFILE_SamplesToBlock(This, &block, &offset);
1036
1037     /* convert samples to bytes */
1038     samples *= This->sInfo.dwSampleSize;
1039
1040     while (samples > 0 && buffersize > 0) {
1041       if (block != This->dwCurrentFrame) {
1042         hr = AVIFILE_ReadBlock(This, block, NULL, 0);
1043         if (FAILED(hr))
1044           return hr;
1045       }
1046
1047       size = min((DWORD)samples, (DWORD)buffersize);
1048       size = min(size, This->cbBuffer - offset);
1049       memcpy(buffer, ((BYTE*)&This->lpBuffer[2]) + offset, size);
1050
1051       block++;
1052       offset = 0;
1053       ((BYTE*)buffer) += size;
1054       samples    -= size;
1055       buffersize -= size;
1056
1057       /* fill out return parameters if given */
1058       if (bytesread != NULL)
1059         *bytesread   += size;
1060       if (samplesread != NULL)
1061         *samplesread += size / This->sInfo.dwSampleSize;
1062     }
1063
1064     if (samples == 0)
1065       return AVIERR_OK;
1066     else
1067       return AVIERR_BUFFERTOOSMALL;
1068   } else {
1069     /* variable samplesize -- we can only read one full frame/block */
1070     if (samples > 1)
1071       samples = 1;
1072
1073     assert(start <= This->lLastFrame);
1074     size = This->idxFrames[start].dwChunkLength;
1075     if (buffer != NULL && buffersize >= size) {
1076       hr = AVIFILE_ReadBlock(This, start, buffer, size);
1077       if (FAILED(hr))
1078         return hr;
1079     } else if (buffer != NULL)
1080       return AVIERR_BUFFERTOOSMALL;
1081
1082     /* fill out return parameters if given */
1083     if (bytesread != NULL)
1084       *bytesread = size;
1085     if (samplesread != NULL)
1086       *samplesread = samples;
1087
1088     return AVIERR_OK;
1089   }
1090 }
1091
1092 static HRESULT WINAPI IAVIStream_fnWrite(IAVIStream *iface, LONG start,
1093                                          LONG samples, LPVOID buffer,
1094                                          LONG buffersize, DWORD flags,
1095                                          LPLONG sampwritten,
1096                                          LPLONG byteswritten)
1097 {
1098   ICOM_THIS(IAVIStreamImpl,iface);
1099
1100   FOURCC  ckid;
1101   HRESULT hr;
1102
1103   TRACE("(%p,%ld,%ld,%p,%ld,0x%08lX,%p,%p)\n", iface, start, samples,
1104         buffer, buffersize, flags, sampwritten, byteswritten);
1105
1106   /* clear return parameters if given */
1107   if (sampwritten != NULL)
1108     *sampwritten = 0;
1109   if (byteswritten != NULL)
1110     *byteswritten = 0;
1111
1112   /* check parameters */
1113   if (buffer == NULL && (buffersize > 0 || samples > 0))
1114     return AVIERR_BADPARAM;
1115
1116   /* Have we write permission? */
1117   if ((This->paf->uMode & MMIO_RWMODE) == 0)
1118     return AVIERR_READONLY;
1119
1120   switch (This->sInfo.fccType) {
1121   case streamtypeAUDIO:
1122     ckid = MAKEAVICKID(cktypeWAVEbytes, This->nStream);
1123     break;
1124   default:
1125     if ((flags & AVIIF_KEYFRAME) && buffersize != 0)
1126       ckid = MAKEAVICKID(cktypeDIBbits, This->nStream);
1127     else
1128       ckid = MAKEAVICKID(cktypeDIBcompressed, This->nStream);
1129     break;
1130   };
1131
1132   /* append to end of stream? */
1133   if (start == -1) {
1134     if (This->lLastFrame == -1)
1135       start = This->sInfo.dwStart;
1136     else
1137       start = This->sInfo.dwLength;
1138   } else if (This->lLastFrame == -1)
1139     This->sInfo.dwStart = start;
1140
1141   if (This->sInfo.dwSampleSize != 0) {
1142     /* fixed sample size -- audio like */
1143     if (samples * This->sInfo.dwSampleSize != buffersize)
1144       return AVIERR_BADPARAM;
1145
1146     /* Couldn't skip audio-like data -- User must supply appropriate silence */
1147     if (This->sInfo.dwLength != start)
1148       return AVIERR_UNSUPPORTED;
1149
1150     /* Convert position to frame/block */
1151     start = This->lLastFrame + 1;
1152
1153     if ((This->paf->fInfo.dwFlags & AVIFILEINFO_ISINTERLEAVED) == 0) {
1154       FIXME(": not interleaved, could collect audio data!\n");
1155     }
1156   } else {
1157     /* variable sample size -- video like */
1158     if (samples > 1)
1159       return AVIERR_UNSUPPORTED;
1160
1161     /* must we fill up with empty frames? */
1162     if (This->lLastFrame != -1) {
1163       FOURCC ckid2 = MAKEAVICKID(cktypeDIBcompressed, This->nStream);
1164
1165       while (start > This->lLastFrame + 1) {
1166         hr = AVIFILE_WriteBlock(This, This->lLastFrame + 1, ckid2, 0, NULL, 0);
1167         if (FAILED(hr))
1168           return hr;
1169       }
1170     }
1171   }
1172
1173   /* write the block now */
1174   hr = AVIFILE_WriteBlock(This, start, ckid, flags, buffer, buffersize);
1175   if (SUCCEEDED(hr)) {
1176     /* fill out return parameters if given */
1177     if (sampwritten != NULL)
1178       *sampwritten = samples;
1179     if (byteswritten != NULL)
1180       *byteswritten = buffersize;
1181   }
1182
1183   return hr;
1184 }
1185
1186 static HRESULT WINAPI IAVIStream_fnDelete(IAVIStream *iface, LONG start,
1187                                           LONG samples)
1188 {
1189   ICOM_THIS(IAVIStreamImpl,iface);
1190
1191   FIXME("(%p,%ld,%ld): stub\n", iface, start, samples);
1192
1193   /* check parameters */
1194   if (start < 0 || samples < 0)
1195     return AVIERR_BADPARAM;
1196
1197   /* Delete before start of stream? */
1198   if (start + samples < This->sInfo.dwStart)
1199     return AVIERR_OK;
1200
1201   /* Delete after end of stream? */
1202   if (start > This->sInfo.dwLength)
1203     return AVIERR_OK;
1204
1205   /* For the rest we need write permissions */
1206   if ((This->paf->uMode & MMIO_RWMODE) == 0)
1207     return AVIERR_READONLY;
1208
1209   /* 1. overwrite the data with JUNK
1210    *
1211    * if ISINTERLEAVED {
1212    *   2. concat all neighboured JUNK-blocks in this record to one
1213    *   3. if this record only contains JUNK and is at end set dwNextFramePos
1214    *      to start of this record, repeat this.
1215    * } else {
1216    *   2. concat all neighboured JUNK-blocks.
1217    *   3. if the JUNK block is at the end, then set dwNextFramePos to
1218    *      start of this block.
1219    * }
1220    */
1221
1222   return AVIERR_UNSUPPORTED;
1223 }
1224
1225 static HRESULT WINAPI IAVIStream_fnReadData(IAVIStream *iface, DWORD fcc,
1226                                             LPVOID lp, LPLONG lpread)
1227 {
1228   ICOM_THIS(IAVIStreamImpl,iface);
1229
1230   TRACE("(%p,0x%08lX,%p,%p)\n", iface, fcc, lp, lpread);
1231
1232   if (fcc == ckidSTREAMHANDLERDATA) {
1233     if (This->lpHandlerData != NULL && This->cbHandlerData > 0) {
1234       if (lp == NULL || *lpread <= 0) {
1235         *lpread = This->cbHandlerData;
1236         return AVIERR_OK;
1237       }
1238
1239       memcpy(lp, This->lpHandlerData, min(This->cbHandlerData, *lpread));
1240       if (*lpread < This->cbHandlerData)
1241         return AVIERR_BUFFERTOOSMALL;
1242       return AVIERR_OK;
1243     } else
1244       return AVIERR_NODATA;
1245   } else
1246     return ReadExtraChunk(&This->extra, fcc, lp, lpread);
1247 }
1248
1249 static HRESULT WINAPI IAVIStream_fnWriteData(IAVIStream *iface, DWORD fcc,
1250                                              LPVOID lp, LONG size)
1251 {
1252   ICOM_THIS(IAVIStreamImpl,iface);
1253
1254   TRACE("(%p,0x%08lx,%p,%ld)\n", iface, fcc, lp, size);
1255
1256   /* check parameters */
1257   if (lp == NULL)
1258     return AVIERR_BADPARAM;
1259   if (size <= 0)
1260     return AVIERR_BADSIZE;
1261
1262   /* need write permission */
1263   if ((This->paf->uMode & MMIO_RWMODE) == 0)
1264     return AVIERR_READONLY;
1265
1266   /* already written something to this file? */
1267   if (This->paf->dwMoviChunkPos != 0) {
1268     /* the data will be inserted before the 'movi' chunk, so check for
1269      * enough space */
1270     DWORD dwPos = AVIFILE_ComputeMoviStart(This->paf);
1271
1272     /* ckid,size => 2 * sizeof(DWORD) */
1273     dwPos += 2 * sizeof(DWORD) + size;
1274     if (size >= This->paf->dwMoviChunkPos)
1275       return AVIERR_UNSUPPORTED; /* not enough space left */
1276   }
1277
1278   This->paf->fDirty = TRUE;
1279
1280   if (fcc == ckidSTREAMHANDLERDATA) {
1281     if (This->lpHandlerData != NULL) {
1282       FIXME(": handler data already set -- overwirte?\n");
1283       return AVIERR_UNSUPPORTED;
1284     }
1285
1286     This->lpHandlerData = GlobalAllocPtr(GMEM_MOVEABLE, size);
1287     if (This->lpHandlerData == NULL)
1288       return AVIERR_MEMORY;
1289     This->cbHandlerData = size;
1290     memcpy(This->lpHandlerData, lp, size);
1291
1292     return AVIERR_OK;
1293   } else
1294     return WriteExtraChunk(&This->extra, fcc, lp, size);
1295 }
1296
1297 static HRESULT WINAPI IAVIStream_fnSetInfo(IAVIStream *iface,
1298                                            LPAVISTREAMINFOW info, LONG infolen)
1299 {
1300   FIXME("(%p,%p,%ld): stub\n", iface, info, infolen);
1301
1302   return E_FAIL;
1303 }
1304
1305 /***********************************************************************/
1306
1307 static HRESULT AVIFILE_AddFrame(IAVIStreamImpl *This, DWORD ckid, DWORD size, DWORD offset, DWORD flags)
1308 {
1309   /* pre-conditions */
1310   assert(This != NULL);
1311
1312   switch (TWOCCFromFOURCC(ckid)) {
1313   case cktypeDIBbits:
1314     if (This->paf->fInfo.dwFlags & AVIFILEINFO_TRUSTCKTYPE)
1315       flags |= AVIIF_KEYFRAME;
1316     break;
1317   case cktypeDIBcompressed:
1318     if (This->paf->fInfo.dwFlags & AVIFILEINFO_TRUSTCKTYPE)
1319       flags &= ~AVIIF_KEYFRAME;
1320     break;
1321   case cktypePALchange:
1322     if (This->sInfo.fccType != streamtypeVIDEO) {
1323       ERR(": found palette change in non-video stream!\n");
1324       return AVIERR_BADFORMAT;
1325     }
1326     This->sInfo.dwFlags |= AVISTREAMINFO_FORMATCHANGES;
1327     This->sInfo.dwFormatChangeCount++;
1328
1329     if (This->idxFmtChanges == NULL || This->sInfo.dwFormatChangeCount < This->nIdxFmtChanges) {
1330       UINT n = This->sInfo.dwFormatChangeCount;
1331
1332       This->nIdxFmtChanges += 16;
1333       This->idxFmtChanges = GlobalReAllocPtr(This->idxFmtChanges, This->nIdxFmtChanges * sizeof(AVIINDEXENTRY), GHND);
1334       if (This->idxFmtChanges == NULL)
1335         return AVIERR_MEMORY;
1336
1337       This->idxFmtChanges[n].ckid          = This->lLastFrame;
1338       This->idxFmtChanges[n].dwFlags       = 0;
1339       This->idxFmtChanges[n].dwChunkOffset = offset;
1340       This->idxFmtChanges[n].dwChunkLength = size;
1341
1342       return AVIERR_OK;
1343     }
1344     break;
1345   case cktypeWAVEbytes:
1346     if (This->paf->fInfo.dwFlags & AVIFILEINFO_TRUSTCKTYPE)
1347       flags |= AVIIF_KEYFRAME;
1348     break;
1349   default:
1350     WARN(": unknown TWOCC 0x%04X found\n", TWOCCFromFOURCC(ckid));
1351     break;
1352   };
1353
1354   /* first frame is alwasy a keyframe */
1355   if (This->lLastFrame == -1)
1356     flags |= AVIIF_KEYFRAME;
1357
1358   if (This->sInfo.dwSuggestedBufferSize < size)
1359     This->sInfo.dwSuggestedBufferSize = size;
1360
1361   /* get memory for index */
1362   if (This->idxFrames == NULL || This->lLastFrame + 1 >= This->nIdxFrames) {
1363     This->nIdxFrames += 512;
1364     This->idxFrames = GlobalReAllocPtr(This->idxFrames, This->nIdxFrames * sizeof(AVIINDEXENTRY), GHND);
1365     if (This->idxFrames == NULL)
1366       return AVIERR_MEMORY;
1367   }
1368
1369   This->lLastFrame++;
1370   This->idxFrames[This->lLastFrame].ckid          = ckid;
1371   This->idxFrames[This->lLastFrame].dwFlags       = flags;
1372   This->idxFrames[This->lLastFrame].dwChunkOffset = offset;
1373   This->idxFrames[This->lLastFrame].dwChunkLength = size;
1374
1375   /* update AVISTREAMINFO structure if neccessary */
1376   if (This->sInfo.dwLength < This->lLastFrame)
1377     This->sInfo.dwLength = This->lLastFrame;
1378
1379   return AVIERR_OK;
1380 }
1381
1382 static DWORD   AVIFILE_ComputeMoviStart(IAVIFileImpl *This)
1383 {
1384   DWORD dwPos;
1385   DWORD nStream;
1386
1387   /* RIFF,hdrl,movi,avih => (3 * 3 + 2) * sizeof(DWORD) = 11 * sizeof(DWORD) */
1388   dwPos = 11 * sizeof(DWORD) + sizeof(MainAVIHeader);
1389
1390   for (nStream = 0; nStream < This->fInfo.dwStreams; nStream++) {
1391     IAVIStreamImpl *pStream = This->ppStreams[nStream];
1392
1393     /* strl,strh,strf => (3 + 2 * 2) * sizeof(DWORD) = 7 * sizeof(DWORD) */
1394     dwPos += 7 * sizeof(DWORD) + sizeof(AVIStreamHeader);
1395     dwPos += ((pStream->cbFormat + 1) & ~1);
1396     if (pStream->lpHandlerData != NULL && pStream->cbHandlerData > 0)
1397       dwPos += 2 * sizeof(DWORD) + ((pStream->cbHandlerData + 1) & ~1);
1398     if (lstrlenW(pStream->sInfo.szName) > 0)
1399       dwPos += 2 * sizeof(DWORD) + ((lstrlenW(pStream->sInfo.szName) + 1) & ~1);
1400   }
1401
1402   if (This->dwMoviChunkPos == 0) {
1403     This->dwNextFramePos = dwPos;
1404
1405     /* pad to multiple of AVI_HEADERSIZE only if we are more than 8 bytes away from it */
1406     if (((dwPos + AVI_HEADERSIZE) & ~(AVI_HEADERSIZE - 1)) - dwPos > 2 * sizeof(DWORD))
1407       This->dwNextFramePos = (dwPos + AVI_HEADERSIZE) & ~(AVI_HEADERSIZE - 1);
1408
1409     This->dwMoviChunkPos = This->dwNextFramePos - sizeof(DWORD);
1410   }
1411
1412   return dwPos;
1413 }
1414
1415 static void    AVIFILE_ConstructAVIStream(IAVIFileImpl *paf, DWORD nr, LPAVISTREAMINFOW asi)
1416 {
1417   IAVIStreamImpl *pstream;
1418
1419   /* pre-conditions */
1420   assert(paf != NULL);
1421   assert(nr < MAX_AVISTREAMS);
1422   assert(paf->ppStreams[nr] != NULL);
1423
1424   pstream = paf->ppStreams[nr];
1425
1426   ICOM_VTBL(pstream)      = &iavist;
1427   pstream->ref            = 0;
1428   pstream->paf            = paf;
1429   pstream->nStream        = nr;
1430   pstream->dwCurrentFrame = -1;
1431   pstream->lLastFrame    = -1;
1432
1433   if (asi != NULL) {
1434     memcpy(&pstream->sInfo, asi, sizeof(pstream->sInfo));
1435
1436     if (asi->dwLength > 0) {
1437       /* pre-allocate mem for frame-index structure */
1438       pstream->idxFrames =
1439         (AVIINDEXENTRY*)GlobalAllocPtr(GHND, asi->dwLength * sizeof(AVIINDEXENTRY));
1440       if (pstream->idxFrames != NULL)
1441         pstream->nIdxFrames = asi->dwLength;
1442     }
1443     if (asi->dwFormatChangeCount > 0) {
1444       /* pre-allocate mem for formatchange-index structure */
1445       pstream->idxFmtChanges =
1446         (AVIINDEXENTRY*)GlobalAllocPtr(GHND, asi->dwFormatChangeCount * sizeof(AVIINDEXENTRY));
1447       if (pstream->idxFmtChanges != NULL)
1448         pstream->nIdxFmtChanges = asi->dwFormatChangeCount;
1449     }
1450
1451     /* These values will be computed */
1452     pstream->sInfo.dwLength              = 0;
1453     pstream->sInfo.dwSuggestedBufferSize = 0;
1454     pstream->sInfo.dwFormatChangeCount   = 0;
1455     pstream->sInfo.dwEditCount           = 1;
1456     if (pstream->sInfo.dwSampleSize > 0)
1457       SetRectEmpty(&pstream->sInfo.rcFrame);
1458   }
1459
1460   pstream->sInfo.dwCaps = AVIFILECAPS_CANREAD|AVIFILECAPS_CANWRITE;
1461 }
1462
1463 static void    AVIFILE_DestructAVIStream(IAVIStreamImpl *This)
1464 {
1465   /* pre-conditions */
1466   assert(This != NULL);
1467
1468   This->dwCurrentFrame = -1;
1469   This->lLastFrame    = -1;
1470   This->paf = NULL;
1471   if (This->idxFrames != NULL) {
1472     GlobalFreePtr(This->idxFrames);
1473     This->idxFrames  = NULL;
1474     This->nIdxFrames = 0;
1475   }
1476   if (This->idxFmtChanges != NULL) {
1477     GlobalFreePtr(This->idxFmtChanges);
1478     This->idxFmtChanges = NULL;
1479   }
1480   if (This->lpBuffer != NULL) {
1481     GlobalFreePtr(This->lpBuffer);
1482     This->lpBuffer = NULL;
1483     This->cbBuffer = 0;
1484   }
1485   if (This->lpHandlerData != NULL) {
1486     GlobalFreePtr(This->lpHandlerData);
1487     This->lpHandlerData = NULL;
1488     This->cbHandlerData = 0;
1489   }
1490   if (This->extra.lp != NULL) {
1491     GlobalFreePtr(This->extra.lp);
1492     This->extra.lp = NULL;
1493     This->extra.cb = 0;
1494   }
1495   if (This->lpFormat != NULL) {
1496     GlobalFreePtr(This->lpFormat);
1497     This->lpFormat = NULL;
1498     This->cbFormat = 0;
1499   }
1500 }
1501
1502 static HRESULT AVIFILE_LoadFile(IAVIFileImpl *This)
1503 {
1504   MainAVIHeader   MainAVIHdr;
1505   MMCKINFO        ckRIFF;
1506   MMCKINFO        ckLIST1;
1507   MMCKINFO        ckLIST2;
1508   MMCKINFO        ck;
1509   IAVIStreamImpl *pStream;
1510   DWORD           nStream;
1511   HRESULT         hr;
1512
1513   if (This->hmmio == NULL)
1514     return AVIERR_FILEOPEN;
1515
1516   /* initialize stream ptr's */
1517   memset(This->ppStreams, 0, sizeof(This->ppStreams));
1518
1519   /* try to get "RIFF" chunk -- must not be at beginning of file! */
1520   ckRIFF.fccType = formtypeAVI;
1521   if (mmioDescend(This->hmmio, &ckRIFF, NULL, MMIO_FINDRIFF) != S_OK) {
1522     ERR(": not an AVI!\n");
1523     return AVIERR_FILEREAD;
1524   }
1525
1526   /* get "LIST" "hdrl" */
1527   ckLIST1.fccType = listtypeAVIHEADER;
1528   hr = FindChunkAndKeepExtras(&This->fileextra, This->hmmio, &ckLIST1, &ckRIFF, MMIO_FINDLIST);
1529   if (FAILED(hr))
1530     return hr;
1531
1532   /* get "avih" chunk */
1533   ck.ckid = ckidAVIMAINHDR;
1534   hr = FindChunkAndKeepExtras(&This->fileextra, This->hmmio, &ck, &ckLIST1, MMIO_FINDCHUNK);
1535   if (FAILED(hr))
1536     return hr;
1537
1538   if (ck.cksize != sizeof(MainAVIHdr)) {
1539     ERR(": invalid size of %ld for MainAVIHeader!\n", ck.cksize);
1540     return AVIERR_BADFORMAT;
1541   }
1542   if (mmioRead(This->hmmio, (HPSTR)&MainAVIHdr, ck.cksize) != ck.cksize)
1543     return AVIERR_FILEREAD;
1544
1545   /* adjust permissions if copyrighted material in file */
1546   if (MainAVIHdr.dwFlags & AVIFILEINFO_COPYRIGHTED) {
1547     This->uMode &= ~MMIO_RWMODE;
1548     This->uMode |= MMIO_READ;
1549   }
1550
1551   /* convert MainAVIHeader into AVIFILINFOW */
1552   memset(&This->fInfo, 0, sizeof(This->fInfo));
1553   This->fInfo.dwRate                = MainAVIHdr.dwMicroSecPerFrame;
1554   This->fInfo.dwScale               = 1000000;
1555   This->fInfo.dwMaxBytesPerSec      = MainAVIHdr.dwMaxBytesPerSec;
1556   This->fInfo.dwFlags               = MainAVIHdr.dwFlags;
1557   This->fInfo.dwCaps                = AVIFILECAPS_CANREAD|AVIFILECAPS_CANWRITE;
1558   This->fInfo.dwLength              = MainAVIHdr.dwTotalFrames;
1559   This->fInfo.dwStreams             = MainAVIHdr.dwStreams;
1560   This->fInfo.dwSuggestedBufferSize = MainAVIHdr.dwSuggestedBufferSize;
1561   This->fInfo.dwWidth               = MainAVIHdr.dwWidth;
1562   This->fInfo.dwHeight              = MainAVIHdr.dwHeight;
1563   LoadStringW(AVIFILE_hModule, IDS_AVIFILETYPE, This->fInfo.szFileType,
1564               sizeof(This->fInfo.szFileType));
1565
1566   /* go back to into header list */
1567   if (mmioAscend(This->hmmio, &ck, 0) != S_OK)
1568     return AVIERR_FILEREAD;
1569
1570   /* foreach stream exists a "LIST","strl" chunk */
1571   for (nStream = 0; nStream < This->fInfo.dwStreams; nStream++) {
1572     /* get next nested chunk in this "LIST","strl" */
1573     if (mmioDescend(This->hmmio, &ckLIST2, &ckLIST1, 0) != S_OK)
1574       return AVIERR_FILEREAD;
1575
1576     /* nested chunk must be of type "LIST","strl" -- when not normally JUNK */
1577     if (ckLIST2.ckid == FOURCC_LIST &&
1578         ckLIST2.fccType == listtypeSTREAMHEADER) {
1579       pStream = This->ppStreams[nStream] =
1580         (IAVIStreamImpl*)LocalAlloc(LPTR, sizeof(IAVIStreamImpl));
1581       if (pStream == NULL)
1582         return AVIERR_MEMORY;
1583       AVIFILE_ConstructAVIStream(This, nStream, NULL);
1584
1585       ck.ckid = 0;
1586       while (mmioDescend(This->hmmio, &ck, &ckLIST2, 0) == S_OK) {
1587         switch (ck.ckid) {
1588         case ckidSTREAMHANDLERDATA:
1589           if (pStream->lpHandlerData != NULL)
1590             return AVIERR_BADFORMAT;
1591           pStream->lpHandlerData = GlobalAllocPtr(GMEM_DDESHARE|GMEM_MOVEABLE,
1592                                                   ck.cksize);
1593           if (pStream->lpHandlerData == NULL)
1594             return AVIERR_MEMORY;
1595           pStream->cbHandlerData = ck.cksize;
1596
1597           if (mmioRead(This->hmmio, (HPSTR)pStream->lpHandlerData, ck.cksize) != ck.cksize)
1598             return AVIERR_FILEREAD;
1599           break;
1600         case ckidSTREAMFORMAT:
1601           if (pStream->lpFormat != NULL)
1602             return AVIERR_BADFORMAT;
1603           pStream->lpFormat = GlobalAllocPtr(GMEM_DDESHARE|GMEM_MOVEABLE,
1604                                              ck.cksize);
1605           if (pStream->lpFormat == NULL)
1606             return AVIERR_MEMORY;
1607           pStream->cbFormat = ck.cksize;
1608
1609           if (mmioRead(This->hmmio, (HPSTR)pStream->lpFormat, ck.cksize) != ck.cksize)
1610             return AVIERR_FILEREAD;
1611
1612           if (pStream->sInfo.fccType == streamtypeVIDEO) {
1613             LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)pStream->lpFormat;
1614
1615             /* some corrections to the video format */
1616             if (lpbi->biClrUsed == 0 && lpbi->biBitCount <= 8)
1617               lpbi->biClrUsed = 1u << lpbi->biBitCount;
1618             if (lpbi->biCompression == BI_RGB && lpbi->biSizeImage == 0)
1619               lpbi->biSizeImage = DIBWIDTHBYTES(*lpbi) * lpbi->biHeight;
1620             if (lpbi->biCompression != BI_RGB && lpbi->biBitCount == 8) {
1621               if (pStream->sInfo.fccHandler == mmioFOURCC('R','L','E','0') ||
1622                   pStream->sInfo.fccHandler == mmioFOURCC('R','L','E',' '))
1623                 lpbi->biCompression = BI_RLE8;
1624             }
1625             if (lpbi->biCompression == BI_RGB &&
1626                 (pStream->sInfo.fccHandler == 0 ||
1627                  pStream->sInfo.fccHandler == mmioFOURCC('N','O','N','E')))
1628               pStream->sInfo.fccHandler = comptypeDIB;
1629
1630             /* init rcFrame if it's empty */
1631             if (IsRectEmpty(&pStream->sInfo.rcFrame))
1632               SetRect(&pStream->sInfo.rcFrame, 0, 0, lpbi->biWidth, lpbi->biHeight);
1633           }
1634           break;
1635         case ckidSTREAMHEADER:
1636           {
1637             static const WCHAR streamTypeFmt[] = {'%','4','.','4','h','s',0};
1638
1639             AVIStreamHeader streamHdr;
1640             WCHAR           szType[25];
1641             WCHAR           streamNameFmt[25];
1642             UINT            count;
1643             LONG            n = ck.cksize;
1644
1645             if (ck.cksize > sizeof(streamHdr))
1646               n = sizeof(streamHdr);
1647
1648             if (mmioRead(This->hmmio, (HPSTR)&streamHdr, n) != n)
1649               return AVIERR_FILEREAD;
1650
1651             pStream->sInfo.fccType               = streamHdr.fccType;
1652             pStream->sInfo.fccHandler            = streamHdr.fccHandler;
1653             pStream->sInfo.dwFlags               = streamHdr.dwFlags;
1654             pStream->sInfo.wPriority             = streamHdr.wPriority;
1655             pStream->sInfo.wLanguage             = streamHdr.wLanguage;
1656             pStream->sInfo.dwInitialFrames       = streamHdr.dwInitialFrames;
1657             pStream->sInfo.dwScale               = streamHdr.dwScale;
1658             pStream->sInfo.dwRate                = streamHdr.dwRate;
1659             pStream->sInfo.dwStart               = streamHdr.dwStart;
1660             pStream->sInfo.dwLength              = streamHdr.dwLength;
1661             pStream->sInfo.dwSuggestedBufferSize =
1662               streamHdr.dwSuggestedBufferSize;
1663             pStream->sInfo.dwQuality             = streamHdr.dwQuality;
1664             pStream->sInfo.dwSampleSize          = streamHdr.dwSampleSize;
1665             pStream->sInfo.rcFrame.left          = streamHdr.rcFrame.left;
1666             pStream->sInfo.rcFrame.top           = streamHdr.rcFrame.top;
1667             pStream->sInfo.rcFrame.right         = streamHdr.rcFrame.right;
1668             pStream->sInfo.rcFrame.bottom        = streamHdr.rcFrame.bottom;
1669             pStream->sInfo.dwEditCount           = 0;
1670             pStream->sInfo.dwFormatChangeCount   = 0;
1671
1672             /* generate description for stream like "filename.avi Type #n" */
1673             if (streamHdr.fccType == streamtypeVIDEO)
1674               LoadStringW(AVIFILE_hModule, IDS_VIDEO, szType, sizeof(szType));
1675             else if (streamHdr.fccType == streamtypeAUDIO)
1676               LoadStringW(AVIFILE_hModule, IDS_AUDIO, szType, sizeof(szType));
1677             else
1678               wsprintfW(szType, streamTypeFmt, (char*)&streamHdr.fccType);
1679
1680             /* get count of this streamtype up to this stream */
1681             count = 0;
1682             for (n = nStream; 0 <= n; n--) {
1683               if (This->ppStreams[n]->sInfo.fccHandler == streamHdr.fccType)
1684                 count++;
1685             }
1686
1687             memset(pStream->sInfo.szName, 0, sizeof(pStream->sInfo.szName));
1688
1689             LoadStringW(AVIFILE_hModule, IDS_AVISTREAMFORMAT, streamNameFmt, sizeof(streamNameFmt));
1690
1691             /* FIXME: avoid overflow -- better use wsnprintfW, which doesn't exists ! */
1692             wsprintfW(pStream->sInfo.szName, streamNameFmt,
1693                       AVIFILE_BasenameW(This->szFileName), szType, count);
1694           }
1695           break;
1696         case ckidSTREAMNAME:
1697           { /* streamname will be saved as ASCII string */
1698             LPSTR str = (LPSTR)LocalAlloc(LMEM_FIXED, ck.cksize);
1699             if (str == NULL)
1700               return AVIERR_MEMORY;
1701
1702             if (mmioRead(This->hmmio, (HPSTR)str, ck.cksize) != ck.cksize)
1703               return AVIERR_FILEREAD;
1704
1705             MultiByteToWideChar(CP_ACP, 0, str, -1, pStream->sInfo.szName,
1706                                 sizeof(pStream->sInfo.szName)/sizeof(pStream->sInfo.szName[0]));
1707
1708             LocalFree((HLOCAL)str);
1709           }
1710           break;
1711         case ckidAVIPADDING:
1712         case mmioFOURCC('p','a','d','d'):
1713           break;
1714         default:
1715           WARN(": found extra chunk 0x%08lX\n", ck.ckid);
1716           hr = ReadChunkIntoExtra(&pStream->extra, This->hmmio, &ck);
1717           if (FAILED(hr))
1718             return hr;
1719         };
1720
1721         if (mmioAscend(This->hmmio, &ck, 0) != S_OK)
1722           return AVIERR_FILEREAD;
1723       }
1724     } else {
1725       /* nested chunks in "LIST","hdrl" which are not of type "LIST","strl" */
1726       hr = ReadChunkIntoExtra(&This->fileextra, This->hmmio, &ckLIST2);
1727       if (FAILED(hr))
1728         return hr;
1729       if (mmioAscend(This->hmmio, &ckLIST2, 0) != S_OK)
1730         return AVIERR_FILEREAD;
1731     }
1732   }
1733
1734   /* read any extra headers in "LIST","hdrl" */
1735   FindChunkAndKeepExtras(&This->fileextra, This->hmmio, &ck, &ckLIST1, 0);
1736   if (mmioAscend(This->hmmio, &ckLIST1, 0) != S_OK)
1737     return AVIERR_FILEREAD;
1738
1739   /* search "LIST","movi" chunk in "RIFF","AVI " */
1740   ckLIST1.fccType = listtypeAVIMOVIE;
1741   hr = FindChunkAndKeepExtras(&This->fileextra, This->hmmio, &ckLIST1, &ckRIFF,
1742                               MMIO_FINDLIST);
1743   if (FAILED(hr))
1744     return hr;
1745
1746   This->dwMoviChunkPos = ckLIST1.dwDataOffset - 2 * sizeof(DWORD);
1747   This->dwIdxChunkPos  = ckLIST1.cksize + ckLIST1.dwDataOffset;
1748   if (mmioAscend(This->hmmio, &ckLIST1, 0) != S_OK)
1749     return AVIERR_FILEREAD;
1750
1751   /* try to find an index */
1752   ck.ckid = ckidAVINEWINDEX;
1753   hr = FindChunkAndKeepExtras(&This->fileextra, This->hmmio,
1754                               &ck, &ckRIFF, MMIO_FINDCHUNK);
1755   if (SUCCEEDED(hr) && ck.cksize > 0) {
1756     if (FAILED(AVIFILE_LoadIndex(This, ck.cksize, ckLIST1.dwDataOffset)))
1757       This->fInfo.dwFlags &= ~AVIFILEINFO_HASINDEX;
1758   }
1759
1760   /* when we haven't found an index or it's bad, then build one
1761    * by parsing 'movi' chunk */
1762   if ((This->fInfo.dwFlags & AVIFILEINFO_HASINDEX) == 0) {
1763     for (nStream = 0; nStream < This->fInfo.dwStreams; nStream++)
1764       This->ppStreams[nStream]->lLastFrame = -1;
1765
1766     if (mmioSeek(This->hmmio, ckLIST1.dwDataOffset + sizeof(DWORD), SEEK_SET) == -1) {
1767       ERR(": Oops, can't seek back to 'movi' chunk!\n");
1768       return AVIERR_FILEREAD;
1769     }
1770
1771     /* seek through the 'movi' list until end */
1772     while (mmioDescend(This->hmmio, &ck, &ckLIST1, 0) == S_OK) {
1773       if (ck.ckid != FOURCC_LIST) {
1774         if (mmioAscend(This->hmmio, &ck, 0) == S_OK) {
1775           nStream = StreamFromFOURCC(ck.ckid);
1776
1777           if (nStream > This->fInfo.dwStreams)
1778             return AVIERR_BADFORMAT;
1779
1780           AVIFILE_AddFrame(This->ppStreams[nStream], ck.ckid, ck.cksize,
1781                            ck.dwDataOffset - 2 * sizeof(DWORD), 0);
1782         } else {
1783           nStream = StreamFromFOURCC(ck.ckid);
1784           WARN(": file seems to be truncated!\n");
1785           if (nStream <= This->fInfo.dwStreams &&
1786               This->ppStreams[nStream]->sInfo.dwSampleSize > 0) {
1787             ck.cksize = mmioSeek(This->hmmio, 0, SEEK_END);
1788             if (ck.cksize != -1) {
1789               ck.cksize -= ck.dwDataOffset;
1790               ck.cksize &= ~(This->ppStreams[nStream]->sInfo.dwSampleSize - 1);
1791
1792               AVIFILE_AddFrame(This->ppStreams[nStream], ck.ckid, ck.cksize,
1793                                ck.dwDataOffset - 2 * sizeof(DWORD), 0);
1794             }
1795           }
1796         }
1797       }
1798     }
1799   }
1800
1801   /* find other chunks */
1802   FindChunkAndKeepExtras(&This->fileextra, This->hmmio, &ck, &ckRIFF, 0);
1803
1804   return AVIERR_OK;
1805 }
1806
1807 static HRESULT AVIFILE_LoadIndex(IAVIFileImpl *This, DWORD size, DWORD offset)
1808 {
1809   AVIINDEXENTRY *lp;
1810   DWORD          pos, n;
1811   HRESULT        hr = AVIERR_OK;
1812   BOOL           bAbsolute = TRUE;
1813
1814   lp = (AVIINDEXENTRY*)GlobalAllocPtr(GMEM_MOVEABLE,
1815                                       IDX_PER_BLOCK * sizeof(AVIINDEXENTRY));
1816   if (lp == NULL)
1817     return AVIERR_MEMORY;
1818
1819   /* adjust limits for index tables, so that inserting will be faster */
1820   for (n = 0; n < This->fInfo.dwStreams; n++) {
1821     IAVIStreamImpl *pStream = This->ppStreams[n];
1822
1823     pStream->lLastFrame = -1;
1824
1825     if (pStream->idxFrames != NULL) {
1826       GlobalFreePtr(pStream->idxFrames);
1827       pStream->idxFrames  = NULL;
1828       pStream->nIdxFrames = 0;
1829     }
1830
1831     if (pStream->sInfo.dwSampleSize != 0) {
1832       if (n > 0 && This->fInfo.dwFlags & AVIFILEINFO_ISINTERLEAVED) {
1833         pStream->nIdxFrames = This->ppStreams[0]->nIdxFrames;
1834       } else if (pStream->sInfo.dwSuggestedBufferSize) {
1835         pStream->nIdxFrames =
1836           pStream->sInfo.dwLength / pStream->sInfo.dwSuggestedBufferSize;
1837       }
1838     } else
1839       pStream->nIdxFrames = pStream->sInfo.dwLength;
1840
1841     pStream->idxFrames =
1842       (AVIINDEXENTRY*)GlobalAllocPtr(GHND, pStream->nIdxFrames * sizeof(AVIINDEXENTRY));
1843     if (pStream->idxFrames == NULL && pStream->nIdxFrames > 0) {
1844       pStream->nIdxFrames = 0;
1845       return AVIERR_MEMORY;
1846     }
1847   }
1848
1849   pos = (DWORD)-1;
1850   while (size != 0) {
1851     LONG read = min(IDX_PER_BLOCK * sizeof(AVIINDEXENTRY), size);
1852
1853     if (mmioRead(This->hmmio, (HPSTR)lp, read) != read) {
1854       hr = AVIERR_FILEREAD;
1855       break;
1856     }
1857     size -= read;
1858
1859     if (pos == (DWORD)-1)
1860       pos = offset - lp->dwChunkOffset + sizeof(DWORD);
1861
1862     AVIFILE_ParseIndex(This, lp, read / sizeof(AVIINDEXENTRY),
1863                        pos, &bAbsolute);
1864   }
1865
1866   if (lp != NULL)
1867     GlobalFreePtr(lp);
1868
1869   return hr;
1870 }
1871
1872 static HRESULT AVIFILE_ParseIndex(IAVIFileImpl *This, AVIINDEXENTRY *lp,
1873                                   LONG count, DWORD pos, BOOL *bAbsolute)
1874 {
1875   if (lp == NULL)
1876     return AVIERR_BADPARAM;
1877
1878   for (; count > 0; count--, lp++) {
1879     WORD nStream = StreamFromFOURCC(lp->ckid);
1880
1881     if (lp->ckid == listtypeAVIRECORD || nStream == 0x7F)
1882       continue; /* skip these */
1883
1884     if (nStream > This->fInfo.dwStreams)
1885       return AVIERR_BADFORMAT;
1886
1887     if (*bAbsolute == TRUE && lp->dwChunkOffset < This->dwMoviChunkPos)
1888       *bAbsolute = FALSE;
1889
1890     if (*bAbsolute)
1891       lp->dwChunkOffset += sizeof(DWORD);
1892     else
1893       lp->dwChunkOffset += pos;
1894
1895     if (FAILED(AVIFILE_AddFrame(This->ppStreams[nStream], lp->ckid, lp->dwChunkLength, lp->dwChunkOffset, lp->dwFlags)))
1896       return AVIERR_MEMORY;
1897   }
1898
1899   return AVIERR_OK;
1900 }
1901
1902 static HRESULT AVIFILE_ReadBlock(IAVIStreamImpl *This, DWORD pos,
1903                                  LPVOID buffer, LONG size)
1904 {
1905   /* pre-conditions */
1906   assert(This != NULL);
1907   assert(This->paf != NULL);
1908   assert(This->paf->hmmio != NULL);
1909   assert(This->sInfo.dwStart <= pos && pos < This->sInfo.dwLength);
1910   assert(pos <= This->lLastFrame);
1911
1912   /* should we read as much as block gives us? */
1913   if (size == 0 || size > This->idxFrames[pos].dwChunkLength)
1914     size = This->idxFrames[pos].dwChunkLength;
1915
1916   /* read into out own buffer or given one? */
1917   if (buffer == NULL) {
1918     /* we also read the chunk */
1919     size += 2 * sizeof(DWORD);
1920
1921     /* check that buffer is big enough -- don't trust dwSuggestedBufferSize */
1922     if (This->lpBuffer == NULL || size < This->cbBuffer) {
1923       This->lpBuffer =
1924         (LPDWORD)GlobalReAllocPtr(This->lpBuffer, max(size, This->sInfo.dwSuggestedBufferSize), GMEM_MOVEABLE);
1925       if (This->lpBuffer == NULL)
1926         return AVIERR_MEMORY;
1927       This->cbBuffer = max(size, This->sInfo.dwSuggestedBufferSize);
1928     }
1929
1930     /* now read the complete chunk into our buffer */
1931     if (mmioSeek(This->paf->hmmio, This->idxFrames[pos].dwChunkOffset, SEEK_SET) == -1)
1932       return AVIERR_FILEREAD;
1933     if (mmioRead(This->paf->hmmio, (HPSTR)This->lpBuffer, size) != size)
1934       return AVIERR_FILEREAD;
1935
1936     /* check if it was the correct block which we have read */
1937     if (This->lpBuffer[0] != This->idxFrames[pos].ckid ||
1938         This->lpBuffer[1] != This->idxFrames[pos].dwChunkLength) {
1939       ERR(": block %ld not found at 0x%08lX\n", pos, This->idxFrames[pos].dwChunkOffset);
1940       ERR(": Index says: '%4.4s'(0x%08lX) size 0x%08lX\n",
1941           (char*)&This->idxFrames[pos].ckid, This->idxFrames[pos].ckid,
1942           This->idxFrames[pos].dwChunkLength);
1943       ERR(": Data  says: '%4.4s'(0x%08lX) size 0x%08lX\n",
1944           (char*)&This->lpBuffer[0], This->lpBuffer[0], This->lpBuffer[1]);
1945       return AVIERR_FILEREAD;
1946     }
1947   } else {
1948     if (mmioSeek(This->paf->hmmio, This->idxFrames[pos].dwChunkOffset + 2 * sizeof(DWORD), SEEK_SET) == -1)
1949       return AVIERR_FILEREAD;
1950     if (mmioRead(This->paf->hmmio, (HPSTR)buffer, size) != size)
1951       return AVIERR_FILEREAD;
1952   }
1953
1954   return AVIERR_OK;
1955 }
1956
1957 static void    AVIFILE_SamplesToBlock(IAVIStreamImpl *This, LPLONG pos,
1958                                       LPLONG offset)
1959 {
1960   DWORD block;
1961
1962   /* pre-conditions */
1963   assert(This != NULL);
1964   assert(pos != NULL);
1965   assert(offset != NULL);
1966   assert(This->sInfo.dwSampleSize != 0);
1967   assert(*pos >= This->sInfo.dwStart);
1968
1969   /* convert start sample to start bytes */
1970   (*offset)  = (*pos) - This->sInfo.dwStart;
1971   (*offset) *= This->sInfo.dwSampleSize;
1972
1973   /* convert bytes to block number */
1974   for (block = 0; block <= This->lLastFrame; block++) {
1975     if (This->idxFrames[block].dwChunkLength <= *offset)
1976       (*offset) -= This->idxFrames[block].dwChunkLength;
1977     else
1978       break;
1979   }
1980
1981   *pos = block;
1982 }
1983
1984 static HRESULT AVIFILE_SaveFile(IAVIFileImpl *This)
1985 {
1986   MainAVIHeader   MainAVIHdr;
1987   IAVIStreamImpl* pStream;
1988   MMCKINFO        ckRIFF;
1989   MMCKINFO        ckLIST1;
1990   MMCKINFO        ckLIST2;
1991   MMCKINFO        ck;
1992   DWORD           nStream;
1993   DWORD           dwPos;
1994   HRESULT         hr;
1995
1996   /* initialize some things */
1997   if (This->dwMoviChunkPos == 0)
1998     AVIFILE_ComputeMoviStart(This);
1999
2000   AVIFILE_UpdateInfo(This);
2001
2002   assert(This->fInfo.dwScale != 0);
2003
2004   memset(&MainAVIHdr, 0, sizeof(MainAVIHdr));
2005   MainAVIHdr.dwMicroSecPerFrame    = MulDiv(This->fInfo.dwRate, 1000000,
2006                                             This->fInfo.dwScale);
2007   MainAVIHdr.dwMaxBytesPerSec      = This->fInfo.dwMaxBytesPerSec;
2008   MainAVIHdr.dwPaddingGranularity  = AVI_HEADERSIZE;
2009   MainAVIHdr.dwFlags               = This->fInfo.dwFlags;
2010   MainAVIHdr.dwTotalFrames         = This->fInfo.dwLength;
2011   MainAVIHdr.dwInitialFrames       = 0;
2012   MainAVIHdr.dwStreams             = This->fInfo.dwStreams;
2013   MainAVIHdr.dwSuggestedBufferSize = This->fInfo.dwSuggestedBufferSize;
2014   MainAVIHdr.dwWidth               = This->fInfo.dwWidth;
2015   MainAVIHdr.dwHeight              = This->fInfo.dwHeight;
2016   for (nStream = 0; nStream < MainAVIHdr.dwStreams; nStream++) {
2017     pStream = This->ppStreams[nStream];
2018
2019     if (MainAVIHdr.dwInitialFrames < pStream->sInfo.dwInitialFrames)
2020       MainAVIHdr.dwInitialFrames = pStream->sInfo.dwInitialFrames;
2021   }
2022
2023   /* now begin writing ... */
2024   mmioSeek(This->hmmio, 0, SEEK_SET);
2025
2026   /* RIFF chunk */
2027   ckRIFF.cksize  = 0;
2028   ckRIFF.fccType = formtypeAVI;
2029   if (mmioCreateChunk(This->hmmio, &ckRIFF, MMIO_CREATERIFF) != S_OK)
2030     return AVIERR_FILEWRITE;
2031
2032   /* AVI headerlist */
2033   ckLIST1.cksize  = 0;
2034   ckLIST1.fccType = listtypeAVIHEADER;
2035   if (mmioCreateChunk(This->hmmio, &ckLIST1, MMIO_CREATELIST) != S_OK)
2036     return AVIERR_FILEWRITE;
2037
2038   /* MainAVIHeader */
2039   ck.ckid    = ckidAVIMAINHDR;
2040   ck.cksize  = sizeof(MainAVIHdr);
2041   ck.fccType = 0;
2042   if (mmioCreateChunk(This->hmmio, &ck, 0) != S_OK)
2043     return AVIERR_FILEWRITE;
2044   if (mmioWrite(This->hmmio, (HPSTR)&MainAVIHdr, ck.cksize) != ck.cksize)
2045     return AVIERR_FILEWRITE;
2046   if (mmioAscend(This->hmmio, &ck, 0) != S_OK)
2047     return AVIERR_FILEWRITE;
2048
2049   /* write the headers of each stream into a seperate streamheader list */
2050   for (nStream = 0; nStream < This->fInfo.dwStreams; nStream++) {
2051     AVIStreamHeader strHdr;
2052
2053     pStream = This->ppStreams[nStream];
2054
2055     /* begin the new streamheader list */
2056     ckLIST2.cksize  = 0;
2057     ckLIST2.fccType = listtypeSTREAMHEADER;
2058     if (mmioCreateChunk(This->hmmio, &ckLIST2, MMIO_CREATELIST) != S_OK)
2059       return AVIERR_FILEWRITE;
2060
2061     /* create an AVIStreamHeader from the AVSTREAMINFO */
2062     strHdr.fccType               = pStream->sInfo.fccType;
2063     strHdr.fccHandler            = pStream->sInfo.fccHandler;
2064     strHdr.dwFlags               = pStream->sInfo.dwFlags;
2065     strHdr.wPriority             = pStream->sInfo.wPriority;
2066     strHdr.wLanguage             = pStream->sInfo.wLanguage;
2067     strHdr.dwInitialFrames       = pStream->sInfo.dwInitialFrames;
2068     strHdr.dwScale               = pStream->sInfo.dwScale;
2069     strHdr.dwRate                = pStream->sInfo.dwRate;
2070     strHdr.dwStart               = pStream->sInfo.dwStart;
2071     strHdr.dwLength              = pStream->sInfo.dwLength;
2072     strHdr.dwSuggestedBufferSize = pStream->sInfo.dwSuggestedBufferSize;
2073     strHdr.dwQuality             = pStream->sInfo.dwQuality;
2074     strHdr.dwSampleSize          = pStream->sInfo.dwSampleSize;
2075     strHdr.rcFrame.left          = pStream->sInfo.rcFrame.left;
2076     strHdr.rcFrame.top           = pStream->sInfo.rcFrame.top;
2077     strHdr.rcFrame.right         = pStream->sInfo.rcFrame.right;
2078     strHdr.rcFrame.bottom        = pStream->sInfo.rcFrame.bottom;
2079
2080     /* now write the AVIStreamHeader */
2081     ck.ckid   = ckidSTREAMHEADER;
2082     ck.cksize = sizeof(strHdr);
2083     if (mmioCreateChunk(This->hmmio, &ck, 0) != S_OK)
2084       return AVIERR_FILEWRITE;
2085     if (mmioWrite(This->hmmio, (HPSTR)&strHdr, ck.cksize) != ck.cksize)
2086       return AVIERR_FILEWRITE;
2087     if (mmioAscend(This->hmmio, &ck, 0) != S_OK)
2088       return AVIERR_FILEWRITE;
2089
2090     /* ... the hopefull ever present streamformat ... */
2091     ck.ckid   = ckidSTREAMFORMAT;
2092     ck.cksize = pStream->cbFormat;
2093     if (mmioCreateChunk(This->hmmio, &ck, 0) != S_OK)
2094       return AVIERR_FILEWRITE;
2095     if (pStream->lpFormat != NULL && ck.cksize > 0) {
2096       if (mmioWrite(This->hmmio, (HPSTR)pStream->lpFormat, ck.cksize) != ck.cksize)
2097         return AVIERR_FILEWRITE;
2098     }
2099     if (mmioAscend(This->hmmio, &ck, 0) != S_OK)
2100       return AVIERR_FILEWRITE;
2101
2102     /* ... some optional existing handler data ... */
2103     if (pStream->lpHandlerData != NULL && pStream->cbHandlerData > 0) {
2104       ck.ckid   = ckidSTREAMHANDLERDATA;
2105       ck.cksize = pStream->cbHandlerData;
2106       if (mmioCreateChunk(This->hmmio, &ck, 0) != S_OK)
2107         return AVIERR_FILEWRITE;
2108       if (mmioWrite(This->hmmio, (HPSTR)pStream->lpHandlerData, ck.cksize) != ck.cksize)
2109         return AVIERR_FILEWRITE;
2110       if (mmioAscend(This->hmmio, &ck, 0) != S_OK)
2111         return AVIERR_FILEWRITE;
2112     }
2113
2114     /* ... some optional additional extra chunk for this stream ... */
2115     if (pStream->extra.lp != NULL && pStream->extra.cb > 0) {
2116       /* the chunk header(s) are already in the strucuture */
2117       if (mmioWrite(This->hmmio, (HPSTR)pStream->extra.lp, pStream->extra.cb) != pStream->extra.cb)
2118         return AVIERR_FILEWRITE;
2119     }
2120
2121     /* ... an optional name for this stream ... */
2122     if (lstrlenW(pStream->sInfo.szName) > 0) {
2123       LPSTR str;
2124
2125       ck.ckid   = ckidSTREAMNAME;
2126       ck.cksize = lstrlenW(pStream->sInfo.szName) + 1;
2127       if (ck.cksize & 1) /* align */
2128         ck.cksize++;
2129       if (mmioCreateChunk(This->hmmio, &ck, 0) != S_OK)
2130         return AVIERR_FILEWRITE;
2131
2132       /* the streamname must be saved in ASCII not Unicode */
2133       str = (LPSTR)LocalAlloc(LPTR, ck.cksize);
2134       if (str == NULL)
2135         return AVIERR_MEMORY;
2136       WideCharToMultiByte(CP_ACP, 0, pStream->sInfo.szName, -1, str,
2137                           ck.cksize, NULL, NULL);
2138
2139       if (mmioWrite(This->hmmio, (HPSTR)str, ck.cksize) != ck.cksize) {
2140         LocalFree((HLOCAL)str); 
2141         return AVIERR_FILEWRITE;
2142       }
2143
2144       LocalFree((HLOCAL)str);
2145       if (mmioAscend(This->hmmio, &ck, 0) != S_OK)
2146         return AVIERR_FILEWRITE;
2147     }
2148
2149     /* close streamheader list for this stream */
2150     if (mmioAscend(This->hmmio, &ckLIST2, 0) != S_OK)
2151       return AVIERR_FILEWRITE;
2152   } /* for (0 <= nStream < MainAVIHdr.dwStreams) */
2153
2154   /* close the aviheader list */
2155   if (mmioAscend(This->hmmio, &ckLIST1, 0) != S_OK)
2156     return AVIERR_FILEWRITE;
2157
2158   /* check for padding to pre-guessed 'movi'-chunk position */
2159   dwPos = ckLIST1.dwDataOffset + ckLIST1.cksize;
2160   if (This->dwMoviChunkPos - 2 * sizeof(DWORD) > dwPos) {
2161     ck.ckid   = ckidAVIPADDING;
2162     ck.cksize = This->dwMoviChunkPos - dwPos - 4 * sizeof(DWORD);
2163     assert((LONG)ck.cksize >= 0);
2164
2165     if (mmioCreateChunk(This->hmmio, &ck, 0) != S_OK)
2166       return AVIERR_FILEWRITE;
2167     if (mmioSeek(This->hmmio, ck.cksize, SEEK_CUR) == -1)
2168       return AVIERR_FILEWRITE;
2169     if (mmioAscend(This->hmmio, &ck, 0) != S_OK)
2170       return AVIERR_FILEWRITE;
2171   }
2172
2173   /* now write the 'movi' chunk */
2174   mmioSeek(This->hmmio, This->dwMoviChunkPos - 2 * sizeof(DWORD), SEEK_SET);
2175   ckLIST1.cksize  = 0;
2176   ckLIST1.fccType = listtypeAVIMOVIE;
2177   if (mmioCreateChunk(This->hmmio, &ckLIST1, MMIO_CREATELIST) != S_OK)
2178     return AVIERR_FILEWRITE;
2179   if (mmioSeek(This->hmmio, This->dwNextFramePos, SEEK_SET) == -1)
2180     return AVIERR_FILEWRITE;
2181   if (mmioAscend(This->hmmio, &ckLIST1, 0) != S_OK)
2182     return AVIERR_FILEWRITE;
2183
2184   /* write 'idx1' chunk */
2185   hr = AVIFILE_SaveIndex(This);
2186   if (FAILED(hr))
2187     return hr;
2188
2189   /* write optional extra file chunks */
2190   if (This->fileextra.lp != NULL && This->fileextra.cb > 0) {
2191     /* as for the streams, are the chunk header(s) in the structure */
2192     if (mmioWrite(This->hmmio, (HPSTR)This->fileextra.lp, This->fileextra.cb) != This->fileextra.cb)
2193       return AVIERR_FILEWRITE;
2194   }
2195
2196   /* close RIFF chunk */
2197   if (mmioAscend(This->hmmio, &ckRIFF, 0) != S_OK)
2198     return AVIERR_FILEWRITE;
2199
2200   /* add some JUNK at end for bad parsers */
2201   memset(&ckRIFF, 0, sizeof(ckRIFF));
2202   mmioWrite(This->hmmio, (HPSTR)&ckRIFF, sizeof(ckRIFF));
2203   mmioFlush(This->hmmio, 0);
2204
2205   return AVIERR_OK;
2206 }
2207
2208 static HRESULT AVIFILE_SaveIndex(IAVIFileImpl *This)
2209 {
2210   IAVIStreamImpl *pStream;
2211   AVIINDEXENTRY   idx;
2212   MMCKINFO        ck;
2213   DWORD           nStream;
2214   LONG            n;
2215
2216   ck.ckid   = ckidAVINEWINDEX;
2217   ck.cksize = 0;
2218   if (mmioCreateChunk(This->hmmio, &ck, 0) != S_OK)
2219     return AVIERR_FILEWRITE;
2220
2221   if (This->fInfo.dwFlags & AVIFILEINFO_ISINTERLEAVED) {
2222     /* is interleaved -- write block of coresponding frames */
2223     LONG lInitialFrames = 0;
2224     LONG stepsize;
2225     LONG i;
2226
2227     if (This->ppStreams[0]->sInfo.dwSampleSize == 0)
2228       stepsize = 1;
2229     else
2230       stepsize = AVIStreamTimeToSample((PAVISTREAM)This->ppStreams[0], 1000000);
2231
2232     for (nStream = 0; nStream < This->fInfo.dwStreams; nStream++) {
2233       if (lInitialFrames < This->ppStreams[nStream]->sInfo.dwInitialFrames)
2234         lInitialFrames = This->ppStreams[nStream]->sInfo.dwInitialFrames;
2235     }
2236
2237     for (i = -lInitialFrames; i < (LONG)This->fInfo.dwLength - lInitialFrames;
2238          i += stepsize) {
2239       DWORD nFrame = lInitialFrames + i;
2240
2241       assert(nFrame < This->nIdxRecords);
2242
2243       idx.ckid          = listtypeAVIRECORD;
2244       idx.dwFlags       = AVIIF_LIST;
2245       idx.dwChunkLength = This->idxRecords[nFrame].dwChunkLength;
2246       idx.dwChunkOffset = This->idxRecords[nFrame].dwChunkOffset
2247         - This->dwMoviChunkPos;
2248       if (mmioWrite(This->hmmio, (HPSTR)&idx, sizeof(idx)) != sizeof(idx))
2249         return AVIERR_FILEWRITE;
2250
2251       for (nStream = 0; nStream < This->fInfo.dwStreams; n++) {
2252         pStream = This->ppStreams[nStream];
2253
2254         /* heave we reached start of this stream? */
2255         if (-(LONG)pStream->sInfo.dwInitialFrames > i)
2256           continue;
2257
2258         if (pStream->sInfo.dwInitialFrames < lInitialFrames)
2259           nFrame -= (lInitialFrames - pStream->sInfo.dwInitialFrames);
2260
2261         /* reached end of this stream? */
2262         if (pStream->lLastFrame <= nFrame)
2263           continue;
2264
2265         if ((pStream->sInfo.dwFlags & AVISTREAMINFO_FORMATCHANGES) &&
2266             pStream->sInfo.dwFormatChangeCount != 0 &&
2267             pStream->idxFmtChanges != NULL) {
2268           DWORD pos;
2269
2270           for (pos = 0; pos < pStream->sInfo.dwFormatChangeCount; pos++) {
2271             if (pStream->idxFmtChanges[pos].ckid == nFrame) {
2272               idx.dwFlags = AVIIF_NOTIME;
2273               idx.ckid    = MAKEAVICKID(cktypePALchange, pStream->nStream);
2274               idx.dwChunkLength = pStream->idxFmtChanges[pos].dwChunkLength;
2275               idx.dwChunkOffset = pStream->idxFmtChanges[pos].dwChunkOffset
2276                 - This->dwMoviChunkPos;
2277
2278               if (mmioWrite(This->hmmio, (HPSTR)&idx, sizeof(idx)) != sizeof(idx))
2279                 return AVIERR_FILEWRITE;
2280               break;
2281             }
2282           }
2283         } /* if have formatchanges */
2284
2285         idx.ckid          = pStream->idxFrames[nFrame].ckid;
2286         idx.dwFlags       = pStream->idxFrames[nFrame].dwFlags;
2287         idx.dwChunkLength = pStream->idxFrames[nFrame].dwChunkLength;
2288         idx.dwChunkOffset = pStream->idxFrames[nFrame].dwChunkOffset
2289           - This->dwMoviChunkPos;
2290         if (mmioWrite(This->hmmio, (HPSTR)&idx, sizeof(idx)) != sizeof(idx))
2291           return AVIERR_FILEWRITE;
2292       }
2293     }
2294   } else {
2295     /* not interleaved -- write index for each stream at once */
2296     for (nStream = 0; nStream < This->fInfo.dwStreams; nStream++) {
2297       pStream = This->ppStreams[nStream];
2298
2299       if (pStream->lLastFrame == -1)
2300         pStream->lLastFrame = 0;
2301
2302       for (n = 0; n < pStream->lLastFrame; n++) {
2303         if ((pStream->sInfo.dwFlags & AVISTREAMINFO_FORMATCHANGES) &&
2304             (pStream->sInfo.dwFormatChangeCount != 0)) {
2305           DWORD pos;
2306
2307           for (pos = 0; pos < pStream->sInfo.dwFormatChangeCount; pos++) {
2308             if (pStream->idxFmtChanges[pos].ckid == n) {
2309               idx.dwFlags = AVIIF_NOTIME;
2310               idx.ckid    = MAKEAVICKID(cktypePALchange, pStream->nStream);
2311               idx.dwChunkLength = pStream->idxFmtChanges[pos].dwChunkLength;
2312               idx.dwChunkOffset =
2313                 pStream->idxFmtChanges[pos].dwChunkOffset - This->dwMoviChunkPos;
2314               if (mmioWrite(This->hmmio, (HPSTR)&idx, sizeof(idx)) != sizeof(idx))
2315                 return AVIERR_FILEWRITE;
2316               break;
2317             }
2318           }
2319         } /* if have formatchanges */
2320
2321         idx.ckid          = pStream->idxFrames[n].ckid;
2322         idx.dwFlags       = pStream->idxFrames[n].dwFlags;
2323         idx.dwChunkLength = pStream->idxFrames[n].dwChunkLength;
2324         idx.dwChunkOffset = pStream->idxFrames[n].dwChunkOffset
2325           - This->dwMoviChunkPos;
2326
2327         if (mmioWrite(This->hmmio, (HPSTR)&idx, sizeof(idx)) != sizeof(idx))
2328           return AVIERR_FILEWRITE;
2329       }
2330     }
2331   } /* if not interleaved */
2332
2333   if (mmioAscend(This->hmmio, &ck, 0) != S_OK)
2334     return AVIERR_FILEWRITE;
2335
2336   return AVIERR_OK;
2337 }
2338
2339 static ULONG  AVIFILE_SearchStream(IAVIFileImpl *This, DWORD fcc, LONG lSkip)
2340 {
2341   UINT i;
2342   UINT nStream;
2343
2344   /* pre-condition */
2345   assert(lSkip >= 0);
2346
2347   if (fcc != 0) {
2348     /* search the number of the specified stream */
2349     nStream = (ULONG)-1;
2350     for (i = 0; i < This->fInfo.dwStreams; i++) {
2351       assert(This->ppStreams[i] != NULL);
2352
2353       if (This->ppStreams[i]->sInfo.fccType == fcc) {
2354         if (lSkip == 0) {
2355           nStream = i;
2356           break;
2357         } else
2358           lSkip--;
2359       }
2360     }
2361   } else
2362     nStream = lSkip;
2363
2364   return nStream;
2365 }
2366
2367 static void    AVIFILE_UpdateInfo(IAVIFileImpl *This)
2368 {
2369   UINT i;
2370
2371   /* pre-conditions */
2372   assert(This != NULL);
2373
2374   This->fInfo.dwMaxBytesPerSec      = 0;
2375   This->fInfo.dwCaps                = AVIFILECAPS_CANREAD|AVIFILECAPS_CANWRITE;
2376   This->fInfo.dwSuggestedBufferSize = 0;
2377   This->fInfo.dwWidth               = 0;
2378   This->fInfo.dwHeight              = 0;
2379   This->fInfo.dwScale               = 0;
2380   This->fInfo.dwRate                = 0;
2381   This->fInfo.dwLength              = 0;
2382
2383   for (i = 0; i < This->fInfo.dwStreams; i++) {
2384     AVISTREAMINFOW *psi;
2385     DWORD           n;
2386
2387     /* pre-conditions */
2388     assert(This->ppStreams[i] != NULL);
2389
2390     psi = &This->ppStreams[i]->sInfo;
2391     assert(psi->dwScale != 0);
2392     assert(psi->dwRate != 0);
2393
2394     if (i == 0) {
2395       /* use first stream timings as base */
2396       This->fInfo.dwScale  = psi->dwScale;
2397       This->fInfo.dwRate   = psi->dwRate;
2398       This->fInfo.dwLength = psi->dwLength;
2399     } else {
2400       n = AVIStreamSampleToSample((PAVISTREAM)This->ppStreams[0],
2401                                   (PAVISTREAM)This->ppStreams[i],psi->dwLength);
2402       if (This->fInfo.dwLength < n)
2403         This->fInfo.dwLength = n;
2404     }
2405
2406     if (This->fInfo.dwSuggestedBufferSize < psi->dwSuggestedBufferSize)
2407       This->fInfo.dwSuggestedBufferSize = psi->dwSuggestedBufferSize;
2408
2409     if (psi->dwSampleSize != 0) {
2410       /* fixed sample size -- exact computation */
2411       This->fInfo.dwMaxBytesPerSec += MulDiv(psi->dwSampleSize, psi->dwRate,
2412                                              psi->dwScale);
2413     } else {
2414       /* variable sample size -- only upper limit */
2415       This->fInfo.dwMaxBytesPerSec += MulDiv(psi->dwSuggestedBufferSize,
2416                                              psi->dwRate, psi->dwScale);
2417
2418       /* update dimensions */
2419       n = psi->rcFrame.right - psi->rcFrame.left;
2420       if (This->fInfo.dwWidth < n)
2421         This->fInfo.dwWidth = n;
2422       n = psi->rcFrame.bottom - psi->rcFrame.top;
2423       if (This->fInfo.dwHeight < n)
2424         This->fInfo.dwHeight = n;
2425     }
2426   }
2427 }
2428
2429 static HRESULT AVIFILE_WriteBlock(IAVIStreamImpl *This, DWORD block,
2430                                   FOURCC ckid, DWORD flags, LPVOID buffer,
2431                                   LONG size)
2432 {
2433   MMCKINFO ck;
2434
2435   ck.ckid    = ckid;
2436   ck.cksize  = size;
2437   ck.fccType = 0;
2438
2439   /* if no frame/block is already written, we must compute start of movi chunk */
2440   if (This->paf->dwMoviChunkPos == 0)
2441     AVIFILE_ComputeMoviStart(This->paf);
2442
2443   if (mmioSeek(This->paf->hmmio, This->paf->dwNextFramePos, SEEK_SET) == -1)
2444     return AVIERR_FILEWRITE;
2445
2446   if (mmioCreateChunk(This->paf->hmmio, &ck, 0) != S_OK)
2447     return AVIERR_FILEWRITE;
2448   if (buffer != NULL && size > 0) {
2449     if (mmioWrite(This->paf->hmmio, (HPSTR)buffer, size) != size)
2450       return AVIERR_FILEWRITE;
2451   }
2452   if (mmioAscend(This->paf->hmmio, &ck, 0) != S_OK)
2453     return AVIERR_FILEWRITE;
2454
2455   This->paf->fDirty         = TRUE;
2456   This->paf->dwNextFramePos = mmioSeek(This->paf->hmmio, 0, SEEK_CUR);
2457
2458   return AVIFILE_AddFrame(This, ckid, size, ck.dwDataOffset, flags);
2459 }