msacm: acmDriverAdd() support for ACM_DRIVERADDF_FUNCTION, implemented
local drivers.
diff --git a/dlls/msacm/driver.c b/dlls/msacm/driver.c
index 5cb56c5..98430a5 100644
--- a/dlls/msacm/driver.c
+++ b/dlls/msacm/driver.c
@@ -101,6 +101,8 @@
 MMRESULT WINAPI acmDriverAddW(PHACMDRIVERID phadid, HINSTANCE hinstModule,
 			      LPARAM lParam, DWORD dwPriority, DWORD fdwAdd)
 {
+    PWINE_ACMLOCALDRIVER pLocalDrv = NULL;
+
     TRACE("(%p, %p, %08lx, %08lx, %08lx)\n",
           phadid, hinstModule, lParam, dwPriority, fdwAdd);
 
@@ -144,11 +146,17 @@
                 dwPriority      (unused, set to 0)
          */
         fdwAdd &= ~ACM_DRIVERADDF_TYPEMASK;
-
-        *phadid = 0;
-        FIXME("(%p, %p, %ld, %ld, %ld): ACM_DRIVERADDF_FUNCTION: stub\n", phadid, hinstModule, lParam, dwPriority, fdwAdd);
-        SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
-        return MMSYSERR_ERROR;
+        /* FIXME: fdwAdd ignored */
+        /* Application-supplied acmDriverProc's are placed at the top of the priority unless
+           fdwAdd indicates ACM_DRIVERADDF_GLOBAL
+         */
+        pLocalDrv = MSACM_RegisterLocalDriver(hinstModule, (DRIVERPROC)lParam);
+        *phadid = pLocalDrv ? (HACMDRIVERID) MSACM_RegisterDriver(NULL, NULL, pLocalDrv) : NULL;
+        if (!*phadid) {
+            ERR("Unable to register driver via ACM_DRIVERADDF_FUNCTION\n");
+            return MMSYSERR_INVALPARAM;
+        }
+        break;
     case ACM_DRIVERADDF_NOTIFYHWND:
         /*
                 hInstModule     (unused)
@@ -203,8 +211,10 @@
     }
 
     /* close driver if it has been opened */
-    if (pad->hDrvr && !padid->hInstModule)
+    if (pad->hDrvr && !pad->pLocalDrvrInst)
 	CloseDriver(pad->hDrvr, 0, 0);
+    else if (pad->pLocalDrvrInst)
+        MSACM_CloseLocalDriver(pad->pLocalDrvrInst);
 
     HeapFree(MSACM_hHeap, 0, pad);
 
@@ -506,8 +516,10 @@
 
     pad->obj.dwType = WINE_ACMOBJ_DRIVER;
     pad->obj.pACMDriverID = padid;
+    pad->hDrvr = 0;
+    pad->pLocalDrvrInst = NULL;
 
-    if (!(pad->hDrvr = (HDRVR)padid->hInstModule))
+    if (padid->pLocalDriver == NULL)
     {
         ACMDRVOPENDESCW	adod;
         int		len;
@@ -540,6 +552,29 @@
             goto gotError;
         }
     }
+    else
+    {
+        ACMDRVOPENDESCW	adod;
+
+        pad->hDrvr = NULL;
+
+        adod.cbStruct = sizeof(adod);
+        adod.fccType = ACMDRIVERDETAILS_FCCTYPE_AUDIOCODEC;
+        adod.fccComp = ACMDRIVERDETAILS_FCCCOMP_UNDEFINED;
+        adod.dwVersion = acmGetVersion();
+        adod.dwFlags = fdwOpen;
+        adod.dwError = 0;
+        adod.pszSectionName = NULL;
+        adod.pszAliasName = NULL;
+        adod.dnDevNode = 0;
+
+        pad->pLocalDrvrInst = MSACM_OpenLocalDriver(padid->pLocalDriver, (DWORD)&adod);
+        if (!pad->pLocalDrvrInst)
+        {
+            ret = adod.dwError;
+            goto gotError;
+        }
+    }
 
     /* insert new pad at beg of list */
     pad->pNextACMDriver = padid->pACMDriverList;
