/*
 * Implementation of the Microsoft Installer (msi.dll)
 *
 * Copyright 2005 Aric Stewart for CodeWeavers
 *
 * 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>

#define COBJMACROS
#define NONAMELESSUNION

#include "windef.h"
#include "winbase.h"
#include "winreg.h"
#include "winnls.h"
#include "shlwapi.h"
#include "wine/debug.h"
#include "msi.h"
#include "msiquery.h"
#include "msipriv.h"
#include "wincrypt.h"
#include "winver.h"
#include "winuser.h"
#include "wine/unicode.h"
#include "sddl.h"

WINE_DEFAULT_DEBUG_CHANNEL(msi);

/*
 * These apis are defined in MSI 3.0
 */

typedef struct tagMediaInfo
{
    struct list entry;
    LPWSTR  path;
    WCHAR   szIndex[10];
    DWORD   index;
} media_info;

static UINT OpenSourceKey(LPCWSTR szProduct, HKEY* key, DWORD dwOptions,
                          MSIINSTALLCONTEXT context, BOOL create)
{
    HKEY rootkey = 0; 
    UINT rc = ERROR_FUNCTION_FAILED;

    if (context == MSIINSTALLCONTEXT_USERUNMANAGED)
    {
        if (dwOptions & MSICODE_PATCH)
            rc = MSIREG_OpenUserPatchesKey(szProduct, &rootkey, create);
        else
            rc = MSIREG_OpenProductKey(szProduct, NULL, context,
                                       &rootkey, create);
    }
    else if (context == MSIINSTALLCONTEXT_USERMANAGED)
    {
        if (dwOptions & MSICODE_PATCH)
            rc = MSIREG_OpenUserPatchesKey(szProduct, &rootkey, create);
        else
            rc = MSIREG_OpenProductKey(szProduct, NULL, context,
                                       &rootkey, create);
    }
    else if (context == MSIINSTALLCONTEXT_MACHINE)
    {
        if (dwOptions & MSICODE_PATCH)
            rc = MSIREG_OpenPatchesKey(szProduct, &rootkey, create);
        else
            rc = MSIREG_OpenProductKey(szProduct, NULL, context,
                                       &rootkey, create);
    }

    if (rc != ERROR_SUCCESS)
    {
        if (dwOptions & MSICODE_PATCH)
            return ERROR_UNKNOWN_PATCH;
        else
            return ERROR_UNKNOWN_PRODUCT;
    }

    if (create)
        rc = RegCreateKeyW(rootkey, szSourceList, key);
    else
    {
        rc = RegOpenKeyW(rootkey,szSourceList, key);
        if (rc != ERROR_SUCCESS)
            rc = ERROR_BAD_CONFIGURATION;
    }

    return rc;
}

static UINT OpenMediaSubkey(HKEY rootkey, HKEY *key, BOOL create)
{
    UINT rc;
    static const WCHAR media[] = {'M','e','d','i','a',0};

    if (create)
        rc = RegCreateKeyW(rootkey, media, key);
    else
        rc = RegOpenKeyW(rootkey,media, key); 

    return rc;
}

static UINT OpenNetworkSubkey(HKEY rootkey, HKEY *key, BOOL create)
{
    UINT rc;
    static const WCHAR net[] = {'N','e','t',0};

    if (create)
        rc = RegCreateKeyW(rootkey, net, key);
    else
        rc = RegOpenKeyW(rootkey, net, key); 

    return rc;
}

static UINT OpenURLSubkey(HKEY rootkey, HKEY *key, BOOL create)
{
    UINT rc;
    static const WCHAR URL[] = {'U','R','L',0};

    if (create)
        rc = RegCreateKeyW(rootkey, URL, key);
    else
        rc = RegOpenKeyW(rootkey, URL, key); 

    return rc;
}

/******************************************************************
 *  MsiSourceListEnumMediaDisksA   (MSI.@)
 */
UINT WINAPI MsiSourceListEnumMediaDisksA(LPCSTR szProductCodeOrPatchCode,
                                         LPCSTR szUserSid, MSIINSTALLCONTEXT dwContext,
                                         DWORD dwOptions, DWORD dwIndex, LPDWORD pdwDiskId,
                                         LPSTR szVolumeLabel, LPDWORD pcchVolumeLabel,
                                         LPSTR szDiskPrompt, LPDWORD pcchDiskPrompt)
{
    LPWSTR product = NULL;
    LPWSTR usersid = NULL;
    LPWSTR volume = NULL;
    LPWSTR prompt = NULL;
    UINT r = ERROR_INVALID_PARAMETER;

    TRACE("(%s, %s, %d, %d, %d, %p, %p, %p, %p, %p)\n", debugstr_a(szProductCodeOrPatchCode),
          debugstr_a(szUserSid), dwContext, dwOptions, dwIndex, pdwDiskId,
          szVolumeLabel, pcchVolumeLabel, szDiskPrompt, pcchDiskPrompt);

    if (szDiskPrompt && !pcchDiskPrompt)
        return ERROR_INVALID_PARAMETER;

    if (szProductCodeOrPatchCode) product = strdupAtoW(szProductCodeOrPatchCode);
    if (szUserSid) usersid = strdupAtoW(szUserSid);

    /* FIXME: add tests for an invalid format */

    if (pcchVolumeLabel)
        volume = msi_alloc(*pcchVolumeLabel * sizeof(WCHAR));

    if (pcchDiskPrompt)
        prompt = msi_alloc(*pcchDiskPrompt * sizeof(WCHAR));

    if (volume) *volume = '\0';
    if (prompt) *prompt = '\0';
    r = MsiSourceListEnumMediaDisksW(product, usersid, dwContext, dwOptions,
                                     dwIndex, pdwDiskId, volume, pcchVolumeLabel,
                                     prompt, pcchDiskPrompt);
    if (r != ERROR_SUCCESS)
        goto done;

    if (szVolumeLabel && pcchVolumeLabel)
        WideCharToMultiByte(CP_ACP, 0, volume, -1, szVolumeLabel,
                            *pcchVolumeLabel + 1, NULL, NULL);

    if (szDiskPrompt)
        WideCharToMultiByte(CP_ACP, 0, prompt, -1, szDiskPrompt,
                            *pcchDiskPrompt + 1, NULL, NULL);

done:
    msi_free(product);
    msi_free(usersid);
    msi_free(volume);
    msi_free(prompt);

    return r;
}

