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