diff --git a/dlls/msacm/internal.c b/dlls/msacm/internal.c
index 6e1f9ba..78ecb14 100644
--- a/dlls/msacm/internal.c
+++ b/dlls/msacm/internal.c
@@ -281,12 +281,12 @@
  *           MSACM_RegisterDriver()
  */
 PWINE_ACMDRIVERID MSACM_RegisterDriver(LPCWSTR pszDriverAlias, LPCWSTR pszFileName,
-				       HINSTANCE hinstModule)
+				       PWINE_ACMLOCALDRIVER pLocalDriver)
 {
     PWINE_ACMDRIVERID	padid;
 
     TRACE("(%s, %s, %p)\n", 
-          debugstr_w(pszDriverAlias), debugstr_w(pszFileName), hinstModule);
+          debugstr_w(pszDriverAlias), debugstr_w(pszFileName), pLocalDriver);
 
     padid = HeapAlloc(MSACM_hHeap, 0, sizeof(WINE_ACMDRIVERID));
     padid->obj.dwType = WINE_ACMOBJ_DRIVERID;
@@ -303,22 +303,35 @@
         padid->pszFileName = HeapAlloc( MSACM_hHeap, 0, (strlenW(pszFileName)+1) * sizeof(WCHAR) );
         strcpyW( padid->pszFileName, pszFileName );
     }
-    padid->hInstModule = hinstModule;
+    padid->pLocalDriver = pLocalDriver;
 
     padid->pACMDriverList = NULL;
-    padid->pNextACMDriverID = NULL;
-    padid->pPrevACMDriverID = MSACM_pLastACMDriverID;
-    if (MSACM_pLastACMDriverID)
-	MSACM_pLastACMDriverID->pNextACMDriverID = padid;
-    MSACM_pLastACMDriverID = padid;
-    if (!MSACM_pFirstACMDriverID)
-	MSACM_pFirstACMDriverID = padid;
+    
+    if (pLocalDriver) {
+        padid->pPrevACMDriverID = NULL;
+        padid->pNextACMDriverID = MSACM_pFirstACMDriverID;
+        if (MSACM_pFirstACMDriverID)
+            MSACM_pFirstACMDriverID->pPrevACMDriverID = padid;
+        MSACM_pFirstACMDriverID = padid;
+        if (!MSACM_pLastACMDriverID)
+            MSACM_pLastACMDriverID = padid;
+    } else {
+        padid->pNextACMDriverID = NULL;
+        padid->pPrevACMDriverID = MSACM_pLastACMDriverID;
+        if (MSACM_pLastACMDriverID)
+	    MSACM_pLastACMDriverID->pNextACMDriverID = padid;
+        MSACM_pLastACMDriverID = padid;
+        if (!MSACM_pFirstACMDriverID)
+	    MSACM_pFirstACMDriverID = padid;
+    }
     /* disable the driver if we cannot load the cache */
-    if (!MSACM_ReadCache(padid) && !MSACM_FillCache(padid)) {
+    if ((!padid->pszDriverAlias || !MSACM_ReadCache(padid)) && !MSACM_FillCache(padid)) {
 	WARN("Couldn't load cache for ACM driver (%s)\n", debugstr_w(pszFileName));
 	MSACM_UnregisterDriver(padid);
 	return NULL;
     }
+
+    if (pLocalDriver) padid->fdwSupport |= ACMDRIVERDETAILS_SUPPORTF_LOCAL;
     return padid;
 }
 
@@ -724,6 +737,7 @@
 
     pNextACMDriverID = p->pNextACMDriverID;
 
+    if (p->pLocalDriver) MSACM_UnregisterLocalDriver(p->pLocalDriver);
     HeapFree(MSACM_hHeap, 0, p);
 
     return pNextACMDriverID;
