/*
 * msvideo 16-bit functions
 *
 * Copyright 1998 Marcus Meissner
 * Copyright 2000 Bradley Baetz
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>
#include <stdio.h>
#include <string.h>

#include "windef.h"
#include "winbase.h"
#include "winver.h"
#include "winnls.h"
#include "winreg.h"
#include "winuser.h"
#include "wine/winbase16.h"
#include "wownt32.h"
#include "vfw16.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(msvideo);

/* Drivers32 settings */
#define HKLM_DRIVERS32 "Software\\Microsoft\\Windows NT\\CurrentVersion\\Drivers32"

/* handle16 --> handle conversions */
#define HDRAWDIB_32(h16)	((HDRAWDIB)(ULONG_PTR)(h16))
#define HIC_32(h16)		((HIC)(ULONG_PTR)(h16))

/* handle --> handle16 conversions */
#define HDRVR_16(h32)		(LOWORD(h32))
#define HDRAWDIB_16(h32)	(LOWORD(h32))
#define HIC_16(h32)		(LOWORD(h32))

/***********************************************************************
 *		DrawDibOpen		[MSVIDEO.102]
 */
HDRAWDIB16 VFWAPI DrawDibOpen16(void)
{
    return HDRAWDIB_16(DrawDibOpen());
}

/***********************************************************************
 *		DrawDibClose		[MSVIDEO.103]
 */
BOOL16 VFWAPI DrawDibClose16(HDRAWDIB16 hdd)
{
    return DrawDibClose(HDRAWDIB_32(hdd));
}

/************************************************************************
 *		DrawDibBegin		[MSVIDEO.104]
 */
BOOL16 VFWAPI DrawDibBegin16(HDRAWDIB16 hdd, HDC16 hdc, INT16 dxDst,
			     INT16 dyDst, LPBITMAPINFOHEADER lpbi, INT16 dxSrc,
			     INT16 dySrc, UINT16 wFlags)
{
    return DrawDibBegin(HDRAWDIB_32(hdd), HDC_32(hdc), dxDst, dyDst, lpbi,
			dxSrc, dySrc, wFlags);
}

/***********************************************************************
 *		DrawDibEnd		[MSVIDEO.105]
 */
BOOL16 VFWAPI DrawDibEnd16(HDRAWDIB16 hdd)
{
    return DrawDibEnd(HDRAWDIB_32(hdd));
}

/**********************************************************************
 *		DrawDibDraw		[MSVIDEO.106]
 */
BOOL16 VFWAPI DrawDibDraw16(HDRAWDIB16 hdd, HDC16 hdc, INT16 xDst, INT16 yDst,
			    INT16 dxDst, INT16 dyDst, LPBITMAPINFOHEADER lpbi,
			    LPVOID lpBits, INT16 xSrc, INT16 ySrc, INT16 dxSrc,
			    INT16 dySrc, UINT16 wFlags)
{
    return DrawDibDraw(HDRAWDIB_32(hdd), HDC_32(hdc), xDst, yDst, dxDst,
		       dyDst, lpbi, lpBits, xSrc, ySrc, dxSrc, dySrc, wFlags);
}

/***********************************************************************
 *              DrawDibGetPalette       [MSVIDEO.108]
 */
HPALETTE16 VFWAPI DrawDibGetPalette16(HDRAWDIB16 hdd)
{
    return HPALETTE_16(DrawDibGetPalette(HDRAWDIB_32(hdd)));
}

/***********************************************************************
 *              DrawDibSetPalette       [MSVIDEO.110]
 */
BOOL16 VFWAPI DrawDibSetPalette16(HDRAWDIB16 hdd, HPALETTE16 hpal)
{
    return DrawDibSetPalette(HDRAWDIB_32(hdd), HPALETTE_32(hpal));
}

/***********************************************************************
 *              DrawDibRealize          [MSVIDEO.112]
 */
UINT16 VFWAPI DrawDibRealize16(HDRAWDIB16 hdd, HDC16 hdc,
			       BOOL16 fBackground)
{
    return (UINT16)DrawDibRealize(HDRAWDIB_32(hdd), HDC_32(hdc), fBackground);
}

/*************************************************************************
 *      DrawDibProfileDisplay     [MSVIDEO.114]
 */
BOOL16 VFWAPI DrawDibProfileDisplay16(LPBITMAPINFOHEADER lpbi)
{
    TRACE("(%p)\n", lpbi);
    return DrawDibProfileDisplay(lpbi);
}

/*************************************************************************
 *		DrawDibStart		[MSVIDEO.118]
 */
BOOL16 VFWAPI DrawDibStart16(HDRAWDIB16 hdd, DWORD rate)
{
    return DrawDibStart(HDRAWDIB_32(hdd), rate);
}

/*************************************************************************
 *		DrawDibStop		[MSVIDEO.119]
 */
BOOL16 VFWAPI DrawDibStop16(HDRAWDIB16 hdd)
{
    return DrawDibStop(HDRAWDIB_32(hdd));
}