/******************************************************************
 *  MsiSourceListEnumMediaDisksW   (MSI.@)
 */
UINT WINAPI MsiSourceListEnumMediaDisksW(LPCWSTR szProductCodeOrPatchCode,
                                         LPCWSTR szUserSid, MSIINSTALLCONTEXT dwContext,
                                         DWORD dwOptions, DWORD dwIndex, LPDWORD pdwDiskId,
                                         LPWSTR szVolumeLabel, LPDWORD pcchVolumeLabel,
                                         LPWSTR szDiskPrompt, LPDWORD pcchDiskPrompt)
{
    WCHAR squished_pc[GUID_SIZE];
    WCHAR convert[11];
    LPWSTR value = NULL;
    LPWSTR data = NULL;
    LPWSTR ptr, ptr2;
    HKEY source, media;
    DWORD valuesz, datasz = 0;
    DWORD type;
    DWORD numvals, size;
    LONG res;
    UINT r;
    static DWORD index = 0;

    static const WCHAR fmt[] = {'#','%','d',0};

    TRACE("(%s, %s, %d, %d, %d, %p, %p, %p, %p)\n", debugstr_w(szProductCodeOrPatchCode),
          debugstr_w(szUserSid), dwContext, dwOptions, dwIndex, szVolumeLabel,
          pcchVolumeLabel, szDiskPrompt, pcchDiskPrompt);

    if (!szProductCodeOrPatchCode ||
        !squash_guid(szProductCodeOrPatchCode, squished_pc))
        return ERROR_INVALID_PARAMETER;

    if (dwContext == MSIINSTALLCONTEXT_MACHINE && szUserSid)
        return ERROR_INVALID_PARAMETER;

    if (dwOptions != MSICODE_PRODUCT && dwOptions != MSICODE_PATCH)
        return ERROR_INVALID_PARAMETER;

    if (szDiskPrompt && !pcchDiskPrompt)
        return ERROR_INVALID_PARAMETER;

    if (dwIndex == 0)
        index = 0;

    if (dwIndex != index)
        return ERROR_INVALID_PARAMETER;

    r = OpenSourceKey(szProductCodeOrPatchCode, &source,
                      dwOptions, dwContext, FALSE);
    if (r != ERROR_SUCCESS)
        return r;

    r = OpenMediaSubkey(source, &media, FALSE);
    if (r != ERROR_SUCCESS)
    {
        RegCloseKey(source);
        return ERROR_NO_MORE_ITEMS;
    }

    if (!pcchVolumeLabel && !pcchDiskPrompt)
    {
        r = RegEnumValueW(media, dwIndex, NULL, NULL, NULL,
                          &type, NULL, NULL);
        goto done;
    }

    res = RegQueryInfoKeyW(media, NULL, NULL, NULL, NULL, NULL,
                           NULL, &numvals, &valuesz, &datasz, NULL, NULL);
    if (res != ERROR_SUCCESS)
    {
        r = ERROR_BAD_CONFIGURATION;
        goto done;
    }

    value = msi_alloc(++valuesz * sizeof(WCHAR));
    data = msi_alloc(++datasz * sizeof(WCHAR));
    if (!value || !data)
    {
        r = ERROR_OUTOFMEMORY;
        goto done;
    }

    r = RegEnumValueW(media, dwIndex, value, &valuesz,
                      NULL, &type, (LPBYTE)data, &datasz);
    if (r != ERROR_SUCCESS)
        goto done;

    if (pdwDiskId)
        *pdwDiskId = atolW(value);

    ptr2 = data;
    ptr = strchrW(data, ';');
    if (!ptr)
        ptr = data;
    else
        *ptr = '\0';

    if (pcchVolumeLabel)
    {
        if (type == REG_DWORD)
        {
            sprintfW(convert, fmt, *data);
            size = lstrlenW(convert);
            ptr2 = convert;
        }
        else
            size = lstrlenW(data);

        if (size >= *pcchVolumeLabel)
            r = ERROR_MORE_DATA;
        else if (szVolumeLabel)
            lstrcpyW(szVolumeLabel, ptr2);

        *pcchVolumeLabel = size;
    }

    if (pcchDiskPrompt)
    {
        if (!*ptr)
            ptr++;

        if (type == REG_DWORD)
        {
            sprintfW(convert, fmt, *ptr);
            size = lstrlenW(convert);
            ptr = convert;
        }
        else
            size = lstrlenW(ptr);

        if (size >= *pcchDiskPrompt)
            r = ERROR_MORE_DATA;
        else if (szDiskPrompt)
            lstrcpyW(szDiskPrompt, ptr);

        *pcchDiskPrompt = size;
    }

    index++;

done:
    msi_free(value);
    msi_free(data);
    RegCloseKey(source);

    return r;
}

/******************************************************************
 *  MsiSourceListEnumSourcesA   (MSI.@)
 */