@@ -744,7 +758,7 @@
     
     while (panwnd) {
 	panwnd = MSACM_UnRegisterNotificationWindow(panwnd);
-    }    
+    }
 }
 
 /***********************************************************************
@@ -784,6 +798,17 @@
     return (PWINE_ACMNOTIFYWND)MSACM_GetObj((HACMOBJ)hDriver, WINE_ACMOBJ_NOTIFYWND);
 }
 
+/***********************************************************************
+ *           MSACM_GetLocalDriver()
+ */
+/* 
+PWINE_ACMLOCALDRIVER MSACM_GetLocalDriver(HACMDRIVER hDriver)
+{
+    return (PWINE_ACMLOCALDRIVER)MSACM_GetObj((HACMOBJ)hDriver, WINE_ACMOBJ_LOCALDRIVER);
+}
+*/
+#define MSACM_DRIVER_SendMessage(PDRVRINST, msg, lParam1, lParam2) \
+        (PDRVRINST)->pLocalDriver->lpDrvProc((PDRVRINST)->dwDriverID, (HDRVR)(PDRVRINST), msg, lParam1, lParam2)
 
 /***********************************************************************
  *           MSACM_Message()
@@ -792,5 +817,252 @@
 {
     PWINE_ACMDRIVER	pad = MSACM_GetDriver(had);
 
-    return pad ? SendDriverMessage(pad->hDrvr, uMsg, lParam1, lParam2) : MMSYSERR_INVALHANDLE;
+    if (!pad) return MMSYSERR_INVALHANDLE;
+    if (pad->hDrvr) return SendDriverMessage(pad->hDrvr, uMsg, lParam1, lParam2);
+    if (pad->pLocalDrvrInst) return MSACM_DRIVER_SendMessage(pad->pLocalDrvrInst, uMsg, lParam1, lParam2);
+
+    return MMSYSERR_INVALHANDLE;
+}
+
+PWINE_ACMLOCALDRIVER MSACM_pFirstACMLocalDriver = NULL;
+PWINE_ACMLOCALDRIVER MSACM_pLastACMLocalDriver = NULL;
+
+PWINE_ACMLOCALDRIVER MSACM_RegisterLocalDriver(HMODULE hModule, DRIVERPROC lpDriverProc)
+{
+    PWINE_ACMLOCALDRIVER paldrv;
+
+    TRACE("(%p, %p)\n", hModule, lpDriverProc);
+    if (!hModule || !lpDriverProc) return NULL;
+    
+    /* look up previous instance of local driver module */
+    for (paldrv = MSACM_pFirstACMLocalDriver; paldrv; paldrv = paldrv->pNextACMLocalDrv)
+    {
+        if (paldrv->hModule == hModule && paldrv->lpDrvProc == lpDriverProc) return paldrv;
+    }
+
+    paldrv = HeapAlloc(MSACM_hHeap, 0, sizeof(WINE_ACMLOCALDRIVER));
+    paldrv->obj.dwType = WINE_ACMOBJ_LOCALDRIVER;
+    paldrv->obj.pACMDriverID = 0;
+    paldrv->hModule = hModule;
+    paldrv->lpDrvProc = lpDriverProc;
+    paldrv->pACMInstList = NULL;
+
+    paldrv->pNextACMLocalDrv = NULL;
+    paldrv->pPrevACMLocalDrv = MSACM_pLastACMLocalDriver;
+    if (MSACM_pLastACMLocalDriver)
+	MSACM_pLastACMLocalDriver->pNextACMLocalDrv = paldrv;
+    MSACM_pLastACMLocalDriver = paldrv;
+    if (!MSACM_pFirstACMLocalDriver)
+	MSACM_pFirstACMLocalDriver = paldrv;
+
+    return paldrv;
+}
+
+/**************************************************************************
+ *			MSACM_GetNumberOfModuleRefs		[internal]
+ *
+ * Returns the number of open drivers which share the same module.
+ * Inspired from implementation in dlls/winmm/driver.c
+ */
+static unsigned MSACM_GetNumberOfModuleRefs(HMODULE hModule, DRIVERPROC lpDrvProc, WINE_ACMLOCALDRIVERINST ** found)
+{
+    PWINE_ACMLOCALDRIVER lpDrv;
+    unsigned		count = 0;
+
+    if (found) *found = NULL;
+    for (lpDrv = MSACM_pFirstACMLocalDriver; lpDrv; lpDrv = lpDrv->pNextACMLocalDrv)
+    {
+	if (lpDrv->hModule == hModule && lpDrv->lpDrvProc == lpDrvProc)
+        {
+            PWINE_ACMLOCALDRIVERINST pInst = lpDrv->pACMInstList;
+        
+	    while (pInst) {
+                if (found && !*found) *found = pInst;
+	        count++;
+	        pInst = pInst->pNextACMInst;
+	    }
+	}
+    }
+    return count;
+}
+
+/**************************************************************************
+ *				MSACM_RemoveFromList		[internal]
+ *
+ * Generates all the logic to handle driver closure / deletion
+ * Removes a driver struct to the list of open drivers.
+ */
+static	BOOL	MSACM_RemoveFromList(PWINE_ACMLOCALDRIVERINST lpDrv)
+{
+    PWINE_ACMLOCALDRIVER pDriverBase = lpDrv->pLocalDriver;
+    PWINE_ACMLOCALDRIVERINST pPrevInst;
+
+    /* last of this driver in list ? */
+    if (MSACM_GetNumberOfModuleRefs(pDriverBase->hModule, pDriverBase->lpDrvProc, NULL) == 1) {
+        MSACM_DRIVER_SendMessage(lpDrv, DRV_DISABLE, 0L, 0L);
+        MSACM_DRIVER_SendMessage(lpDrv, DRV_FREE,    0L, 0L);
+    }
+    
+    pPrevInst = NULL;
+    if (pDriverBase->pACMInstList != lpDrv) {
+        pPrevInst = pDriverBase->pACMInstList;
+        while (pPrevInst && pPrevInst->pNextACMInst != lpDrv)
+            pPrevInst = pPrevInst->pNextACMInst;
+        if (!pPrevInst) {
+            ERR("requested to remove invalid instance %p\n", pPrevInst);
+            return FALSE;
+        }
+    }
+    if (!pPrevInst) {
+        /* first driver instance on list */
+        pDriverBase->pACMInstList = lpDrv->pNextACMInst;
+    } else {
+        pPrevInst->pNextACMInst = lpDrv->pNextACMInst;
+    }
+    return TRUE;
+}
+
+/**************************************************************************
+ *				MSACM_AddToList		[internal]
+ *
+ * Adds a driver struct to the list of open drivers.
+ * Generates all the logic to handle driver creation / open.
+ */
+static	BOOL	MSACM_AddToList(PWINE_ACMLOCALDRIVERINST lpNewDrv, LPARAM lParam2)
+{
+    PWINE_ACMLOCALDRIVER pDriverBase = lpNewDrv->pLocalDriver;
+
+    /* first of this driver in list ? */
+    if (MSACM_GetNumberOfModuleRefs(pDriverBase->hModule, pDriverBase->lpDrvProc, NULL) == 0) {
+        if (MSACM_DRIVER_SendMessage(lpNewDrv, DRV_LOAD, 0L, 0L) != DRV_SUCCESS) {
+            FIXME("DRV_LOAD failed on driver %p\n", lpNewDrv);
+            return FALSE;
+        }
+        /* returned value is not checked */
+        MSACM_DRIVER_SendMessage(lpNewDrv, DRV_ENABLE, 0L, 0L);
+    }
+
+    lpNewDrv->pNextACMInst = NULL;
+    if (pDriverBase->pACMInstList == NULL) {
+	pDriverBase->pACMInstList = lpNewDrv;
+    } else {
+        PWINE_ACMLOCALDRIVERINST lpDrvInst = pDriverBase->pACMInstList;
+    
+        while (lpDrvInst->pNextACMInst != NULL)
+            lpDrvInst = lpDrvInst->pNextACMInst;
+
+	lpDrvInst->pNextACMInst = lpNewDrv;
+    }
+
+    /* Now just open a new instance of a driver on this module */
+    lpNewDrv->dwDriverID = MSACM_DRIVER_SendMessage(lpNewDrv, DRV_OPEN, 0, lParam2);
+
+    if (lpNewDrv->dwDriverID == 0) {
+        FIXME("DRV_OPEN failed on driver %p\n", lpNewDrv);
+        MSACM_RemoveFromList(lpNewDrv);
+        return FALSE;
+    }
+    return TRUE;
+}
+
+PWINE_ACMLOCALDRIVERINST MSACM_OpenLocalDriver(PWINE_ACMLOCALDRIVER paldrv, LPARAM lParam2)
+{
+    PWINE_ACMLOCALDRIVERINST pDrvInst;
+    
+    pDrvInst = HeapAlloc(MSACM_hHeap, 0, sizeof(WINE_ACMLOCALDRIVERINST));
+    pDrvInst->pLocalDriver = paldrv;
+    pDrvInst->dwDriverID = 0;
+    pDrvInst->pNextACMInst = NULL;
+    pDrvInst->bSession = FALSE;
+    
+    /* Win32 installable drivers must support a two phase opening scheme:
+     * + first open with NULL as lParam2 (session instance),
+     * + then do a second open with the real non null lParam2)
+     */
+    if (MSACM_GetNumberOfModuleRefs(paldrv->hModule, paldrv->lpDrvProc, NULL) == 0 && lParam2)
+    {
+        PWINE_ACMLOCALDRIVERINST   ret;
+
+        if (!MSACM_AddToList(pDrvInst, 0L))
+        {
+            ERR("load0 failed\n");
+            goto exit;
+        }
+        ret = MSACM_OpenLocalDriver(paldrv, lParam2);
+        if (!ret)
+        {
+            MSACM_CloseLocalDriver(pDrvInst);
+            ERR("load1 failed\n");
+            goto exit;
+        }
+        pDrvInst->bSession = TRUE;
+        return ret;
+    }
+    
+    if (!MSACM_AddToList(pDrvInst, lParam2))
+    {
+        ERR("load failed\n");
+        goto exit;
+    }
+
+    TRACE("=> %p\n", pDrvInst);
+    return pDrvInst;
+exit:
+    HeapFree(MSACM_hHeap, 0, pDrvInst);
+    return NULL;
+}
+
+LRESULT MSACM_CloseLocalDriver(PWINE_ACMLOCALDRIVERINST paldrv)
+{
+    if (MSACM_RemoveFromList(paldrv)) {
+        PWINE_ACMLOCALDRIVERINST lpDrv0;
+        PWINE_ACMLOCALDRIVER pDriverBase = paldrv->pLocalDriver;
+    
+        MSACM_DRIVER_SendMessage(paldrv, DRV_CLOSE, 0, 0);
+        paldrv->dwDriverID = 0;
+    
+        if (paldrv->bSession)
+            ERR("should not directly close session instance (%p)\n", paldrv);
+
+        /* if driver has an opened session instance, we have to close it too */
+        if (MSACM_GetNumberOfModuleRefs(pDriverBase->hModule, pDriverBase->lpDrvProc, &lpDrv0) == 1 &&
+                lpDrv0->bSession)
+        {
+            MSACM_DRIVER_SendMessage(lpDrv0, DRV_CLOSE, 0L, 0L);
+            lpDrv0->dwDriverID = 0;
+            MSACM_RemoveFromList(lpDrv0);
+            HeapFree(GetProcessHeap(), 0, lpDrv0);
+        }
+
+        HeapFree(MSACM_hHeap, 0, paldrv);
+        return TRUE;
+    }
+    ERR("unable to close driver instance\n");
+    return FALSE;
+}
+
+PWINE_ACMLOCALDRIVER MSACM_UnregisterLocalDriver(PWINE_ACMLOCALDRIVER paldrv)
+{
+    PWINE_ACMLOCALDRIVER pNextACMLocalDriver;
+
+    if (paldrv->pACMInstList) {
+        ERR("local driver instances still present after closing all drivers - memory leak\n");
+        return NULL;
+    }
+
+    if (paldrv == MSACM_pFirstACMLocalDriver)
+	MSACM_pFirstACMLocalDriver = paldrv->pNextACMLocalDrv;
+    if (paldrv == MSACM_pLastACMLocalDriver)
+	MSACM_pLastACMLocalDriver = paldrv->pPrevACMLocalDrv;
+
+    if (paldrv->pPrevACMLocalDrv)
+	paldrv->pPrevACMLocalDrv->pNextACMLocalDrv = paldrv->pNextACMLocalDrv;
+    if (paldrv->pNextACMLocalDrv)
+	paldrv->pNextACMLocalDrv->pPrevACMLocalDrv = paldrv->pPrevACMLocalDrv;
+
+    pNextACMLocalDriver = paldrv->pNextACMLocalDrv;
+
+    HeapFree(MSACM_hHeap, 0, paldrv);
+
+    return pNextACMLocalDriver;
 }