/***********************************************************************
 *		ICOpen				[MSVIDEO.203]
 */
HIC16 VFWAPI ICOpen16(DWORD fccType, DWORD fccHandler, UINT16 wMode)
{
    return HIC_16(ICOpen(fccType, fccHandler, wMode));
}

/***********************************************************************
 *		_ICMessage			[MSVIDEO.207]
 */
LRESULT VFWAPIV ICMessage16( HIC16 hic, UINT16 msg, UINT16 cb, VA_LIST16 valist )
{
    LPWORD lpData;
    SEGPTR segData;
    LRESULT ret;
    UINT16 i;

    lpData = HeapAlloc(GetProcessHeap(), 0, cb);

    TRACE("0x%08x, %u, %u, ...)\n", (DWORD) hic, msg, cb);

    for (i = 0; i < cb / sizeof(WORD); i++)
    {
	lpData[i] = VA_ARG16(valist, WORD);
    }

    segData = MapLS(lpData);
    ret = ICSendMessage16(hic, msg, segData, (DWORD) cb);
    UnMapLS(segData);
    HeapFree(GetProcessHeap(), 0, lpData);
    return ret;
}

/***********************************************************************
 *		ICGetInfo			[MSVIDEO.212]
 */
LRESULT VFWAPI ICGetInfo16(HIC16 hic, ICINFO16 * picinfo, DWORD cb)
{
    LRESULT ret;

    TRACE("(0x%08x,%p,%d)\n", (DWORD) hic, picinfo, cb);
    ret = ICSendMessage16(hic, ICM_GETINFO, (DWORD) picinfo, cb);
    TRACE("	-> 0x%08lx\n", ret);
    return ret;
}

/***********************************************************************
 *		ICLocate			[MSVIDEO.213]
 */
HIC16 VFWAPI ICLocate16(DWORD fccType, DWORD fccHandler,
			LPBITMAPINFOHEADER lpbiIn, LPBITMAPINFOHEADER lpbiOut,
			WORD wFlags)
{
    return HIC_16(ICLocate(fccType, fccHandler, lpbiIn, lpbiOut, wFlags));
}

/***********************************************************************
 *		_ICCompress			[MSVIDEO.224]
 */
DWORD VFWAPIV ICCompress16(HIC16 hic, DWORD dwFlags,
			   LPBITMAPINFOHEADER lpbiOutput, LPVOID lpData,
			   LPBITMAPINFOHEADER lpbiInput, LPVOID lpBits,
			   LPDWORD lpckid, LPDWORD lpdwFlags,
			   LONG lFrameNum, DWORD dwFrameSize,
			   DWORD dwQuality, LPBITMAPINFOHEADER lpbiPrev,
			   LPVOID lpPrev)
{
    DWORD ret;
    ICCOMPRESS iccmp;
    SEGPTR seg_iccmp;

    TRACE("(0x%08x,%d,%p,%p,%p,%p,...)\n", (DWORD) hic, dwFlags,
	  lpbiOutput, lpData, lpbiInput, lpBits);

    iccmp.dwFlags = dwFlags;

    iccmp.lpbiOutput = lpbiOutput;
    iccmp.lpOutput = lpData;
    iccmp.lpbiInput = lpbiInput;
    iccmp.lpInput = lpBits;

    iccmp.lpckid = lpckid;
    iccmp.lpdwFlags = lpdwFlags;
    iccmp.lFrameNum = lFrameNum;
    iccmp.dwFrameSize = dwFrameSize;
    iccmp.dwQuality = dwQuality;
    iccmp.lpbiPrev = lpbiPrev;
    iccmp.lpPrev = lpPrev;
    seg_iccmp = MapLS(&iccmp);
    ret = ICSendMessage16(hic, ICM_COMPRESS, seg_iccmp, sizeof(ICCOMPRESS));
    UnMapLS(seg_iccmp);
    return ret;
}

/***********************************************************************
 *		_ICDecompress			[MSVIDEO.230]
 */
DWORD VFWAPIV ICDecompress16(HIC16 hic, DWORD dwFlags,
			     LPBITMAPINFOHEADER lpbiFormat, LPVOID lpData,
			     LPBITMAPINFOHEADER lpbi, LPVOID lpBits)
{
    ICDECOMPRESS icd;
    SEGPTR segptr;
    DWORD ret;

    TRACE("(0x%08x,%d,%p,%p,%p,%p)\n", (DWORD) hic, dwFlags, lpbiFormat,
	  lpData, lpbi, lpBits);

    icd.dwFlags = dwFlags;
    icd.lpbiInput = lpbiFormat;
    icd.lpInput = lpData;
    icd.lpbiOutput = lpbi;
    icd.lpOutput = lpBits;
    icd.ckid = 0;
    segptr = MapLS(&icd);
    ret = ICSendMessage16(hic, ICM_DECOMPRESS, segptr, sizeof(ICDECOMPRESS));
    UnMapLS(segptr);
    return ret;
}