UINT WINAPI MsiSourceListEnumSourcesA(LPCSTR szProductCodeOrPatch, LPCSTR szUserSid,
                                      MSIINSTALLCONTEXT dwContext,
                                      DWORD dwOptions, DWORD dwIndex,
                                      LPSTR szSource, LPDWORD pcchSource)
{
    LPWSTR product = NULL;
    LPWSTR usersid = NULL;
    LPWSTR source = NULL;
    DWORD len = 0;
    UINT r = ERROR_INVALID_PARAMETER;
    static DWORD index = 0;

    TRACE("(%s, %s, %d, %d, %d, %p, %p)\n", debugstr_a(szProductCodeOrPatch),
          debugstr_a(szUserSid), dwContext, dwOptions, dwIndex, szSource, pcchSource);

    if (dwIndex == 0)
        index = 0;

    if (szSource && !pcchSource)
        goto done;

    if (dwIndex != index)
        goto done;

    if (szProductCodeOrPatch) product = strdupAtoW(szProductCodeOrPatch);
    if (szUserSid) usersid = strdupAtoW(szUserSid);

    r = MsiSourceListEnumSourcesW(product, usersid, dwContext, dwOptions,
                                  dwIndex, NULL, &len);
    if (r != ERROR_SUCCESS)
        goto done;

    source = msi_alloc(++len * sizeof(WCHAR));
    if (!source)
    {
        r = ERROR_OUTOFMEMORY;
        goto done;
    }

    *source = '\0';
    r = MsiSourceListEnumSourcesW(product, usersid, dwContext, dwOptions,
                                  dwIndex, source, &len);
    if (r != ERROR_SUCCESS)
        goto done;

    len = WideCharToMultiByte(CP_ACP, 0, source, -1, NULL, 0, NULL, NULL);
    if (pcchSource && *pcchSource >= len)
        WideCharToMultiByte(CP_ACP, 0, source, -1, szSource, len, NULL, NULL);
    else if (szSource)
        r = ERROR_MORE_DATA;

    if (pcchSource)
        *pcchSource = len - 1;

done:
    msi_free(product);
    msi_free(usersid);
    msi_free(source);

    if (r == ERROR_SUCCESS)
    {
        if (szSource || !pcchSource) index++;
    }
    else if (dwIndex > index)
        index = 0;

    return r;
}

/******************************************************************
 *  MsiSourceListEnumSourcesW   (MSI.@)
 */
UINT WINAPI MsiSourceListEnumSourcesW(LPCWSTR szProductCodeOrPatch, LPCWSTR szUserSid,
                                      MSIINSTALLCONTEXT dwContext,
                                      DWORD dwOptions, DWORD dwIndex,
                                      LPWSTR szSource, LPDWORD pcchSource)
{
    WCHAR squished_pc[GUID_SIZE];
    WCHAR name[32];
    HKEY source = NULL;
    HKEY subkey = NULL;
    LONG res;
    UINT r = ERROR_INVALID_PARAMETER;
    static DWORD index = 0;

    static const WCHAR format[] = {'%','d',0};

    TRACE("(%s, %s, %d, %d, %d, %p, %p)\n", debugstr_w(szProductCodeOrPatch),
          debugstr_w(szUserSid), dwContext, dwOptions, dwIndex, szSource, pcchSource);

    if (dwIndex == 0)
        index = 0;

    if (!szProductCodeOrPatch || !squash_guid(szProductCodeOrPatch, squished_pc))
        goto done;

    if (szSource && !pcchSource)
        goto done;

    if (!(dwOptions & (MSISOURCETYPE_NETWORK | MSISOURCETYPE_URL)))
        goto done;

    if ((dwOptions & MSISOURCETYPE_NETWORK) && (dwOptions & MSISOURCETYPE_URL))
        goto done;

    if (dwContext == MSIINSTALLCONTEXT_MACHINE && szUserSid)
        goto done;

    if (dwIndex != index)
        goto done;

    r = OpenSourceKey(szProductCodeOrPatch, &source,
                      dwOptions, dwContext, FALSE);
    if (r != ERROR_SUCCESS)
        goto done;

    if (dwOptions & MSISOURCETYPE_NETWORK)
        r = OpenNetworkSubkey(source, &subkey, FALSE);
    else if (dwOptions & MSISOURCETYPE_URL)
        r = OpenURLSubkey(source, &subkey, FALSE);

    if (r != ERROR_SUCCESS)
    {
        r = ERROR_NO_MORE_ITEMS;
        goto done;
    }

    sprintfW(name, format, dwIndex + 1);

    res = RegQueryValueExW(subkey, name, 0, 0, (LPBYTE)szSource, pcchSource);
    if (res != ERROR_SUCCESS && res != ERROR_MORE_DATA)
        r = ERROR_NO_MORE_ITEMS;

done:
    RegCloseKey(subkey);
    RegCloseKey(source);

    if (r == ERROR_SUCCESS)
    {
        if (szSource || !pcchSource) index++;
    }
    else if (dwIndex > index)
        index = 0;

    return r;
}

/******************************************************************
 *  MsiSourceListGetInfoA   (MSI.@)
 */
UINT WINAPI MsiSourceListGetInfoA( LPCSTR szProduct, LPCSTR szUserSid,
                                   MSIINSTALLCONTEXT dwContext, DWORD dwOptions,
                                   LPCSTR szProperty, LPSTR szValue,
                                   LPDWORD pcchValue)
{
    UINT ret;
    LPWSTR product = NULL;
    LPWSTR usersid = NULL;
    LPWSTR property = NULL;
    LPWSTR value = NULL;
    DWORD len = 0;

    if (szValue && !pcchValue)
        return ERROR_INVALID_PARAMETER;

    if (szProduct) product = strdupAtoW(szProduct);
    if (szUserSid) usersid = strdupAtoW(szUserSid);
    if (szProperty) property = strdupAtoW(szProperty);

    ret = MsiSourceListGetInfoW(product, usersid, dwContext, dwOptions,
                                property, NULL, &len);
    if (ret != ERROR_SUCCESS)
        goto done;

    value = msi_alloc(++len * sizeof(WCHAR));
    if (!value)
        return ERROR_OUTOFMEMORY;

    *value = '\0';
    ret = MsiSourceListGetInfoW(product, usersid, dwContext, dwOptions,
                                property, value, &len);
    if (ret != ERROR_SUCCESS)
        goto done;

    len = WideCharToMultiByte(CP_ACP, 0, value, -1, NULL, 0, NULL, NULL);
    if (*pcchValue >= len)
        WideCharToMultiByte(CP_ACP, 0, value, -1, szValue, len, NULL, NULL);
    else if (szValue)
        ret = ERROR_MORE_DATA;

    *pcchValue = len - 1;

done:
    msi_free(product);
    msi_free(usersid);
    msi_free(property);
    msi_free(value);
    return ret;
}