diff --git a/dlls/msacm/wineacm.h b/dlls/msacm/wineacm.h
index 1a56b1a..abbb4f8 100644
--- a/dlls/msacm/wineacm.h
+++ b/dlls/msacm/wineacm.h
@@ -292,6 +292,7 @@
 #define WINE_ACMOBJ_DRIVER	0x5EED0002
 #define WINE_ACMOBJ_STREAM	0x5EED0003
 #define WINE_ACMOBJ_NOTIFYWND   0x5EED0004
+#define WINE_ACMOBJ_LOCALDRIVER 0x5EED0005
 
 typedef struct _WINE_ACMOBJ
 {
@@ -299,10 +300,32 @@
     PWINE_ACMDRIVERID	pACMDriverID;
 } WINE_ACMOBJ, *PWINE_ACMOBJ;
 
+typedef struct _WINE_ACMLOCALDRIVER * PWINE_ACMLOCALDRIVER;
+typedef struct _WINE_ACMLOCALDRIVERINST * PWINE_ACMLOCALDRIVERINST;
+typedef struct _WINE_ACMLOCALDRIVER
+{
+    WINE_ACMOBJ         obj;
+    HMODULE             hModule;
+    DRIVERPROC          lpDrvProc;
+    PWINE_ACMLOCALDRIVERINST pACMInstList;
+    PWINE_ACMLOCALDRIVER pNextACMLocalDrv;
+    PWINE_ACMLOCALDRIVER pPrevACMLocalDrv;
+} WINE_ACMLOCALDRIVER;
+
+typedef struct _WINE_ACMLOCALDRIVERINST
+{
+    PWINE_ACMLOCALDRIVER pLocalDriver;
+    DWORD dwDriverID;
+    BOOL bSession;
+    PWINE_ACMLOCALDRIVERINST pNextACMInst;
+} WINE_ACMLOCALDRIVERINST;
+
 typedef struct _WINE_ACMDRIVER
 {
     WINE_ACMOBJ		obj;
     HDRVR      		hDrvr;
+    PWINE_ACMLOCALDRIVERINST pLocalDrvrInst;
+
     PWINE_ACMDRIVER	pNextACMDriver;
 } WINE_ACMDRIVER;
 