/***********************************************************************
 *		_ICDrawBegin		[MSVIDEO.232]
 */
DWORD VFWAPIV ICDrawBegin16(HIC16 hic,		/* [in] */
			    DWORD dwFlags,	/* [in] flags */
			    HPALETTE16 hpal,	/* [in] palette to draw with */
			    HWND16 hwnd,	/* [in] window to draw to */
			    HDC16 hdc,		/* [in] HDC to draw to */
			    INT16 xDst,		/* [in] destination rectangle */
			    INT16 yDst,		/* [in] */
			    INT16 dxDst,	/* [in] */
			    INT16 dyDst,	/* [in] */
			    LPBITMAPINFOHEADER lpbi,	/* [in] format of frame to draw NOTE: SEGPTR */
			    INT16 xSrc,		/* [in] source rectangle */
			    INT16 ySrc,		/* [in] */
			    INT16 dxSrc,	/* [in] */
			    INT16 dySrc,	/* [in] */
			    DWORD dwRate,	/* [in] frames/second = (dwRate/dwScale) */
			    DWORD dwScale)	/* [in] */
{
    DWORD ret;
    ICDRAWBEGIN16 icdb;
    SEGPTR seg_icdb;

    TRACE ("(0x%08x,%d,0x%08x,0x%08x,0x%08x,%u,%u,%u,%u,%p,%u,%u,%u,%u,%d,%d)\n",
	   (DWORD) hic, dwFlags, (DWORD) hpal, (DWORD) hwnd, (DWORD) hdc,
	   xDst, yDst, dxDst, dyDst, lpbi, xSrc, ySrc, dxSrc, dySrc, dwRate,
	   dwScale);

    icdb.dwFlags = dwFlags;
    icdb.hpal = hpal;
    icdb.hwnd = hwnd;
    icdb.hdc = hdc;
    icdb.xDst = xDst;
    icdb.yDst = yDst;
    icdb.dxDst = dxDst;
    icdb.dyDst = dyDst;
    icdb.lpbi = lpbi;		/* Keep this as SEGPTR for the mapping code to deal with */
    icdb.xSrc = xSrc;
    icdb.ySrc = ySrc;
    icdb.dxSrc = dxSrc;
    icdb.dySrc = dySrc;
    icdb.dwRate = dwRate;
    icdb.dwScale = dwScale;
    seg_icdb = MapLS(&icdb);
    ret = (DWORD) ICSendMessage16(hic, ICM_DRAW_BEGIN, seg_icdb,
				  sizeof(ICDRAWBEGIN16));
    UnMapLS(seg_icdb);
    return ret;
}

/***********************************************************************
 *		_ICDraw			[MSVIDEO.234]
 */
DWORD VFWAPIV ICDraw16(HIC16 hic, DWORD dwFlags,
		       LPVOID lpFormat,	/* [???] NOTE: SEGPTR */
		       LPVOID lpData,	/* [???] NOTE: SEGPTR */
		       DWORD cbData, LONG lTime)
{
    DWORD ret;
    ICDRAW icd;
    SEGPTR seg_icd;

    TRACE("(0x%08x,0x%08x,%p,%p,%d,%d)\n", (DWORD) hic, dwFlags,
	  lpFormat, lpData, cbData, lTime);
    icd.dwFlags = dwFlags;
    icd.lpFormat = lpFormat;
    icd.lpData = lpData;
    icd.cbData = cbData;
    icd.lTime = lTime;
    seg_icd = MapLS(&icd);
    ret = ICSendMessage16(hic, ICM_DRAW, seg_icd, sizeof(ICDRAW));
    UnMapLS(seg_icd);
    return ret;
}

/***********************************************************************
 *		ICGetDisplayFormat			[MSVIDEO.239]
 */
HIC16 VFWAPI ICGetDisplayFormat16(HIC16 hic, LPBITMAPINFOHEADER lpbiIn,
				  LPBITMAPINFOHEADER lpbiOut, INT16 depth,
				  INT16 dx, INT16 dy)
{
    return HIC_16(ICGetDisplayFormat(HIC_32(hic), lpbiIn, lpbiOut, depth,
				     dx, dy));
}

#define COPY(x,y) (x->y = x##16->y);
#define COPYPTR(x,y) (x->y = MapSL((SEGPTR)x##16->y));

/******************************************************************
 *		MSVIDEO_MapICDEX16To32
 *
 *
 */
static LPVOID MSVIDEO_MapICDEX16To32(LPDWORD lParam)
{
    LPVOID ret;

    ICDECOMPRESSEX *icdx = HeapAlloc(GetProcessHeap(), 0, sizeof(ICDECOMPRESSEX));
    ICDECOMPRESSEX16 *icdx16 = MapSL(*lParam);
    ret = icdx16;

    COPY(icdx, dwFlags);
    COPYPTR(icdx, lpbiSrc);
    COPYPTR(icdx, lpSrc);
    COPYPTR(icdx, lpbiDst);
    COPYPTR(icdx, lpDst);
    COPY(icdx, xDst);
    COPY(icdx, yDst);
    COPY(icdx, dxDst);
    COPY(icdx, dyDst);
    COPY(icdx, xSrc);
    COPY(icdx, ySrc);
    COPY(icdx, dxSrc);
    COPY(icdx, dySrc);

    *lParam = (DWORD)(icdx);
    return ret;
}