/******************************************************************
 *  MsiSourceListGetInfoW   (MSI.@)
 */
UINT WINAPI MsiSourceListGetInfoW( LPCWSTR szProduct, LPCWSTR szUserSid,
                                   MSIINSTALLCONTEXT dwContext, DWORD dwOptions,
                                   LPCWSTR szProperty, LPWSTR szValue, 
                                   LPDWORD pcchValue) 
{
    WCHAR squished_pc[GUID_SIZE];
    HKEY sourcekey, media;
    LPWSTR source, ptr;
    DWORD size;
    UINT rc;

    static const WCHAR mediapack[] = {
        'M','e','d','i','a','P','a','c','k','a','g','e',0};

    TRACE("%s %s\n", debugstr_w(szProduct), debugstr_w(szProperty));

    if (!szProduct || !squash_guid(szProduct, squished_pc))
        return ERROR_INVALID_PARAMETER;

    if (szValue && !pcchValue)
        return ERROR_INVALID_PARAMETER;

    if (dwContext != MSIINSTALLCONTEXT_USERMANAGED &&
        dwContext != MSIINSTALLCONTEXT_USERUNMANAGED &&
        dwContext != MSIINSTALLCONTEXT_MACHINE)
        return ERROR_INVALID_PARAMETER;

    if (!szProperty)
        return ERROR_INVALID_PARAMETER;

    if (szUserSid)
        FIXME("Unhandled UserSid %s\n",debugstr_w(szUserSid));

    rc = OpenSourceKey(szProduct, &sourcekey, dwOptions, dwContext, FALSE);
    if (rc != ERROR_SUCCESS)
        return rc;

    if (!strcmpW( szProperty, INSTALLPROPERTY_MEDIAPACKAGEPATHW ) ||
        !strcmpW( szProperty, INSTALLPROPERTY_DISKPROMPTW ))
    {
        rc = OpenMediaSubkey(sourcekey, &media, FALSE);
        if (rc != ERROR_SUCCESS)
        {
            RegCloseKey(sourcekey);
            return ERROR_SUCCESS;
        }

        if (!strcmpW( szProperty, INSTALLPROPERTY_MEDIAPACKAGEPATHW ))
            szProperty = mediapack;

        RegQueryValueExW(media, szProperty, 0, 0, (LPBYTE)szValue, pcchValue);
        RegCloseKey(media);
    }
    else if (!strcmpW( szProperty, INSTALLPROPERTY_LASTUSEDSOURCEW ) ||
             !strcmpW( szProperty, INSTALLPROPERTY_LASTUSEDTYPEW ))
    {
        rc = RegQueryValueExW(sourcekey, INSTALLPROPERTY_LASTUSEDSOURCEW,
                              0, 0, NULL, &size);
        if (rc != ERROR_SUCCESS)
        {
            RegCloseKey(sourcekey);
            return ERROR_SUCCESS;
        }

        source = msi_alloc(size);
        RegQueryValueExW(sourcekey, INSTALLPROPERTY_LASTUSEDSOURCEW,
                         0, 0, (LPBYTE)source, &size);

        if (!*source)
        {
            msi_free(source);
            RegCloseKey(sourcekey);
            return ERROR_SUCCESS;
        }

        if (!strcmpW( szProperty, INSTALLPROPERTY_LASTUSEDTYPEW ))
        {
            if (*source != 'n' && *source != 'u' && *source != 'm')
            {
                msi_free(source);
                RegCloseKey(sourcekey);
                return ERROR_SUCCESS;
            }

            ptr = source;
            source[1] = '\0';
        }
        else
        {
            ptr = strrchrW(source, ';');
            if (!ptr)
                ptr = source;
            else
                ptr++;
        }

        if (szValue)
        {
            if (strlenW(ptr) < *pcchValue)
                lstrcpyW(szValue, ptr);
            else
                rc = ERROR_MORE_DATA;
        }

        *pcchValue = lstrlenW(ptr);
        msi_free(source);
    }
    else if (!strcmpW( szProperty, INSTALLPROPERTY_PACKAGENAMEW ))
    {
        *pcchValue = *pcchValue * sizeof(WCHAR);
        rc = RegQueryValueExW(sourcekey, INSTALLPROPERTY_PACKAGENAMEW, 0, 0,
                              (LPBYTE)szValue, pcchValue);
        if (rc != ERROR_SUCCESS && rc != ERROR_MORE_DATA)
        {
            *pcchValue = 0;
            rc = ERROR_SUCCESS;
        }
        else
        {
            if (*pcchValue)
                *pcchValue = (*pcchValue - 1) / sizeof(WCHAR);
            if (szValue)
                szValue[*pcchValue] = '\0';
        }
    }
    else
    {
        FIXME("Unknown property %s\n",debugstr_w(szProperty));
        rc = ERROR_UNKNOWN_PROPERTY;
    }

    RegCloseKey(sourcekey);
    return rc;
}

/******************************************************************
 *  MsiSourceListSetInfoA   (MSI.@)
 */
UINT WINAPI MsiSourceListSetInfoA(LPCSTR szProduct, LPCSTR szUserSid,
                                  MSIINSTALLCONTEXT dwContext, DWORD dwOptions,
                                  LPCSTR szProperty, LPCSTR szValue)
{
    UINT ret;
    LPWSTR product = NULL;
    LPWSTR usersid = NULL;
    LPWSTR property = NULL;
    LPWSTR value = NULL;

    if (szProduct) product = strdupAtoW(szProduct);
    if (szUserSid) usersid = strdupAtoW(szUserSid);
    if (szProperty) property = strdupAtoW(szProperty);
    if (szValue) value = strdupAtoW(szValue);

    ret = MsiSourceListSetInfoW(product, usersid, dwContext, dwOptions,
                                property, value);

    msi_free(product);
    msi_free(usersid);
    msi_free(property);
    msi_free(value);

    return ret;
}