@@ -319,7 +342,7 @@
     WINE_ACMOBJ		obj;
     LPWSTR		pszDriverAlias;
     LPWSTR              pszFileName;
-    HINSTANCE		hInstModule;          /* NULL if global */
+    PWINE_ACMLOCALDRIVER pLocalDriver;          /* NULL if global */
     PWINE_ACMDRIVER     pACMDriverList;
     PWINE_ACMDRIVERID   pNextACMDriverID;
     PWINE_ACMDRIVERID	pPrevACMDriverID;
@@ -349,7 +372,7 @@
 extern PWINE_ACMDRIVERID MSACM_pFirstACMDriverID;
 extern PWINE_ACMDRIVERID MSACM_pLastACMDriverID;
 extern PWINE_ACMDRIVERID MSACM_RegisterDriver(LPCWSTR pszDriverAlias, LPCWSTR pszFileName,
-					      HINSTANCE hinstModule);
+					      PWINE_ACMLOCALDRIVER pLocalDriver);
 extern void MSACM_RegisterAllDrivers(void);
 extern PWINE_ACMDRIVERID MSACM_UnregisterDriver(PWINE_ACMDRIVERID p);
 extern void MSACM_UnregisterAllDrivers(void);
@@ -371,6 +394,13 @@
 
 extern PWINE_ACMDRIVERID MSACM_RegisterDriverFromRegistry(LPCWSTR pszRegEntry);
 
+extern PWINE_ACMLOCALDRIVER MSACM_RegisterLocalDriver(HMODULE hModule, DRIVERPROC lpDriverProc);
+extern PWINE_ACMLOCALDRIVERINST MSACM_OpenLocalDriver(PWINE_ACMLOCALDRIVER, LPARAM);
+extern LRESULT MSACM_CloseLocalDriver(PWINE_ACMLOCALDRIVERINST);
+extern PWINE_ACMLOCALDRIVER MSACM_UnregisterLocalDriver(PWINE_ACMLOCALDRIVER);
+/*
+extern PWINE_ACMLOCALDRIVER MSACM_GetLocalDriver(HACMDRIVER hDriver);
+*/
 /* From msacm32.c */
 extern HINSTANCE MSACM_hInstance32;