/******************************************************************
 *		MSVIDEO_MapMsg16To32
 *
 *
 */
static LPVOID MSVIDEO_MapMsg16To32(UINT msg, LPDWORD lParam1, LPDWORD lParam2)
{
    LPVOID ret = 0;

    TRACE("Mapping %d\n", msg);

    switch (msg)
    {
    case DRV_LOAD:
    case DRV_ENABLE:
    case DRV_CLOSE:
    case DRV_DISABLE:
    case DRV_FREE:
    case ICM_ABOUT:
    case ICM_CONFIGURE:
    case ICM_COMPRESS_END:
    case ICM_DECOMPRESS_END:
    case ICM_DECOMPRESSEX_END:
    case ICM_SETQUALITY:
    case ICM_DRAW_START_PLAY:
    case ICM_DRAW_STOP_PLAY:
    case ICM_DRAW_REALIZE:
    case ICM_DRAW_RENDERBUFFER:
    case ICM_DRAW_END:
        break;
    case DRV_OPEN:
    case ICM_GETDEFAULTQUALITY:
    case ICM_GETQUALITY:
    case ICM_SETSTATE:
    case ICM_DRAW_WINDOW:
    case ICM_GETBUFFERSWANTED:
        *lParam1 = (DWORD)MapSL(*lParam1);
        break;
    case ICM_GETINFO:
        {
            ICINFO *ici = HeapAlloc(GetProcessHeap(), 0, sizeof(ICINFO));
            ICINFO16 *ici16;

            ici16 = MapSL(*lParam1);
            ret = ici16;

            ici->dwSize = sizeof(ICINFO);
            COPY(ici, fccType);
            COPY(ici, fccHandler);
            COPY(ici, dwFlags);
            COPY(ici, dwVersion);
            COPY(ici, dwVersionICM);
            MultiByteToWideChar( CP_ACP, 0, ici16->szName, -1, ici->szName, 16 );
            MultiByteToWideChar( CP_ACP, 0, ici16->szDescription, -1, ici->szDescription, 128 );
            MultiByteToWideChar( CP_ACP, 0, ici16->szDriver, -1, ici->szDriver, 128 );
            *lParam1 = (DWORD)(ici);
            *lParam2 = sizeof(ICINFO);
        }
        break;
    case ICM_COMPRESS:
        {
            ICCOMPRESS *icc = HeapAlloc(GetProcessHeap(), 0, sizeof(ICCOMPRESS));
            ICCOMPRESS *icc16;

            icc16 = MapSL(*lParam1);
            ret = icc16;

            COPY(icc, dwFlags);
            COPYPTR(icc, lpbiOutput);
            COPYPTR(icc, lpOutput);
            COPYPTR(icc, lpbiInput);
            COPYPTR(icc, lpInput);
            COPYPTR(icc, lpckid);
            COPYPTR(icc, lpdwFlags);
            COPY(icc, lFrameNum);
            COPY(icc, dwFrameSize);
            COPY(icc, dwQuality);
            COPYPTR(icc, lpbiPrev);
            COPYPTR(icc, lpPrev);

            *lParam1 = (DWORD)(icc);
            *lParam2 = sizeof(ICCOMPRESS);
        }
        break;
    case ICM_DECOMPRESS:
        {
            ICDECOMPRESS *icd = HeapAlloc(GetProcessHeap(), 0, sizeof(ICDECOMPRESS));
            ICDECOMPRESS *icd16; /* Same structure except for the pointers */

            icd16 = MapSL(*lParam1);
            ret = icd16;

            COPY(icd, dwFlags);
            COPYPTR(icd, lpbiInput);
            COPYPTR(icd, lpInput);
            COPYPTR(icd, lpbiOutput);
            COPYPTR(icd, lpOutput);
            COPY(icd, ckid);

            *lParam1 = (DWORD)(icd);
            *lParam2 = sizeof(ICDECOMPRESS);
        }
        break;
    case ICM_COMPRESS_BEGIN:
    case ICM_COMPRESS_GET_FORMAT:
    case ICM_COMPRESS_GET_SIZE:
    case ICM_COMPRESS_QUERY:
    case ICM_DECOMPRESS_GET_FORMAT:
    case ICM_DECOMPRESS_QUERY:
    case ICM_DECOMPRESS_BEGIN:
    case ICM_DECOMPRESS_SET_PALETTE:
    case ICM_DECOMPRESS_GET_PALETTE:
        *lParam1 = (DWORD)MapSL(*lParam1);
        *lParam2 = (DWORD)MapSL(*lParam2);
        break;
    case ICM_DECOMPRESSEX_QUERY:
        if ((*lParam2 != sizeof(ICDECOMPRESSEX16)) && (*lParam2 != 0))
            WARN("*lParam2 has unknown value %p\n", (ICDECOMPRESSEX16*)*lParam2);
        /* FIXME: *lParm2 is meant to be 0 or an ICDECOMPRESSEX16*, but is sizeof(ICDECOMRPESSEX16)
         * This is because of ICMessage(). Special case it?
         {
         LPVOID* addr = HeapAlloc(GetProcessHeap(), 0, 2*sizeof(LPVOID));
         addr[0] = MSVIDEO_MapICDEX16To32(lParam1);
         if (*lParam2)
         addr[1] = MSVIDEO_MapICDEX16To32(lParam2);
         else
         addr[1] = 0;

         ret = addr;
         }
         break;*/
    case ICM_DECOMPRESSEX_BEGIN:
    case ICM_DECOMPRESSEX:
        ret = MSVIDEO_MapICDEX16To32(lParam1);
        *lParam2 = sizeof(ICDECOMPRESSEX);
        break;
    case ICM_DRAW_BEGIN:
        {
            ICDRAWBEGIN *icdb = HeapAlloc(GetProcessHeap(), 0, sizeof(ICDRAWBEGIN));
            ICDRAWBEGIN16 *icdb16 = MapSL(*lParam1);
            ret = icdb16;

            COPY(icdb, dwFlags);
            icdb->hpal = HPALETTE_32(icdb16->hpal);
            icdb->hwnd = HWND_32(icdb16->hwnd);
            icdb->hdc = HDC_32(icdb16->hdc);
            COPY(icdb, xDst);
            COPY(icdb, yDst);
            COPY(icdb, dxDst);
            COPY(icdb, dyDst);
            COPYPTR(icdb, lpbi);
            COPY(icdb, xSrc);
            COPY(icdb, ySrc);
            COPY(icdb, dxSrc);
            COPY(icdb, dySrc);
            COPY(icdb, dwRate);
            COPY(icdb, dwScale);

            *lParam1 = (DWORD)(icdb);
            *lParam2 = sizeof(ICDRAWBEGIN);
        }
        break;
    case ICM_DRAW_SUGGESTFORMAT:
        {
            ICDRAWSUGGEST *icds = HeapAlloc(GetProcessHeap(), 0, sizeof(ICDRAWSUGGEST));
            ICDRAWSUGGEST16 *icds16 = MapSL(*lParam1);

            ret = icds16;

            COPY(icds, dwFlags);
            COPYPTR(icds, lpbiIn);
            COPYPTR(icds, lpbiSuggest);
            COPY(icds, dxSrc);
            COPY(icds, dySrc);
            COPY(icds, dxDst);
            COPY(icds, dyDst);
            icds->hicDecompressor = HIC_32(icds16->hicDecompressor);

            *lParam1 = (DWORD)(icds);
            *lParam2 = sizeof(ICDRAWSUGGEST);
        }
        break;
    case ICM_DRAW:
        {
            ICDRAW *icd = HeapAlloc(GetProcessHeap(), 0, sizeof(ICDRAW));
            ICDRAW *icd16 = MapSL(*lParam1);
            ret = icd16;

            COPY(icd, dwFlags);
            COPYPTR(icd, lpFormat);
            COPYPTR(icd, lpData);
            COPY(icd, cbData);
            COPY(icd, lTime);

            *lParam1 = (DWORD)(icd);
            *lParam2 = sizeof(ICDRAW);
        }
        break;
    case ICM_DRAW_START:
    case ICM_DRAW_STOP:
        break;
    default:
        FIXME("%d is not yet handled. Expect a crash.\n", msg);
    }
    return ret;
}