UINT msi_set_last_used_source(LPCWSTR product, LPCWSTR usersid,
                              MSIINSTALLCONTEXT context, DWORD options,
                              LPCWSTR value)
{
    HKEY source;
    LPWSTR buffer;
    WCHAR typechar;
    DWORD size;
    UINT r;
    int index = 1;

    static const WCHAR format[] = {'%','c',';','%','i',';','%','s',0};

    if (options & MSISOURCETYPE_NETWORK)
        typechar = 'n';
    else if (options & MSISOURCETYPE_URL)
        typechar = 'u';
    else if (options & MSISOURCETYPE_MEDIA)
        typechar = 'm';
    else
        return ERROR_INVALID_PARAMETER;

    if (!(options & MSISOURCETYPE_MEDIA))
    {
        r = MsiSourceListAddSourceExW(product, usersid, context,
                                      options, value, 0);
        if (r != ERROR_SUCCESS)
            return r;

        index = 0;
        while ((r = MsiSourceListEnumSourcesW(product, usersid, context, options,
                                              index, NULL, NULL)) == ERROR_SUCCESS)
            index++;

        if (r != ERROR_NO_MORE_ITEMS)
            return r;
    }

    size = (lstrlenW(format) + lstrlenW(value) + 7) * sizeof(WCHAR);
    buffer = msi_alloc(size);
    if (!buffer)
        return ERROR_OUTOFMEMORY;

    r = OpenSourceKey(product, &source, MSICODE_PRODUCT, context, FALSE);
    if (r != ERROR_SUCCESS)
        return r;

    sprintfW(buffer, format, typechar, index, value);

    size = (lstrlenW(buffer) + 1) * sizeof(WCHAR);
    r = RegSetValueExW(source, INSTALLPROPERTY_LASTUSEDSOURCEW, 0,
                       REG_SZ, (LPBYTE)buffer, size);
    msi_free(buffer);

    RegCloseKey(source);
    return r;
}

/******************************************************************
 *  MsiSourceListSetInfoW   (MSI.@)
 */
UINT WINAPI MsiSourceListSetInfoW( LPCWSTR szProduct, LPCWSTR szUserSid,
                                   MSIINSTALLCONTEXT dwContext, DWORD dwOptions,
                                   LPCWSTR szProperty, LPCWSTR szValue)
{
    WCHAR squished_pc[GUID_SIZE];
    HKEY sourcekey, media;
    LPCWSTR property;
    UINT rc;

    static const WCHAR media_package[] = {
        'M','e','d','i','a','P','a','c','k','a','g','e',0
    };

    TRACE("%s %s %x %x %s %s\n", debugstr_w(szProduct), debugstr_w(szUserSid),
            dwContext, dwOptions, debugstr_w(szProperty), debugstr_w(szValue));

    if (!szProduct || !squash_guid(szProduct, squished_pc))
        return ERROR_INVALID_PARAMETER;

    if (!szProperty)
        return ERROR_INVALID_PARAMETER;

    if (!szValue)
        return ERROR_UNKNOWN_PROPERTY;

    if (dwContext == MSIINSTALLCONTEXT_MACHINE && szUserSid)
        return ERROR_INVALID_PARAMETER;

    if (dwOptions & MSICODE_PATCH)
    {
        FIXME("Unhandled options MSICODE_PATCH\n");
        return ERROR_UNKNOWN_PATCH;
    }

    property = szProperty;
    if (!strcmpW( szProperty, INSTALLPROPERTY_MEDIAPACKAGEPATHW ))
        property = media_package;

    rc = OpenSourceKey(szProduct, &sourcekey, MSICODE_PRODUCT, dwContext, FALSE);
    if (rc != ERROR_SUCCESS)
        return rc;

    if (strcmpW( szProperty, INSTALLPROPERTY_LASTUSEDSOURCEW ) &&
        dwOptions & (MSISOURCETYPE_NETWORK | MSISOURCETYPE_URL))
    {
        RegCloseKey(sourcekey);
        return ERROR_INVALID_PARAMETER;
    }

    if (!strcmpW( szProperty, INSTALLPROPERTY_MEDIAPACKAGEPATHW ) ||
        !strcmpW( szProperty, INSTALLPROPERTY_DISKPROMPTW ))
    {
        rc = OpenMediaSubkey(sourcekey, &media, TRUE);
        if (rc == ERROR_SUCCESS)
        {
            rc = msi_reg_set_val_str(media, property, szValue);
            RegCloseKey(media);
        }
    }
    else if (!strcmpW( szProperty, INSTALLPROPERTY_PACKAGENAMEW ))
    {
        DWORD size = (lstrlenW(szValue) + 1) * sizeof(WCHAR);
        rc = RegSetValueExW(sourcekey, INSTALLPROPERTY_PACKAGENAMEW, 0,
                REG_SZ, (const BYTE *)szValue, size);
        if (rc != ERROR_SUCCESS)
            rc = ERROR_UNKNOWN_PROPERTY;
    }
    else if (!strcmpW( szProperty, INSTALLPROPERTY_LASTUSEDSOURCEW ))
    {
        if (!(dwOptions & (MSISOURCETYPE_NETWORK | MSISOURCETYPE_URL)))
            rc = ERROR_INVALID_PARAMETER;
        else
            rc = msi_set_last_used_source(szProduct, szUserSid, dwContext,
                                          dwOptions, szValue);
    }
    else
        rc = ERROR_UNKNOWN_PROPERTY;

    RegCloseKey(sourcekey);
    return rc;
}

/******************************************************************
 *  MsiSourceListAddSourceW (MSI.@)
 */