#undef COPY
#undef COPYPTR

/******************************************************************
 *		MSVIDEO_UnmapMsg16To32
 *
 *
 */
static void MSVIDEO_UnmapMsg16To32(UINT msg, LPVOID data16, LPDWORD lParam1, LPDWORD lParam2)
{
    TRACE("Unmapping %d\n", msg);

#define UNCOPY(x, y) (x##16->y = x->y);

    switch (msg)
    {
    case ICM_GETINFO:
        {
            ICINFO *ici = (ICINFO*)(*lParam1);
            ICINFO16 *ici16 = data16;

            UNCOPY(ici, fccType);
            UNCOPY(ici, fccHandler);
            UNCOPY(ici, dwFlags);
            UNCOPY(ici, dwVersion);
            UNCOPY(ici, dwVersionICM);
            WideCharToMultiByte( CP_ACP, 0, ici->szName, -1, ici16->szName,
                                 sizeof(ici16->szName), NULL, NULL );
            ici16->szName[sizeof(ici16->szName)-1] = 0;
            WideCharToMultiByte( CP_ACP, 0, ici->szDescription, -1, ici16->szDescription,
                                 sizeof(ici16->szDescription), NULL, NULL );
            ici16->szDescription[sizeof(ici16->szDescription)-1] = 0;
            /* This just gives garbage for some reason - BB
               lstrcpynWtoA(ici16->szDriver, ici->szDriver, 128);*/

            HeapFree(GetProcessHeap(), 0, ici);
        }
        break;
    case ICM_DECOMPRESS_QUERY:
        /*{
          LPVOID* x = data16;
          HeapFree(GetProcessHeap(), 0, x[0]);
          if (x[1])
          HeapFree(GetProcessHeap(), 0, x[1]);
          }
          break;*/
    case ICM_COMPRESS:
    case ICM_DECOMPRESS:
    case ICM_DECOMPRESSEX_QUERY:
    case ICM_DECOMPRESSEX_BEGIN:
    case ICM_DECOMPRESSEX:
    case ICM_DRAW_BEGIN:
    case ICM_DRAW_SUGGESTFORMAT:
    case ICM_DRAW:
        HeapFree(GetProcessHeap(), 0, data16);
        break;
    default:
        ERR("Unmapping unmapped msg %d\n", msg);
    }
#undef UNCOPY
}

/***********************************************************************
 *		ICInfo				[MSVIDEO.200]
 */
BOOL16 VFWAPI ICInfo16(DWORD fccType, DWORD fccHandler, ICINFO16 *lpicinfo)
{
    BOOL16 ret;
    LPVOID lpv;
    DWORD lParam = (DWORD)lpicinfo;
    DWORD size = ((ICINFO*)(MapSL((SEGPTR)lpicinfo)))->dwSize;

    /* Use the mapping functions to map the ICINFO structure */
    lpv = MSVIDEO_MapMsg16To32(ICM_GETINFO, &lParam, &size);

    ret = ICInfo(fccType, fccHandler, (ICINFO*)lParam);

    MSVIDEO_UnmapMsg16To32(ICM_GETINFO, lpv, &lParam, &size);

    return ret;
}

/******************************************************************
 *		IC_Callback3216
 *
 *
 */
static  LRESULT CALLBACK  IC_Callback3216(DWORD pfn16, HIC hic, HDRVR hdrv, UINT msg, LPARAM lp1, LPARAM lp2)
{
    WORD args[8];
    DWORD ret = 0;

    switch (msg)
    {
    case DRV_OPEN:
        lp2 = (DWORD)MapLS((void*)lp2);
        break;
    }
    args[7] = HIWORD(hic);
    args[6] = LOWORD(hic);
    args[5] = HDRVR_16(hdrv);
    args[4] = msg;
    args[3] = HIWORD(lp1);
    args[2] = LOWORD(lp1);
    args[1] = HIWORD(lp2);
    args[0] = LOWORD(lp2);
    WOWCallback16Ex( pfn16, WCB16_PASCAL, sizeof(args), args, &ret );

    switch (msg)
    {
    case DRV_OPEN:
        UnMapLS(lp2);
        break;
    }
    return ret;
}

#define MAX_THUNKS      32

#include "pshpack1.h"
static struct msvideo_thunk
{
    BYTE        popl_eax;        /* popl  %eax (return address) */
    BYTE        pushl_func;      /* pushl $pfn16 (16bit callback function) */
    DWORD       pfn16;
    BYTE        pushl_eax;       /* pushl %eax */
    BYTE        jmp;             /* ljmp WDML_InvokeCallback16 */
    DWORD       callback;
    HIC16       hIC16;           /* driver's handle */
} *MSVIDEO_Thunks;
#include "poppack.h"