UINT WINAPI MsiSourceListAddSourceW( LPCWSTR szProduct, LPCWSTR szUserName,
        DWORD dwReserved, LPCWSTR szSource)
{
    WCHAR squished_pc[GUID_SIZE];
    INT ret;
    LPWSTR sidstr = NULL;
    DWORD sidsize = 0;
    DWORD domsize = 0;
    DWORD context;
    HKEY hkey = 0;
    UINT r;

    TRACE("%s %s %s\n", debugstr_w(szProduct), debugstr_w(szUserName), debugstr_w(szSource));

    if (!szSource || !*szSource)
        return ERROR_INVALID_PARAMETER;

    if (dwReserved != 0)
        return ERROR_INVALID_PARAMETER;

    if (!szProduct || !squash_guid(szProduct, squished_pc))
        return ERROR_INVALID_PARAMETER;

    if (!szUserName || !*szUserName)
        context = MSIINSTALLCONTEXT_MACHINE;
    else
    {
        if (LookupAccountNameW(NULL, szUserName, NULL, &sidsize, NULL, &domsize, NULL))
        {
            PSID psid = msi_alloc(sidsize);

            if (LookupAccountNameW(NULL, szUserName, psid, &sidsize, NULL, &domsize, NULL))
                ConvertSidToStringSidW(psid, &sidstr);

            msi_free(psid);
        }

        r = MSIREG_OpenProductKey(szProduct, NULL,
                                  MSIINSTALLCONTEXT_USERMANAGED, &hkey, FALSE);
        if (r == ERROR_SUCCESS)
            context = MSIINSTALLCONTEXT_USERMANAGED;
        else
        {
            r = MSIREG_OpenProductKey(szProduct, NULL,
                                      MSIINSTALLCONTEXT_USERUNMANAGED,
                                      &hkey, FALSE);
            if (r != ERROR_SUCCESS)
                return ERROR_UNKNOWN_PRODUCT;

            context = MSIINSTALLCONTEXT_USERUNMANAGED;
        }

        RegCloseKey(hkey);
    }

    ret = MsiSourceListAddSourceExW(szProduct, sidstr, 
        context, MSISOURCETYPE_NETWORK, szSource, 0);

    if (sidstr)
        LocalFree(sidstr);

    return ret;
}

/******************************************************************
 *  MsiSourceListAddSourceA (MSI.@)
 */
UINT WINAPI MsiSourceListAddSourceA( LPCSTR szProduct, LPCSTR szUserName,
        DWORD dwReserved, LPCSTR szSource)
{
    INT ret;
    LPWSTR szwproduct;
    LPWSTR szwusername;
    LPWSTR szwsource;

    szwproduct = strdupAtoW( szProduct );
    szwusername = strdupAtoW( szUserName );
    szwsource = strdupAtoW( szSource );

    ret = MsiSourceListAddSourceW(szwproduct, szwusername, dwReserved, szwsource);

    msi_free(szwproduct);
    msi_free(szwusername);
    msi_free(szwsource);

    return ret;
}

/******************************************************************
 *  MsiSourceListAddSourceExA (MSI.@)
 */
UINT WINAPI MsiSourceListAddSourceExA(LPCSTR szProduct, LPCSTR szUserSid,
        MSIINSTALLCONTEXT dwContext, DWORD dwOptions, LPCSTR szSource, DWORD dwIndex)
{
    UINT ret;
    LPWSTR product, usersid, source;

    product = strdupAtoW(szProduct);
    usersid = strdupAtoW(szUserSid);
    source = strdupAtoW(szSource);

    ret = MsiSourceListAddSourceExW(product, usersid, dwContext,
                                    dwOptions, source, dwIndex);

    msi_free(product);
    msi_free(usersid);
    msi_free(source);

    return ret;
}

static void free_source_list(struct list *sourcelist)
{
    while (!list_empty(sourcelist))
    {
        media_info *info = LIST_ENTRY(list_head(sourcelist), media_info, entry);
        list_remove(&info->entry);
        msi_free(info->path);
        msi_free(info);
    }
}

static void add_source_to_list(struct list *sourcelist, media_info *info,
                               DWORD *index)
{
    media_info *iter;
    BOOL found = FALSE;
    static const WCHAR fmt[] = {'%','i',0};

    if (index) *index = 0;

    if (list_empty(sourcelist))
    {
        list_add_head(sourcelist, &info->entry);
        return;
    }

    LIST_FOR_EACH_ENTRY(iter, sourcelist, media_info, entry)
    {
        if (!found && info->index < iter->index)
        {
            found = TRUE;
            list_add_before(&iter->entry, &info->entry);
        }

        /* update the rest of the list */
        if (found)
            sprintfW(iter->szIndex, fmt, ++iter->index);
        else if (index)
            (*index)++;
    }

    if (!found)
        list_add_after(&iter->entry, &info->entry);
}

static UINT fill_source_list(struct list *sourcelist, HKEY sourcekey, DWORD *count)
{
    UINT r = ERROR_SUCCESS;
    DWORD index = 0;
    WCHAR name[10];
    DWORD size, val_size;
    media_info *entry;

    *count = 0;

    while (r == ERROR_SUCCESS)
    {
        size = sizeof(name) / sizeof(name[0]);
        r = RegEnumValueW(sourcekey, index, name, &size, NULL, NULL, NULL, &val_size);
        if (r != ERROR_SUCCESS)
            return r;

        entry = msi_alloc(sizeof(media_info));
        if (!entry)
            goto error;

        entry->path = msi_alloc(val_size);
        if (!entry->path)
        {
            msi_free(entry);
            goto error;
        }

        lstrcpyW(entry->szIndex, name);
        entry->index = atoiW(name);

        size++;
        r = RegEnumValueW(sourcekey, index, name, &size, NULL,
                          NULL, (LPBYTE)entry->path, &val_size);
        if (r != ERROR_SUCCESS)
        {
            msi_free(entry->path);
            msi_free(entry);
            goto error;
        }

        index = ++(*count);
        add_source_to_list(sourcelist, entry, NULL);
    }

error:
    *count = -1;
    free_source_list(sourcelist);
    return ERROR_OUTOFMEMORY;
}