static CRITICAL_SECTION msvideo_cs;
static CRITICAL_SECTION_DEBUG critsect_debug =
{
    0, 0, &msvideo_cs,
    { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
      0, 0, { (DWORD_PTR)(__FILE__ ": msvideo_cs") }
};
static CRITICAL_SECTION msvideo_cs = { &critsect_debug, -1, 0, 0, 0, 0 };

static struct msvideo_thunk*      MSVIDEO_AddThunk(DWORD pfn16)
{
    struct msvideo_thunk* thunk;

    if (!MSVIDEO_Thunks)
    {
        MSVIDEO_Thunks = VirtualAlloc(NULL, MAX_THUNKS * sizeof(*MSVIDEO_Thunks), MEM_COMMIT,
                                      PAGE_EXECUTE_READWRITE);
        if (!MSVIDEO_Thunks) return NULL;
        for (thunk = MSVIDEO_Thunks; thunk < &MSVIDEO_Thunks[MAX_THUNKS]; thunk++)
        {
            thunk->popl_eax     = 0x58;   /* popl  %eax */
            thunk->pushl_func   = 0x68;   /* pushl $pfn16 */
            thunk->pfn16        = 0;
            thunk->pushl_eax    = 0x50;   /* pushl %eax */
            thunk->jmp          = 0xe9;   /* jmp IC_Callback3216 */
            thunk->callback     = (char *)IC_Callback3216 - (char *)(&thunk->callback + 1);
            thunk->hIC16        = 0;
        }
    }
    for (thunk = MSVIDEO_Thunks; thunk < &MSVIDEO_Thunks[MAX_THUNKS]; thunk++)
    {
        if (thunk->pfn16 == 0)
        {
            thunk->pfn16 = pfn16;
            return thunk;
        }
    }
    FIXME("Out of msvideo-thunks. Bump MAX_THUNKS\n");
    return NULL;
}

static struct msvideo_thunk*    MSVIDEO_HasThunk(HIC16 hic)
{
    struct msvideo_thunk* thunk;

    if (!MSVIDEO_Thunks)
        return NULL;

    for (thunk = MSVIDEO_Thunks; thunk < &MSVIDEO_Thunks[MAX_THUNKS]; thunk++)
    {
        if (thunk->hIC16 == hic) return thunk;
    }
    return NULL;
}

/***********************************************************************
 *		ICOpenFunction			[MSVIDEO.206]
 */
HIC16 VFWAPI ICOpenFunction16(DWORD fccType, DWORD fccHandler, UINT16 wMode, FARPROC16 lpfnHandler)
{
    HIC         hic32;
    struct msvideo_thunk*       thunk;

    EnterCriticalSection(&msvideo_cs);
    if (!(thunk = MSVIDEO_AddThunk((DWORD)lpfnHandler)))
    {
        LeaveCriticalSection(&msvideo_cs);
        return 0;
    }
    if ((hic32 = ICOpenFunction(fccType, fccHandler, wMode, (DRIVERPROC)thunk)))
        thunk->hIC16 = HIC_16(hic32);
    else
        thunk->pfn16 = 0;
    LeaveCriticalSection(&msvideo_cs);
    return HIC_16(hic32);
}

/***********************************************************************
 *		ICSendMessage			[MSVIDEO.205]
 */
LRESULT VFWAPI ICSendMessage16(HIC16 hic, UINT16 msg, DWORD lParam1, DWORD lParam2)
{
    LRESULT     ret = ICERR_BADHANDLE;
    struct msvideo_thunk* thunk;

    if ((thunk = MSVIDEO_HasThunk(hic)))
    {
        WORD args[8];
        DWORD result;

        /* FIXME: original code was passing hdrv first and hic second */
        /* but this doesn't match what IC_Callback3216 does */
        args[7] = HIWORD(hic);
        args[6] = LOWORD(hic);
        args[5] = 0; /* the 32bit also sets it to NULL */
        args[4] = msg;
        args[3] = HIWORD(lParam1);
        args[2] = LOWORD(lParam1);
        args[1] = HIWORD(lParam2);
        args[0] = LOWORD(lParam2);
        WOWCallback16Ex( thunk->pfn16, WCB16_PASCAL, sizeof(args), args, &result );
        ret = result;
    }
    else
    {
        /* map the message for a 32 bit infrastructure, and pass it along */
        void*       data16 = MSVIDEO_MapMsg16To32(msg, &lParam1, &lParam2);

        ret = ICSendMessage(HIC_32(hic), msg, lParam1, lParam2);
        if (data16)
            MSVIDEO_UnmapMsg16To32(msg, data16, &lParam1, &lParam2);
    }
    return ret;
}

/***********************************************************************
 *		ICClose			[MSVIDEO.204]
 */
LRESULT WINAPI ICClose16(HIC16 hic)
{
    BOOL ret = ICClose(HIC_32(hic));

    EnterCriticalSection(&msvideo_cs);
    if (ret)
    {
        struct msvideo_thunk* thunk;
        if ((thunk = MSVIDEO_HasThunk(hic)))
        {
            thunk->pfn16 = 0;
            thunk->hIC16 = 0;
        }
        else ret = FALSE;
    }
    LeaveCriticalSection(&msvideo_cs);
    return ret;
}

/***********************************************************************
 *		VideoCapDriverDescAndVer	[MSVIDEO.22]
 */
DWORD WINAPI VideoCapDriverDescAndVer16(WORD nr, LPSTR buf1, WORD buf1len,
                                        LPSTR buf2, WORD buf2len)
{
    static const char version_info_spec[] = "\\StringFileInfo\\040904E4\\FileDescription";
    DWORD	verhandle;
    DWORD	infosize;
    UINT	subblocklen;
    char	*s, buf[2048], fn[260];
    LPBYTE	infobuf;
    LPVOID	subblock;
    DWORD	i, cnt = 0, lRet;
    DWORD	bufLen, fnLen;
    FILETIME	lastWrite;
    HKEY	hKey;
    BOOL        found = FALSE;

    TRACE("(%d,%p,%d,%p,%d)\n", nr, buf1, buf1len, buf2, buf2len);
    lRet = RegOpenKeyExA(HKEY_LOCAL_MACHINE, HKLM_DRIVERS32, 0, KEY_QUERY_VALUE, &hKey);
    if (lRet == ERROR_SUCCESS)
    {
	RegQueryInfoKeyA( hKey, 0, 0, 0, &cnt, 0, 0, 0, 0, 0, 0, 0);
	for (i = 0; i < cnt; i++)
	{
	    bufLen = sizeof(buf) / sizeof(buf[0]);
	    lRet = RegEnumKeyExA(hKey, i, buf, &bufLen, 0, 0, 0, &lastWrite);
	    if (lRet != ERROR_SUCCESS) continue;
	    if (strncasecmp(buf, "vid", 3)) continue;
	    if (nr--) continue;
	    fnLen = sizeof(fn);
	    lRet = RegQueryValueExA(hKey, buf, 0, 0, (LPBYTE)fn, &fnLen);
	    if (lRet == ERROR_SUCCESS) found = TRUE;
	    break;
	}
	RegCloseKey( hKey );
    }

    /* search system.ini if not found in the registry */
    if (!found && GetPrivateProfileStringA("drivers32", NULL, NULL, buf, sizeof(buf), "system.ini"))
    {
	for (s = buf; *s; s += strlen(s) + 1)
	{
	    if (strncasecmp(s, "vid", 3)) continue;
	    if (nr--) continue;
	    if (GetPrivateProfileStringA("drivers32", s, NULL, fn, sizeof(fn), "system.ini"))
		found = TRUE;
	    break;
	}
    }

    if (!found)
    {
        TRACE("No more VID* entries found nr=%d\n", nr);
        return 20;
    }
    infosize = GetFileVersionInfoSizeA(fn, &verhandle);
    if (!infosize)
    {
        TRACE("%s has no fileversioninfo.\n", fn);
        return 18;
    }
    infobuf = HeapAlloc(GetProcessHeap(), 0, infosize);
    if (GetFileVersionInfoA(fn, verhandle, infosize, infobuf))
    {
        /* Yes, two space behind : */
        /* FIXME: test for buflen */
        snprintf(buf2, buf2len, "Version:  %d.%d.%d.%d\n",
                ((WORD*)infobuf)[0x0f],
                ((WORD*)infobuf)[0x0e],
                ((WORD*)infobuf)[0x11],
                ((WORD*)infobuf)[0x10]
	    );
        TRACE("version of %s is %s\n", fn, buf2);
    }
    else
    {
        TRACE("GetFileVersionInfoA failed for %s.\n", fn);
        lstrcpynA(buf2, fn, buf2len); /* msvideo.dll appears to copy fn*/
    }
    /* FIXME: language problem? */
    if (VerQueryValueA(	infobuf,
                        version_info_spec,
                        &subblock,
                        &subblocklen
            ))
    {
        UINT copylen = min(subblocklen,buf1len-1);
        memcpy(buf1, subblock, copylen);
        buf1[copylen] = '\0';
        TRACE("VQA returned %s\n", (LPCSTR)subblock);
    }
    else
    {
        TRACE("VQA did not return on query \\StringFileInfo\\040904E4\\FileDescription?\n");
        lstrcpynA(buf1, fn, buf1len); /* msvideo.dll appears to copy fn*/
    }
    HeapFree(GetProcessHeap(), 0, infobuf);
    return 0;
}

/**************************************************************************
 *                      DllEntryPoint (MSVIDEO.3)
 *
 * MSVIDEO DLL entry point
 *
 */
BOOL WINAPI VIDEO_LibMain(DWORD fdwReason, HINSTANCE hinstDLL, WORD ds,
                          WORD wHeapSize, DWORD dwReserved1, WORD wReserved2)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        break;
    case DLL_PROCESS_DETACH:
        DeleteCriticalSection(&msvideo_cs);
        break;
    }
    return TRUE;
}

/***********************************************************************
 *                      MCIWndRegisterClass(MSVIDEO.251)
 */
BOOL CDECL MCIWndRegisterClass16(void)
{
    return MCIWndRegisterClass();
}

static LRESULT (WINAPI *pMCIWndProc)(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam);

static LRESULT WINAPI MCIWndProc16(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    switch (msg)
    {
    case MCIWNDM_SENDSTRINGA:
    case MCIWNDM_SETTIMEFORMATA:
        lparam = (ULONG_PTR)MapSL(lparam);
        break;

    default:
        break;
    }

    return CallWindowProcA(pMCIWndProc, hwnd, msg, wparam, lparam);
}

/***********************************************************************
 *                      MCIWndCreate(MSVIDEO.250)
 */
HWND16 CDECL MCIWndCreate16(HWND16 parent, HINSTANCE16 hinst16,
                            DWORD style, LPSTR file)
{
    HWND hwnd = MCIWndCreateA(HWND_32(parent), 0, style, file);
    if (hwnd)
        pMCIWndProc = (void *)SetWindowLongPtrA(hwnd, GWLP_WNDPROC, (ULONG_PTR)MCIWndProc16);
    return HWND_16(hwnd);
}