/******************************************************************
 *  MsiSourceListAddSourceExW (MSI.@)
 */
UINT WINAPI MsiSourceListAddSourceExW( LPCWSTR szProduct, LPCWSTR szUserSid,
        MSIINSTALLCONTEXT dwContext, DWORD dwOptions, LPCWSTR szSource, 
        DWORD dwIndex)
{
    HKEY sourcekey;
    HKEY typekey;
    UINT rc;
    struct list sourcelist;
    media_info *info;
    WCHAR squished_pc[GUID_SIZE];
    WCHAR name[10];
    LPWSTR source;
    LPCWSTR postfix;
    DWORD size, count;
    DWORD index;

    static const WCHAR fmt[] = {'%','i',0};

    TRACE("%s %s %x %x %s %i\n", debugstr_w(szProduct), debugstr_w(szUserSid),
          dwContext, dwOptions, debugstr_w(szSource), dwIndex);

    if (!szProduct || !squash_guid(szProduct, squished_pc))
        return ERROR_INVALID_PARAMETER;

    if (!szSource || !*szSource)
        return ERROR_INVALID_PARAMETER;

    if (!(dwOptions & (MSISOURCETYPE_NETWORK | MSISOURCETYPE_URL)))
        return ERROR_INVALID_PARAMETER;

    if (dwOptions & MSICODE_PATCH)
    {
        FIXME("Unhandled options MSICODE_PATCH\n");
        return ERROR_FUNCTION_FAILED;
    }

    if (szUserSid && (dwContext & MSIINSTALLCONTEXT_MACHINE))
        return ERROR_INVALID_PARAMETER;

    rc = OpenSourceKey(szProduct, &sourcekey, MSICODE_PRODUCT, dwContext, FALSE);
    if (rc != ERROR_SUCCESS)
        return rc;

    if (dwOptions & MSISOURCETYPE_NETWORK)
        rc = OpenNetworkSubkey(sourcekey, &typekey, TRUE);
    else if (dwOptions & MSISOURCETYPE_URL)
        rc = OpenURLSubkey(sourcekey, &typekey, TRUE);
    else if (dwOptions & MSISOURCETYPE_MEDIA)
        rc = OpenMediaSubkey(sourcekey, &typekey, TRUE);
    else
    {
        ERR("unknown media type: %08x\n", dwOptions);
        RegCloseKey(sourcekey);
        return ERROR_FUNCTION_FAILED;
    }
    if (rc != ERROR_SUCCESS)
    {
        ERR("can't open subkey %u\n", rc);
        RegCloseKey(sourcekey);
        return rc;
    }

    postfix = (dwOptions & MSISOURCETYPE_NETWORK) ? szBackSlash : szForwardSlash;
    if (szSource[lstrlenW(szSource) - 1] == *postfix)
        source = strdupW(szSource);
    else
    {
        size = lstrlenW(szSource) + 2;
        source = msi_alloc(size * sizeof(WCHAR));
        lstrcpyW(source, szSource);
        lstrcatW(source, postfix);
    }

    list_init(&sourcelist);
    rc = fill_source_list(&sourcelist, typekey, &count);
    if (rc != ERROR_NO_MORE_ITEMS)
        return rc;

    size = (lstrlenW(source) + 1) * sizeof(WCHAR);

    if (count == 0)
    {
        rc = RegSetValueExW(typekey, szOne, 0, REG_EXPAND_SZ, (LPBYTE)source, size);
        goto done;
    }
    else if (dwIndex > count || dwIndex == 0)
    {
        sprintfW(name, fmt, count + 1);
        rc = RegSetValueExW(typekey, name, 0, REG_EXPAND_SZ, (LPBYTE)source, size);
        goto done;
    }
    else
    {
        sprintfW(name, fmt, dwIndex);
        info = msi_alloc(sizeof(media_info));
        if (!info)
        {
            rc = ERROR_OUTOFMEMORY;
            goto done;
        }

        info->path = strdupW(source);
        lstrcpyW(info->szIndex, name);
        info->index = dwIndex;
        add_source_to_list(&sourcelist, info, &index);

        LIST_FOR_EACH_ENTRY(info, &sourcelist, media_info, entry)
        {
            if (info->index < index)
                continue;

            size = (lstrlenW(info->path) + 1) * sizeof(WCHAR);
            rc = RegSetValueExW(typekey, info->szIndex, 0,
                                REG_EXPAND_SZ, (LPBYTE)info->path, size);
            if (rc != ERROR_SUCCESS)
                goto done;
        }
    }

done:
    free_source_list(&sourcelist);
    msi_free(source);
    RegCloseKey(typekey);
    RegCloseKey(sourcekey);
    return rc;
}

/******************************************************************
 *  MsiSourceListAddMediaDiskA (MSI.@)
 */
UINT WINAPI MsiSourceListAddMediaDiskA(LPCSTR szProduct, LPCSTR szUserSid,
        MSIINSTALLCONTEXT dwContext, DWORD dwOptions, DWORD dwDiskId,
        LPCSTR szVolumeLabel, LPCSTR szDiskPrompt)
{
    UINT r;
    LPWSTR product = NULL;
    LPWSTR usersid = NULL;
    LPWSTR volume = NULL;
    LPWSTR prompt = NULL;

    if (szProduct) product = strdupAtoW(szProduct);
    if (szUserSid) usersid = strdupAtoW(szUserSid);
    if (szVolumeLabel) volume = strdupAtoW(szVolumeLabel);
    if (szDiskPrompt) prompt = strdupAtoW(szDiskPrompt);

    r = MsiSourceListAddMediaDiskW(product, usersid, dwContext, dwOptions,
                                     dwDiskId, volume, prompt);

    msi_free(product);
    msi_free(usersid);
    msi_free(volume);
    msi_free(prompt);

    return r;
}

/******************************************************************
 *  MsiSourceListAddMediaDiskW (MSI.@)
 */
UINT WINAPI MsiSourceListAddMediaDiskW(LPCWSTR szProduct, LPCWSTR szUserSid, 
        MSIINSTALLCONTEXT dwContext, DWORD dwOptions, DWORD dwDiskId, 
        LPCWSTR szVolumeLabel, LPCWSTR szDiskPrompt)
{
    HKEY sourcekey;
    HKEY mediakey;
    UINT rc;
    WCHAR szIndex[10];
    WCHAR squished_pc[GUID_SIZE];
    LPWSTR buffer;
    DWORD size;

    static const WCHAR fmt[] = {'%','i',0};

    TRACE("%s %s %x %x %i %s %s\n", debugstr_w(szProduct),
            debugstr_w(szUserSid), dwContext, dwOptions, dwDiskId,
            debugstr_w(szVolumeLabel), debugstr_w(szDiskPrompt));

    if (!szProduct || !squash_guid(szProduct, squished_pc))
        return ERROR_INVALID_PARAMETER;

    if (dwOptions != MSICODE_PRODUCT && dwOptions != MSICODE_PATCH)
        return ERROR_INVALID_PARAMETER;

    if ((szVolumeLabel && !*szVolumeLabel) || (szDiskPrompt && !*szDiskPrompt))
        return ERROR_INVALID_PARAMETER;

    if ((dwContext & MSIINSTALLCONTEXT_MACHINE) && szUserSid)
        return ERROR_INVALID_PARAMETER;

    if (dwOptions & MSICODE_PATCH)
    {
        FIXME("Unhandled options MSICODE_PATCH\n");
        return ERROR_FUNCTION_FAILED;
    }

    rc = OpenSourceKey(szProduct, &sourcekey, MSICODE_PRODUCT, dwContext, FALSE);
    if (rc != ERROR_SUCCESS)
        return rc;

    OpenMediaSubkey(sourcekey, &mediakey, TRUE);

    sprintfW(szIndex, fmt, dwDiskId);

    size = 2;
    if (szVolumeLabel) size += lstrlenW(szVolumeLabel);
    if (szDiskPrompt) size += lstrlenW(szDiskPrompt);

    size *= sizeof(WCHAR);
    buffer = msi_alloc(size);
    *buffer = '\0';

    if (szVolumeLabel) lstrcpyW(buffer, szVolumeLabel);
    lstrcatW(buffer, szSemiColon);
    if (szDiskPrompt) lstrcatW(buffer, szDiskPrompt);

    RegSetValueExW(mediakey, szIndex, 0, REG_SZ, (LPBYTE)buffer, size);
    msi_free(buffer);

    RegCloseKey(sourcekey);
    RegCloseKey(mediakey);

    return ERROR_SUCCESS;
}

/******************************************************************
 *  MsiSourceListClearAllA (MSI.@)
 */
UINT WINAPI MsiSourceListClearAllA( LPCSTR szProduct, LPCSTR szUserName, DWORD dwReserved )
{
    FIXME("(%s %s %d)\n", debugstr_a(szProduct), debugstr_a(szUserName), dwReserved);
    return ERROR_SUCCESS;
}

/******************************************************************
 *  MsiSourceListClearAllW (MSI.@)
 */
UINT WINAPI MsiSourceListClearAllW( LPCWSTR szProduct, LPCWSTR szUserName, DWORD dwReserved )
{
    FIXME("(%s %s %d)\n", debugstr_w(szProduct), debugstr_w(szUserName), dwReserved);
    return ERROR_SUCCESS;
}

/******************************************************************
 *  MsiSourceListClearAllExA (MSI.@)
 */
UINT WINAPI MsiSourceListClearAllExA( LPCSTR szProduct, LPCSTR szUserSid,
    MSIINSTALLCONTEXT dwContext, DWORD dwOptions )
{
    FIXME("(%s %s %d %08x)\n", debugstr_a(szProduct), debugstr_a(szUserSid),
          dwContext, dwOptions);
    return ERROR_SUCCESS;
}

/******************************************************************
 *  MsiSourceListClearAllExW (MSI.@)
 */
UINT WINAPI MsiSourceListClearAllExW( LPCWSTR szProduct, LPCWSTR szUserSid,
    MSIINSTALLCONTEXT dwContext, DWORD dwOptions )
{
    FIXME("(%s %s %d %08x)\n", debugstr_w(szProduct), debugstr_w(szUserSid),
          dwContext, dwOptions);
    return ERROR_SUCCESS;
}

/******************************************************************
 *  MsiSourceListClearSourceA (MSI.@)
 */
UINT WINAPI MsiSourceListClearSourceA(LPCSTR szProductCodeOrPatchCode, LPCSTR szUserSid,
                                      MSIINSTALLCONTEXT dwContext, DWORD dwOptions,
                                      LPCSTR szSource)
{
    FIXME("(%s %s %x %x %s)\n", debugstr_a(szProductCodeOrPatchCode), debugstr_a(szUserSid),
          dwContext, dwOptions, debugstr_a(szSource));
    return ERROR_SUCCESS;
}

/******************************************************************
 *  MsiSourceListClearSourceW (MSI.@)
 */
UINT WINAPI MsiSourceListClearSourceW(LPCWSTR szProductCodeOrPatchCode, LPCWSTR szUserSid,
                                      MSIINSTALLCONTEXT dwContext, DWORD dwOptions,
                                      LPCWSTR szSource)
{
    FIXME("(%s %s %x %x %s)\n", debugstr_w(szProductCodeOrPatchCode), debugstr_w(szUserSid),
          dwContext, dwOptions, debugstr_w(szSource));
    return ERROR_SUCCESS;
}
