/*
 * Copyright 2002 Jon Griffiths
 * Copyright 2016 Sebastian Lackner
 *
 * 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

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "initguid.h"
#include "ocidl.h"
#include "featurestagingapi.h"
#include "shellscalingapi.h"
#include "shcore.h"
#define WINSHLWAPI
#include "shlwapi.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(shcore);

static DWORD shcore_tls;
static IUnknown *process_ref;

BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, void *reserved)
{
    TRACE("%p, %lu, %p.\n", instance, reason, reserved);

    switch (reason)
    {
        case DLL_PROCESS_ATTACH:
            DisableThreadLibraryCalls(instance);
            shcore_tls = TlsAlloc();
            break;
        case DLL_PROCESS_DETACH:
            if (reserved) break;
            if (shcore_tls != TLS_OUT_OF_INDEXES)
                TlsFree(shcore_tls);
            break;
    }

    return TRUE;
}

HRESULT WINAPI GetProcessDpiAwareness(HANDLE process, PROCESS_DPI_AWARENESS *value)
{
    if (GetProcessDpiAwarenessInternal( process, (DPI_AWARENESS *)value )) return S_OK;
    return HRESULT_FROM_WIN32( GetLastError() );
}

HRESULT WINAPI SetProcessDpiAwareness(PROCESS_DPI_AWARENESS value)
{
    if (SetProcessDpiAwarenessInternal( (DPI_AWARENESS)value )) return S_OK;
    return HRESULT_FROM_WIN32( GetLastError() );
}

HRESULT WINAPI GetDpiForMonitor(HMONITOR monitor, MONITOR_DPI_TYPE type, UINT *x, UINT *y)
{
    if (GetDpiForMonitorInternal( monitor, type, x, y )) return S_OK;
    return HRESULT_FROM_WIN32( GetLastError() );
}

HRESULT WINAPI GetScaleFactorForMonitor(HMONITOR monitor, DEVICE_SCALE_FACTOR *scale)
{
    FIXME("(%p %p): stub\n", monitor, scale);

    *scale = SCALE_100_PERCENT;
    return S_OK;
}

DEVICE_SCALE_FACTOR WINAPI GetScaleFactorForDevice(DISPLAY_DEVICE_TYPE device_type)
{
    FIXME("%d\n", device_type);

    return SCALE_100_PERCENT;
}

HRESULT WINAPI _IStream_Read(IStream *stream, void *dest, ULONG size)
{
    ULONG read;
    HRESULT hr;

    TRACE("%p, %p, %lu.\n", stream, dest, size);

    hr = IStream_Read(stream, dest, size, &read);
    if (SUCCEEDED(hr) && read != size)
        hr = E_FAIL;
    return hr;
}

HRESULT WINAPI IStream_Reset(IStream *stream)
{
    static const LARGE_INTEGER zero;

    TRACE("(%p)\n", stream);

    return IStream_Seek(stream, zero, 0, NULL);
}

HRESULT WINAPI IStream_Size(IStream *stream, ULARGE_INTEGER *size)
{
    STATSTG statstg;
    HRESULT hr;

    TRACE("(%p, %p)\n", stream, size);

    memset(&statstg, 0, sizeof(statstg));

    hr = IStream_Stat(stream, &statstg, STATFLAG_NONAME);

    if (SUCCEEDED(hr) && size)
        *size = statstg.cbSize;
    return hr;
}

HRESULT WINAPI _IStream_Write(IStream *stream, const void *src, ULONG size)
{
    ULONG written;
    HRESULT hr;

    TRACE("%p, %p, %lu.\n", stream, src, size);

    hr = IStream_Write(stream, src, size, &written);
    if (SUCCEEDED(hr) && written != size)
        hr = E_FAIL;

    return hr;
}

void WINAPI IUnknown_AtomicRelease(IUnknown **obj)
{
    TRACE("(%p)\n", obj);

    if (!obj || !*obj)
        return;

    IUnknown_Release(*obj);
    *obj = NULL;
}

HRESULT WINAPI IUnknown_GetSite(IUnknown *unk, REFIID iid, void **site)
{
    IObjectWithSite *obj = NULL;
    HRESULT hr = E_INVALIDARG;

    TRACE("(%p, %s, %p)\n", unk, debugstr_guid(iid), site);

    if (unk && iid && site)
    {
        hr = IUnknown_QueryInterface(unk, &IID_IObjectWithSite, (void **)&obj);
        if (SUCCEEDED(hr) && obj)
        {
            hr = IObjectWithSite_GetSite(obj, iid, site);
            IObjectWithSite_Release(obj);
        }
    }

    return hr;
}

HRESULT WINAPI IUnknown_QueryService(IUnknown *obj, REFGUID sid, REFIID iid, void **out)
{
    IServiceProvider *provider = NULL;
    HRESULT hr;

    if (!out)
        return E_FAIL;

    *out = NULL;

    if (!obj)
        return E_FAIL;

    hr = IUnknown_QueryInterface(obj, &IID_IServiceProvider, (void **)&provider);
    if (hr == S_OK && provider)
    {
        TRACE("Using provider %p.\n", provider);

        hr = IServiceProvider_QueryService(provider, sid, iid, out);

        TRACE("Provider %p returned %p.\n", provider, *out);

        IServiceProvider_Release(provider);
    }

    return hr;
}

void WINAPI IUnknown_Set(IUnknown **dest, IUnknown *src)
{
    TRACE("(%p, %p)\n", dest, src);

    IUnknown_AtomicRelease(dest);

    if (src)
    {
        IUnknown_AddRef(src);
        *dest = src;
    }
}

HRESULT WINAPI IUnknown_SetSite(IUnknown *obj, IUnknown *site)
{
    IInternetSecurityManager *sec_manager;
    IObjectWithSite *objwithsite;
    HRESULT hr;

    if (!obj)
        return E_FAIL;

    hr = IUnknown_QueryInterface(obj, &IID_IObjectWithSite, (void **)&objwithsite);
    TRACE("ObjectWithSite %p, hr %#lx.\n", objwithsite, hr);
    if (SUCCEEDED(hr))
    {
        hr = IObjectWithSite_SetSite(objwithsite, site);
        TRACE("SetSite() hr %#lx.\n", hr);
        IObjectWithSite_Release(objwithsite);
    }
    else
    {
        hr = IUnknown_QueryInterface(obj, &IID_IInternetSecurityManager, (void **)&sec_manager);
        TRACE("InternetSecurityManager %p, hr %#lx.\n", sec_manager, hr);
        if (FAILED(hr))
            return hr;

        hr = IInternetSecurityManager_SetSecuritySite(sec_manager, (IInternetSecurityMgrSite *)site);
        TRACE("SetSecuritySite() hr %#lx.\n", hr);
        IInternetSecurityManager_Release(sec_manager);
    }

    return hr;
}

HRESULT WINAPI SetCurrentProcessExplicitAppUserModelID(const WCHAR *appid)
{
    FIXME("%s: stub\n", debugstr_w(appid));
    return S_OK;
}

HRESULT WINAPI GetCurrentProcessExplicitAppUserModelID(const WCHAR **appid)
{
    FIXME("%p: stub\n", appid);
    *appid = NULL;
    return E_NOTIMPL;
}

/*************************************************************************
 * CommandLineToArgvW            [SHCORE.@]
 *
 * We must interpret the quotes in the command line to rebuild the argv
 * array correctly:
 * - arguments are separated by spaces or tabs
 * - quotes serve as optional argument delimiters
 *   '"a b"'   -> 'a b'
 * - escaped quotes must be converted back to '"'
 *   '\"'      -> '"'
 * - consecutive backslashes preceding a quote see their number halved with
 *   the remainder escaping the quote:
 *   2n   backslashes + quote -> n backslashes + quote as an argument delimiter
 *   2n+1 backslashes + quote -> n backslashes + literal quote
 * - backslashes that are not followed by a quote are copied literally:
 *   'a\b'     -> 'a\b'
 *   'a\\b'    -> 'a\\b'
 * - in quoted strings, consecutive quotes see their number divided by three
 *   with the remainder modulo 3 deciding whether to close the string or not.
 *   Note that the opening quote must be counted in the consecutive quotes,
 *   that's the (1+) below:
 *   (1+) 3n   quotes -> n quotes
 *   (1+) 3n+1 quotes -> n quotes plus closes the quoted string
 *   (1+) 3n+2 quotes -> n+1 quotes plus closes the quoted string
 * - in unquoted strings, the first quote opens the quoted string and the
 *   remaining consecutive quotes follow the above rule.
 */
WCHAR** WINAPI CommandLineToArgvW(const WCHAR *cmdline, int *numargs)
{
    int qcount, bcount;
    const WCHAR *s;
    WCHAR **argv;
    DWORD argc;
    WCHAR *d;

    if (!numargs)
    {
        SetLastError(ERROR_INVALID_PARAMETER);
        return NULL;
    }

    if (*cmdline == 0)
    {
        /* Return the path to the executable */
        DWORD len, deslen = MAX_PATH, size;

        size = sizeof(WCHAR *) * 2 + deslen * sizeof(WCHAR);
        for (;;)
        {
            if (!(argv = LocalAlloc(LMEM_FIXED, size))) return NULL;
            len = GetModuleFileNameW(0, (WCHAR *)(argv + 2), deslen);
            if (!len)
            {
                LocalFree(argv);
                return NULL;
            }
            if (len < deslen) break;
            deslen *= 2;
            size = sizeof(WCHAR *) * 2 + deslen * sizeof(WCHAR);
            LocalFree(argv);
        }
        argv[0] = (WCHAR *)(argv + 2);
        argv[1] = NULL;
        *numargs = 1;

        return argv;
    }

    /* --- First count the arguments */
    argc = 1;
    s = cmdline;
    /* The first argument, the executable path, follows special rules */
    if (*s == '"')
    {
        /* The executable path ends at the next quote, no matter what */
        s++;
        while (*s)
            if (*s++ == '"')
                break;
    }
    else
    {
        /* The executable path ends at the next space, no matter what */
        while (*s && *s != ' ' && *s != '\t')
            s++;
    }
    /* skip to the first argument, if any */
    while (*s == ' ' || *s == '\t')
        s++;
    if (*s)
        argc++;

    /* Analyze the remaining arguments */
    qcount = bcount = 0;
    while (*s)
    {
        if ((*s == ' ' || *s == '\t') && qcount == 0)
        {
            /* skip to the next argument and count it if any */
            while (*s == ' ' || *s == '\t')
                s++;
            if (*s)
                argc++;
            bcount = 0;
        }
        else if (*s == '\\')
        {
            /* '\', count them */
            bcount++;
            s++;
        }
        else if (*s == '"')
        {
            /* '"' */
            if ((bcount & 1) == 0)
                qcount++; /* unescaped '"' */
            s++;
            bcount = 0;
            /* consecutive quotes, see comment in copying code below */
            while (*s == '"')
            {
                qcount++;
                s++;
            }
            qcount = qcount % 3;
            if (qcount == 2)
                qcount = 0;
        }
        else
        {
            /* a regular character */
            bcount = 0;
            s++;
        }
    }

    /* Allocate in a single lump, the string array, and the strings that go
     * with it. This way the caller can make a single LocalFree() call to free
     * both, as per MSDN.
     */
    argv = LocalAlloc(LMEM_FIXED, (argc + 1) * sizeof(WCHAR *) + (lstrlenW(cmdline) + 1) * sizeof(WCHAR));
    if (!argv)
        return NULL;

    /* --- Then split and copy the arguments */
    argv[0] = d = lstrcpyW((WCHAR *)(argv + argc + 1), cmdline);
    argc = 1;
    /* The first argument, the executable path, follows special rules */
    if (*d == '"')
    {
        /* The executable path ends at the next quote, no matter what */
        s = d + 1;
        while (*s)
        {
            if (*s == '"')
            {
                s++;
                break;
            }
            *d++ = *s++;
        }
    }
    else
    {
        /* The executable path ends at the next space, no matter what */
        while (*d && *d != ' ' && *d != '\t')
            d++;
        s = d;
        if (*s)
            s++;
    }
    /* close the executable path */
    *d++ = 0;
    /* skip to the first argument and initialize it if any */
    while (*s == ' ' || *s == '\t')
        s++;
    if (!*s)
    {
        /* There are no parameters so we are all done */
        argv[argc] = NULL;
        *numargs = argc;
        return argv;
    }

    /* Split and copy the remaining arguments */
    argv[argc++] = d;
    qcount = bcount = 0;
    while (*s)
    {
        if ((*s == ' ' || *s == '\t') && qcount == 0)
        {
            /* close the argument */
            *d++ = 0;
            bcount = 0;

            /* skip to the next one and initialize it if any */
            do {
                s++;
            } while (*s == ' ' || *s == '\t');
            if (*s)
                argv[argc++] = d;
        }
        else if (*s=='\\')
        {
            *d++ = *s++;
            bcount++;
        }
        else if (*s == '"')
        {
            if ((bcount & 1) == 0)
            {
                /* Preceded by an even number of '\', this is half that
                 * number of '\', plus a quote which we erase.
                 */
                d -= bcount / 2;
                qcount++;
            }
            else
            {
                /* Preceded by an odd number of '\', this is half that
                 * number of '\' followed by a '"'
                 */
                d = d - bcount / 2 - 1;
                *d++ = '"';
            }
            s++;
            bcount = 0;
            /* Now count the number of consecutive quotes. Note that qcount
             * already takes into account the opening quote if any, as well as
             * the quote that lead us here.
             */
            while (*s == '"')
            {
                if (++qcount == 3)
                {
                    *d++ = '"';
                    qcount = 0;
                }
                s++;
            }
            if (qcount == 2)
                qcount = 0;
        }
        else
        {
            /* a regular character */
            *d++ = *s++;
            bcount = 0;
        }
    }
    *d = '\0';
    argv[argc] = NULL;
    *numargs = argc;

    return argv;
}

struct shstream
{
    IStream IStream_iface;
    LONG refcount;

    union
    {
        struct
        {
            BYTE *buffer;
            DWORD length;
            DWORD position;

            HKEY hkey;
            WCHAR *valuename;
        } mem;
        struct
        {
            HANDLE handle;
            DWORD mode;
            WCHAR *path;
        } file;
    } u;
};

static inline struct shstream *impl_from_IStream(IStream *iface)
{
    return CONTAINING_RECORD(iface, struct shstream, IStream_iface);
}

static HRESULT WINAPI shstream_QueryInterface(IStream *iface, REFIID riid, void **out)
{
    struct shstream *stream = impl_from_IStream(iface);

    TRACE("(%p)->(%s, %p)\n", stream, debugstr_guid(riid), out);

    if (IsEqualIID(riid, &IID_IUnknown) ||
        IsEqualIID(riid, &IID_IStream) ||
        IsEqualIID(riid, &IID_ISequentialStream))
    {
        *out = iface;
        IStream_AddRef(iface);
        return S_OK;
    }

    *out = NULL;
    WARN("Unsupported interface %s.\n", debugstr_guid(riid));
    return E_NOINTERFACE;
}

static ULONG WINAPI shstream_AddRef(IStream *iface)
{
    struct shstream *stream = impl_from_IStream(iface);
    ULONG refcount = InterlockedIncrement(&stream->refcount);

    TRACE("%p, refcount %lu.\n", iface, refcount);

    return refcount;
}

static ULONG WINAPI memstream_Release(IStream *iface)
{
    struct shstream *stream = impl_from_IStream(iface);
    ULONG refcount = InterlockedDecrement(&stream->refcount);

    TRACE("%p, refcount %lu.\n", iface, refcount);

    if (!refcount)
    {
        free(stream->u.mem.buffer);
        free(stream);
    }

    return refcount;
}

static HRESULT WINAPI memstream_Read(IStream *iface, void *buff, ULONG buff_size, ULONG *read_len)
{
    struct shstream *stream = impl_from_IStream(iface);
    DWORD length;

    TRACE("%p, %p, %lu, %p.\n", iface, buff, buff_size, read_len);

    if (stream->u.mem.position >= stream->u.mem.length)
    {
        if (read_len)
            *read_len = 0;
        return S_FALSE;
    }

    length = stream->u.mem.length - stream->u.mem.position;
    if (buff_size < length)
        length = buff_size;

    memmove(buff, stream->u.mem.buffer + stream->u.mem.position, length);
    stream->u.mem.position += length;

    if (read_len)
        *read_len = length;

    return S_OK;
}

static HRESULT WINAPI memstream_Write(IStream *iface, const void *buff, ULONG buff_size, ULONG *written)
{
    struct shstream *stream = impl_from_IStream(iface);
    DWORD length = stream->u.mem.position + buff_size;

    TRACE("%p, %p, %lu, %p.\n", iface, buff, buff_size, written);

    if (length < stream->u.mem.position) /* overflow */
        return STG_E_INSUFFICIENTMEMORY;

    if (length > stream->u.mem.length)
    {
        BYTE *buffer = _recalloc(stream->u.mem.buffer, 1, length);
        if (!buffer)
            return STG_E_INSUFFICIENTMEMORY;

        stream->u.mem.length = length;
        stream->u.mem.buffer = buffer;
    }
    memmove(stream->u.mem.buffer + stream->u.mem.position, buff, buff_size);
    stream->u.mem.position += buff_size; /* adjust pointer */

    if (written)
        *written = buff_size;

    return S_OK;
}

static HRESULT WINAPI memstream_Seek(IStream *iface, LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER*new_pos)
{
    struct shstream *stream = impl_from_IStream(iface);
    LARGE_INTEGER tmp;

    TRACE("%p, %s, %ld, %p.\n", iface, wine_dbgstr_longlong(move.QuadPart), origin, new_pos);

    if (origin == STREAM_SEEK_SET)
        tmp = move;
    else if (origin == STREAM_SEEK_CUR)
        tmp.QuadPart = stream->u.mem.position + move.QuadPart;
    else if (origin == STREAM_SEEK_END)
        tmp.QuadPart = stream->u.mem.length + move.QuadPart;
    else
        return STG_E_INVALIDPARAMETER;

    if (tmp.QuadPart < 0)
        return STG_E_INVALIDFUNCTION;

    /* we cut off the high part here */
    stream->u.mem.position = tmp.u.LowPart;

    if (new_pos)
        new_pos->QuadPart = stream->u.mem.position;
    return S_OK;
}

static HRESULT WINAPI memstream_SetSize(IStream *iface, ULARGE_INTEGER new_size)
{
    struct shstream *stream = impl_from_IStream(iface);
    DWORD length;
    BYTE *buffer;

    TRACE("(%p, %s)\n", stream, wine_dbgstr_longlong(new_size.QuadPart));

    /* we cut off the high part here */
    length = new_size.u.LowPart;
    buffer = _recalloc(stream->u.mem.buffer, 1, length);
    if (!buffer)
        return STG_E_INSUFFICIENTMEMORY;

    stream->u.mem.buffer = buffer;
    stream->u.mem.length = length;

    return S_OK;
}

static HRESULT WINAPI shstream_CopyTo(IStream *iface, IStream *dest, ULARGE_INTEGER size,
        ULARGE_INTEGER *read_len, ULARGE_INTEGER *written)
{
    struct shstream *stream = impl_from_IStream(iface);
    ULARGE_INTEGER total_read, total_written;
    HRESULT hr = S_OK;
    BYTE buffer[0x400];

    TRACE("(%p, %p, %s, %p, %p)\n", stream, dest, wine_dbgstr_longlong(size.QuadPart), read_len, written);

    if (!dest)
        return E_POINTER;

    total_read.QuadPart = 0;
    total_written.QuadPart = 0;

    while (size.QuadPart > 0)
    {
        ULONG chunk_size = size.QuadPart >= sizeof(buffer) ? sizeof(buffer) : size.u.LowPart;
        ULONG chunk_read, chunk_written;

        hr = IStream_Read(iface, buffer, chunk_size, &chunk_read);
        if (FAILED(hr))
            break;

        total_read.QuadPart += chunk_read;

        if (chunk_read)
        {
            hr = IStream_Write(dest, buffer, chunk_read, &chunk_written);
            if (FAILED(hr))
                break;

            total_written.QuadPart += chunk_written;
        }

        if (chunk_read != chunk_size)
            size.QuadPart = 0;
        else
            size.QuadPart -= chunk_read;
    }

    if (read_len)
        read_len->QuadPart = total_read.QuadPart;
    if (written)
        written->QuadPart = total_written.QuadPart;

    return hr;
}

static HRESULT WINAPI shstream_Commit(IStream *iface, DWORD flags)
{
    TRACE("%p, %#lx.\n", iface, flags);

    /* Commit is not supported by this stream */
    return E_NOTIMPL;
}

static HRESULT WINAPI shstream_Revert(IStream *iface)
{
    struct shstream *stream = impl_from_IStream(iface);

    TRACE("(%p)\n", stream);

    /* revert not supported by this stream */
    return E_NOTIMPL;
}

static HRESULT WINAPI shstream_LockRegion(IStream *iface, ULARGE_INTEGER offset, ULARGE_INTEGER size, DWORD lock_type)
{
    struct shstream *stream = impl_from_IStream(iface);

    TRACE("(%p)\n", stream);

    /* lock/unlock not supported by this stream */
    return E_NOTIMPL;
}

static HRESULT WINAPI shstream_UnlockRegion(IStream *iface, ULARGE_INTEGER offset, ULARGE_INTEGER size, DWORD lock_type)
{
    struct shstream *stream = impl_from_IStream(iface);

    TRACE("(%p)\n", stream);

    /* lock/unlock not supported by this stream */
    return E_NOTIMPL;
}

static HRESULT WINAPI memstream_Stat(IStream *iface, STATSTG *statstg, DWORD flags)
{
    struct shstream *stream = impl_from_IStream(iface);

    TRACE("%p, %p, %#lx.\n", iface, statstg, flags);

    memset(statstg, 0, sizeof(*statstg));
    statstg->type = STGTY_STREAM;
    statstg->cbSize.QuadPart = stream->u.mem.length;
    statstg->grfMode = STGM_READWRITE;

    return S_OK;
}

static HRESULT WINAPI shstream_Clone(IStream *iface, IStream **dest)
{
    struct shstream *stream = impl_from_IStream(iface);

    TRACE("(%p, %p)\n", stream, dest);

    *dest = NULL;

    /* clone not supported by this stream */
    return E_NOTIMPL;
}

static const IStreamVtbl memstreamvtbl =
{
    shstream_QueryInterface,
    shstream_AddRef,
    memstream_Release,
    memstream_Read,
    memstream_Write,
    memstream_Seek,
    memstream_SetSize,
    shstream_CopyTo,
    shstream_Commit,
    shstream_Revert,
    shstream_LockRegion,
    shstream_UnlockRegion,
    memstream_Stat,
    shstream_Clone,
};

static struct shstream *shstream_create(const IStreamVtbl *vtbl, const BYTE *data, UINT data_len)
{
    struct shstream *stream;

    if (!data)
        data_len = 0;

    stream = malloc(sizeof(*stream));
    stream->IStream_iface.lpVtbl = vtbl;
    stream->refcount = 1;
    stream->u.mem.buffer = malloc(data_len);
    if (!stream->u.mem.buffer)
    {
        free(stream);
        return NULL;
    }
    memcpy(stream->u.mem.buffer, data, data_len);
    stream->u.mem.length = data_len;
    stream->u.mem.position = 0;

    return stream;
}

/*************************************************************************
 * SHCreateMemStream   [SHCORE.@]
 *
 * Create an IStream object on a block of memory.
 *
 * PARAMS
 * data     [I] Memory block to create the IStream object on
 * data_len [I] Length of data block
 *
 * RETURNS
 * Success: A pointer to the IStream object.
 * Failure: NULL, if any parameters are invalid or an error occurs.
 *
 * NOTES
 *  A copy of the memory block is made, it's freed when the stream is released.
 */
IStream * WINAPI SHCreateMemStream(const BYTE *data, UINT data_len)
{
    struct shstream *stream;

    TRACE("(%p, %u)\n", data, data_len);

    stream = shstream_create(&memstreamvtbl, data, data_len);
    return stream ? &stream->IStream_iface : NULL;
}

static ULONG WINAPI filestream_Release(IStream *iface)
{
    struct shstream *stream = impl_from_IStream(iface);
    ULONG refcount = InterlockedDecrement(&stream->refcount);

    TRACE("%p, refcount %lu.\n", iface, refcount);

    if (!refcount)
    {
        CloseHandle(stream->u.file.handle);
        free(stream->u.file.path);
        free(stream);
    }

    return refcount;
}

static HRESULT WINAPI filestream_Read(IStream *iface, void *buff, ULONG size, ULONG *read_len)
{
    struct shstream *stream = impl_from_IStream(iface);
    DWORD read = 0;

    TRACE("%p, %p, %lu, %p.\n", iface, buff, size, read_len);

    if (!ReadFile(stream->u.file.handle, buff, size, &read, NULL))
    {
        WARN("error %ld reading file\n", GetLastError());
        return S_FALSE;
    }

    if (read_len)
        *read_len = read;

    return read == size ? S_OK : S_FALSE;
}

static HRESULT WINAPI filestream_Write(IStream *iface, const void *buff, ULONG size, ULONG *written)
{
    struct shstream *stream = impl_from_IStream(iface);
    DWORD written_len = 0;

    TRACE("%p, %p, %lu, %p.\n", iface, buff, size, written);

    switch (stream->u.file.mode & 0xf)
    {
        case STGM_WRITE:
        case STGM_READWRITE:
            break;
        default:
            return STG_E_ACCESSDENIED;
    }

    if (!WriteFile(stream->u.file.handle, buff, size, &written_len, NULL))
        return HRESULT_FROM_WIN32(GetLastError());

    if (written)
        *written = written_len;

    return S_OK;
}

static HRESULT WINAPI filestream_Seek(IStream *iface, LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *new_pos)
{
    struct shstream *stream = impl_from_IStream(iface);
    DWORD position;

    TRACE("%p, %s, %ld, %p.\n", iface, wine_dbgstr_longlong(move.QuadPart), origin, new_pos);

    position = SetFilePointer(stream->u.file.handle, move.u.LowPart, NULL, origin);
    if (position == INVALID_SET_FILE_POINTER)
        return HRESULT_FROM_WIN32(GetLastError());

    if (new_pos)
    {
        new_pos->u.HighPart = 0;
        new_pos->u.LowPart = position;
    }

    return S_OK;
}

static HRESULT WINAPI filestream_SetSize(IStream *iface, ULARGE_INTEGER size)
{
    struct shstream *stream = impl_from_IStream(iface);
    LARGE_INTEGER origin, move;

    TRACE("(%p, %s)\n", stream, wine_dbgstr_longlong(size.QuadPart));

    move.QuadPart = 0;
    if (!SetFilePointerEx(stream->u.file.handle, move, &origin, FILE_CURRENT))
        return E_FAIL;

    move.QuadPart = size.QuadPart;
    if (!SetFilePointerEx(stream->u.file.handle, move, NULL, FILE_BEGIN))
        return E_FAIL;

    if (stream->u.file.mode != STGM_READ)
    {
        if (!SetEndOfFile(stream->u.file.handle))
            return E_FAIL;
        if (!SetFilePointerEx(stream->u.file.handle, origin, NULL, FILE_BEGIN))
            return E_FAIL;
    }

    return S_OK;
}

static HRESULT WINAPI filestream_CopyTo(IStream *iface, IStream *dest, ULARGE_INTEGER size,
        ULARGE_INTEGER *read_len, ULARGE_INTEGER *written)
{
    struct shstream *stream = impl_from_IStream(iface);
    HRESULT hr = S_OK;
    char buff[1024];

    TRACE("(%p, %p, %s, %p, %p)\n", stream, dest, wine_dbgstr_longlong(size.QuadPart), read_len, written);

    if (read_len)
        read_len->QuadPart = 0;
    if (written)
        written->QuadPart = 0;

    if (!dest)
        return S_OK;

    while (size.QuadPart)
    {
        ULONG left, read_chunk, written_chunk;

        left = size.QuadPart > sizeof(buff) ? sizeof(buff) : size.QuadPart;

        /* Read */
        hr = IStream_Read(iface, buff, left, &read_chunk);
        if (FAILED(hr) || read_chunk == 0)
            break;
        if (read_len)
            read_len->QuadPart += read_chunk;

        /* Write */
        hr = IStream_Write(dest, buff, read_chunk, &written_chunk);
        if (written)
            written->QuadPart += written_chunk;
        if (FAILED(hr) || written_chunk != left)
            break;

        size.QuadPart -= left;
    }

    return hr;
}

static HRESULT WINAPI filestream_Commit(IStream *iface, DWORD flags)
{
    TRACE("%p, %#lx.\n", iface, flags);

    return S_OK;
}

static HRESULT WINAPI filestream_Stat(IStream *iface, STATSTG *statstg, DWORD flags)
{
    struct shstream *stream = impl_from_IStream(iface);
    BY_HANDLE_FILE_INFORMATION fi;

    TRACE("%p, %p, %#lx.\n", iface, statstg, flags);

    if (!statstg)
        return STG_E_INVALIDPOINTER;

    memset(&fi, 0, sizeof(fi));
    GetFileInformationByHandle(stream->u.file.handle, &fi);

    if (flags & STATFLAG_NONAME)
        statstg->pwcsName = NULL;
    else
    {
        int len = lstrlenW(stream->u.file.path);
        if ((statstg->pwcsName = CoTaskMemAlloc((len + 1) * sizeof(WCHAR))))
            memcpy(statstg->pwcsName, stream->u.file.path, (len + 1) * sizeof(WCHAR));
    }
    statstg->type = 0;
    statstg->cbSize.u.LowPart = fi.nFileSizeLow;
    statstg->cbSize.u.HighPart = fi.nFileSizeHigh;
    statstg->mtime = fi.ftLastWriteTime;
    statstg->ctime = fi.ftCreationTime;
    statstg->atime = fi.ftLastAccessTime;
    statstg->grfMode = stream->u.file.mode;
    statstg->grfLocksSupported = 0;
    memcpy(&statstg->clsid, &IID_IStream, sizeof(CLSID));
    statstg->grfStateBits = 0;
    statstg->reserved = 0;

    return S_OK;
}

static const IStreamVtbl filestreamvtbl =
{
    shstream_QueryInterface,
    shstream_AddRef,
    filestream_Release,
    filestream_Read,
    filestream_Write,
    filestream_Seek,
    filestream_SetSize,
    filestream_CopyTo,
    filestream_Commit,
    shstream_Revert,
    shstream_LockRegion,
    shstream_UnlockRegion,
    filestream_Stat,
    shstream_Clone,
};

/*************************************************************************
 * SHCreateStreamOnFileEx   [SHCORE.@]
 */
HRESULT WINAPI SHCreateStreamOnFileEx(const WCHAR *path, DWORD mode, DWORD attributes,
    BOOL create, IStream *template, IStream **ret)
{
    DWORD access, share, creation_disposition, len;
    struct shstream *stream;
    HANDLE hFile;

    TRACE("%s, %ld, %#lx, %d, %p, %p)\n", debugstr_w(path), mode, attributes,
        create, template, ret);

    if (!path || !ret || template)
        return E_INVALIDARG;

    *ret = NULL;

    /* Access */
    switch (mode & 0xf)
    {
        case STGM_WRITE:
        case STGM_READWRITE:
            access = GENERIC_READ | GENERIC_WRITE;
            break;
        case STGM_READ:
            access = GENERIC_READ;
            break;
        default:
            return E_INVALIDARG;
    }

    /* Sharing */
    switch (mode & 0xf0)
    {
        case 0:
        case STGM_SHARE_DENY_NONE:
            share = FILE_SHARE_READ | FILE_SHARE_WRITE;
            break;
        case STGM_SHARE_DENY_READ:
            share = FILE_SHARE_WRITE;
            break;
        case STGM_SHARE_DENY_WRITE:
            share = FILE_SHARE_READ;
            break;
        case STGM_SHARE_EXCLUSIVE:
            share = 0;
            break;
        default:
            return E_INVALIDARG;
    }

    switch (mode & 0xf000)
    {
        case STGM_FAILIFTHERE:
            creation_disposition = create ? CREATE_NEW : OPEN_EXISTING;
            break;
        case STGM_CREATE:
            creation_disposition = CREATE_ALWAYS;
            break;
        default:
            return E_INVALIDARG;
    }

    hFile = CreateFileW(path, access, share, NULL, creation_disposition, attributes, 0);
    if (hFile == INVALID_HANDLE_VALUE)
        return HRESULT_FROM_WIN32(GetLastError());

    stream = malloc(sizeof(*stream));
    stream->IStream_iface.lpVtbl = &filestreamvtbl;
    stream->refcount = 1;
    stream->u.file.handle = hFile;
    stream->u.file.mode = mode;

    len = lstrlenW(path);
    stream->u.file.path = malloc((len + 1) * sizeof(WCHAR));
    memcpy(stream->u.file.path, path, (len + 1) * sizeof(WCHAR));

    *ret = &stream->IStream_iface;

    return S_OK;
}

/*************************************************************************
 * SHCreateStreamOnFileW   [SHCORE.@]
 */
HRESULT WINAPI SHCreateStreamOnFileW(const WCHAR *path, DWORD mode, IStream **stream)
{
    TRACE("%s, %#lx, %p.\n", debugstr_w(path), mode, stream);

    if (!path || !stream)
        return E_INVALIDARG;

    if ((mode & (STGM_CONVERT | STGM_DELETEONRELEASE | STGM_TRANSACTED)) != 0)
        return E_INVALIDARG;

    return SHCreateStreamOnFileEx(path, mode, 0, FALSE, NULL, stream);
}

/*************************************************************************
 * SHCreateStreamOnFileA   [SHCORE.@]
 */
HRESULT WINAPI SHCreateStreamOnFileA(const char *path, DWORD mode, IStream **stream)
{
    WCHAR *pathW;
    HRESULT hr;
    DWORD len;

    TRACE("%s, %#lx, %p.\n", debugstr_a(path), mode, stream);

    if (!path)
        return HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);

    len = MultiByteToWideChar(CP_ACP, 0, path, -1, NULL, 0);
    pathW = malloc(len * sizeof(WCHAR));
    if (!pathW)
        return E_OUTOFMEMORY;

    MultiByteToWideChar(CP_ACP, 0, path, -1, pathW, len);
    hr = SHCreateStreamOnFileW(pathW, mode, stream);
    free(pathW);

    return hr;
}

static ULONG WINAPI regstream_Release(IStream *iface)
{
    struct shstream *stream = impl_from_IStream(iface);
    ULONG refcount = InterlockedDecrement(&stream->refcount);

    TRACE("%p, refcount %lu.\n", iface, refcount);

    if (!refcount)
    {
        if (stream->u.mem.hkey)
        {
            if (stream->u.mem.length)
                RegSetValueExW(stream->u.mem.hkey, stream->u.mem.valuename, 0, REG_BINARY,
                        (const BYTE *)stream->u.mem.buffer, stream->u.mem.length);
            else
                RegDeleteValueW(stream->u.mem.hkey, stream->u.mem.valuename);
            RegCloseKey(stream->u.mem.hkey);
        }
        CoTaskMemFree(stream->u.mem.valuename);
        free(stream->u.mem.buffer);
        free(stream);
    }

    return refcount;
}

static const IStreamVtbl regstreamvtbl =
{
    shstream_QueryInterface,
    shstream_AddRef,
    regstream_Release,
    memstream_Read,
    memstream_Write,
    memstream_Seek,
    memstream_SetSize,
    shstream_CopyTo,
    shstream_Commit,
    shstream_Revert,
    shstream_LockRegion,
    shstream_UnlockRegion,
    memstream_Stat,
    shstream_Clone,
};

/*************************************************************************
 * SHOpenRegStream2W        [SHCORE.@]
 */
IStream * WINAPI SHOpenRegStream2W(HKEY hKey, const WCHAR *subkey, const WCHAR *value, DWORD mode)
{
    struct shstream *stream;
    HKEY hStrKey = NULL;
    BYTE *buff = NULL;
    DWORD length = 0;
    LONG ret;

    TRACE("%p, %s, %s, %#lx.\n", hKey, debugstr_w(subkey), debugstr_w(value), mode);

    if (mode == STGM_READ)
        ret = RegOpenKeyExW(hKey, subkey, 0, KEY_READ, &hStrKey);
    else /* in write mode we make sure the subkey exits */
        ret = RegCreateKeyExW(hKey, subkey, 0, NULL, 0, KEY_READ | KEY_WRITE, NULL, &hStrKey, NULL);

    if (ret == ERROR_SUCCESS)
    {
        if (mode == STGM_READ || mode == STGM_READWRITE)
        {
            /* read initial data */
            ret = RegQueryValueExW(hStrKey, value, 0, 0, 0, &length);
            if (ret == ERROR_SUCCESS && length)
            {
                buff = malloc(length);
                RegQueryValueExW(hStrKey, value, 0, 0, buff, &length);
            }
        }

        if (!length)
            buff = malloc(length);

        stream = shstream_create(&regstreamvtbl, buff, length);
        free(buff);
        if (stream)
        {
            stream->u.mem.hkey = hStrKey;
            SHStrDupW(value, &stream->u.mem.valuename);
            return &stream->IStream_iface;
        }
    }

    if (hStrKey)
        RegCloseKey(hStrKey);

    return NULL;
}

/*************************************************************************
 * SHOpenRegStream2A        [SHCORE.@]
 */
IStream * WINAPI SHOpenRegStream2A(HKEY hKey, const char *subkey, const char *value, DWORD mode)
{
    WCHAR *subkeyW = NULL, *valueW = NULL;
    IStream *stream;

    TRACE("%p, %s, %s, %#lx.\n", hKey, debugstr_a(subkey), debugstr_a(value), mode);

    if (subkey && FAILED(SHStrDupA(subkey, &subkeyW)))
        return NULL;
    if (value && FAILED(SHStrDupA(value, &valueW)))
    {
        CoTaskMemFree(subkeyW);
        return NULL;
    }

    stream = SHOpenRegStream2W(hKey, subkeyW, valueW, mode);
    CoTaskMemFree(subkeyW);
    CoTaskMemFree(valueW);
    return stream;
}

/*************************************************************************
 * SHOpenRegStreamA        [SHCORE.@]
 */
IStream * WINAPI SHOpenRegStreamA(HKEY hkey, const char *subkey, const char *value, DWORD mode)
{
    WCHAR *subkeyW = NULL, *valueW = NULL;
    IStream *stream;

    TRACE("%p, %s, %s, %#lx.\n", hkey, debugstr_a(subkey), debugstr_a(value), mode);

    if (subkey && FAILED(SHStrDupA(subkey, &subkeyW)))
        return NULL;
    if (value && FAILED(SHStrDupA(value, &valueW)))
    {
        CoTaskMemFree(subkeyW);
        return NULL;
    }

    stream = SHOpenRegStreamW(hkey, subkeyW, valueW, mode);
    CoTaskMemFree(subkeyW);
    CoTaskMemFree(valueW);
    return stream;
}

static ULONG WINAPI dummystream_AddRef(IStream *iface)
{
    TRACE("()\n");
    return 2;
}

static ULONG WINAPI dummystream_Release(IStream *iface)
{
    TRACE("()\n");
    return 1;
}

static HRESULT WINAPI dummystream_Read(IStream *iface, void *buff, ULONG buff_size, ULONG *read_len)
{
    if (read_len)
        *read_len = 0;

    return E_NOTIMPL;
}

static const IStreamVtbl dummystreamvtbl =
{
    shstream_QueryInterface,
    dummystream_AddRef,
    dummystream_Release,
    dummystream_Read,
    memstream_Write,
    memstream_Seek,
    memstream_SetSize,
    shstream_CopyTo,
    shstream_Commit,
    shstream_Revert,
    shstream_LockRegion,
    shstream_UnlockRegion,
    memstream_Stat,
    shstream_Clone,
};

static struct shstream dummyregstream = { { &dummystreamvtbl } };

/*************************************************************************
 * SHOpenRegStreamW        [SHCORE.@]
 */
IStream * WINAPI SHOpenRegStreamW(HKEY hkey, const WCHAR *subkey, const WCHAR *value, DWORD mode)
{
    IStream *stream;

    TRACE("%p, %s, %s, %#lx.\n", hkey, debugstr_w(subkey), debugstr_w(value), mode);
    stream = SHOpenRegStream2W(hkey, subkey, value, mode);
    return stream ? stream : &dummyregstream.IStream_iface;
}

struct threadref
{
    IUnknown IUnknown_iface;
    LONG *refcount;
};

static inline struct threadref *threadref_impl_from_IUnknown(IUnknown *iface)
{
    return CONTAINING_RECORD(iface, struct threadref, IUnknown_iface);
}

static HRESULT WINAPI threadref_QueryInterface(IUnknown *iface, REFIID riid, void **out)
{
    struct threadref *threadref = threadref_impl_from_IUnknown(iface);

    TRACE("(%p, %s, %p)\n", threadref, debugstr_guid(riid), out);

    if (out == NULL)
        return E_POINTER;

    if (IsEqualGUID(&IID_IUnknown, riid))
    {
        *out = iface;
        IUnknown_AddRef(iface);
        return S_OK;
    }

    *out = NULL;
    WARN("Interface %s not supported.\n", debugstr_guid(riid));
    return E_NOINTERFACE;
}

static ULONG WINAPI threadref_AddRef(IUnknown *iface)
{
    struct threadref *threadref = threadref_impl_from_IUnknown(iface);
    LONG refcount = InterlockedIncrement(threadref->refcount);

    TRACE("%p, refcount %ld.\n", threadref, refcount);

    return refcount;
}

static ULONG WINAPI threadref_Release(IUnknown *iface)
{
    struct threadref *threadref = threadref_impl_from_IUnknown(iface);
    LONG refcount = InterlockedDecrement(threadref->refcount);

    TRACE("%p, refcount %ld.\n", threadref, refcount);

    if (!refcount)
        free(threadref);

    return refcount;
}

static const IUnknownVtbl threadrefvtbl =
{
    threadref_QueryInterface,
    threadref_AddRef,
    threadref_Release,
};

/*************************************************************************
 * SHCreateThreadRef        [SHCORE.@]
 */
HRESULT WINAPI SHCreateThreadRef(LONG *refcount, IUnknown **out)
{
    struct threadref *threadref;

    TRACE("(%p, %p)\n", refcount, out);

    if (!refcount || !out)
        return E_INVALIDARG;

    *out = NULL;

    threadref = malloc(sizeof(*threadref));
    if (!threadref)
        return E_OUTOFMEMORY;
    threadref->IUnknown_iface.lpVtbl = &threadrefvtbl;
    threadref->refcount = refcount;

    *refcount = 1;
    *out = &threadref->IUnknown_iface;

    TRACE("Created %p.\n", threadref);
    return S_OK;
}

/*************************************************************************
 * SHGetThreadRef        [SHCORE.@]
 */
HRESULT WINAPI SHGetThreadRef(IUnknown **out)
{
    TRACE("(%p)\n", out);

    if (shcore_tls == TLS_OUT_OF_INDEXES)
        return E_NOINTERFACE;

    *out = TlsGetValue(shcore_tls);
    if (!*out)
        return E_NOINTERFACE;

    IUnknown_AddRef(*out);
    return S_OK;
}

/*************************************************************************
 * SHSetThreadRef        [SHCORE.@]
 */
HRESULT WINAPI SHSetThreadRef(IUnknown *obj)
{
    TRACE("(%p)\n", obj);

    if (shcore_tls == TLS_OUT_OF_INDEXES)
        return E_NOINTERFACE;

    TlsSetValue(shcore_tls, obj);
    return S_OK;
}

/*************************************************************************
 * SHReleaseThreadRef        [SHCORE.@]
 */
HRESULT WINAPI SHReleaseThreadRef(void)
{
    FIXME("() - stub!\n");
    return S_OK;
}

/*************************************************************************
 * GetProcessReference        [SHCORE.@]
 */
HRESULT WINAPI GetProcessReference(IUnknown **obj)
{
    TRACE("(%p)\n", obj);

    *obj = process_ref;

    if (!process_ref)
        return E_FAIL;

    if (*obj)
        IUnknown_AddRef(*obj);

    return S_OK;
}

/*************************************************************************
 * SetProcessReference        [SHCORE.@]
 */
void WINAPI SetProcessReference(IUnknown *obj)
{
    TRACE("(%p)\n", obj);

    process_ref = obj;
}

struct thread_data
{
    LPTHREAD_START_ROUTINE thread_proc;
    LPTHREAD_START_ROUTINE callback;
    void *data;
    DWORD flags;
    HANDLE hEvent;
    IUnknown *thread_ref;
    IUnknown *process_ref;
};

static DWORD WINAPI shcore_thread_wrapper(void *data)
{
    struct thread_data thread_data;
    HRESULT hr = E_FAIL;
    DWORD retval;

    TRACE("(%p)\n", data);

    /* We are now executing in the context of the newly created thread.
     * So we copy the data passed to us (it is on the stack of the function
     * that called us, which is waiting for us to signal an event before
     * returning). */
    thread_data = *(struct thread_data *)data;

    if (thread_data.flags & CTF_COINIT)
    {
        hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
        if (FAILED(hr))
            hr = CoInitializeEx(NULL, COINIT_DISABLE_OLE1DDE);
    }

    if (thread_data.callback)
        thread_data.callback(thread_data.data);

    /* Signal the thread that created us; it can return now. */
    SetEvent(thread_data.hEvent);

    /* Execute the callers start code. */
    retval = thread_data.thread_proc(thread_data.data);

    /* Release thread and process references. */
    if (thread_data.thread_ref)
        IUnknown_Release(thread_data.thread_ref);

    if (thread_data.process_ref)
        IUnknown_Release(thread_data.process_ref);

    if (SUCCEEDED(hr))
        CoUninitialize();

    return retval;
}

/*************************************************************************
 *      SHCreateThread        [SHCORE.@]
 */
BOOL WINAPI SHCreateThread(LPTHREAD_START_ROUTINE thread_proc, void *data, DWORD flags, LPTHREAD_START_ROUTINE callback)
{
    struct thread_data thread_data;
    BOOL called = FALSE;

    TRACE("%p, %p, %#lx, %p.\n", thread_proc, data, flags, callback);

    thread_data.thread_proc = thread_proc;
    thread_data.callback = callback;
    thread_data.data = data;
    thread_data.flags = flags;
    thread_data.hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);

    if (flags & CTF_THREAD_REF)
        SHGetThreadRef(&thread_data.thread_ref);
    else
        thread_data.thread_ref = NULL;

    if (flags & CTF_PROCESS_REF)
        GetProcessReference(&thread_data.process_ref);
    else
        thread_data.process_ref = NULL;

    /* Create the thread */
    if (thread_data.hEvent)
    {
        HANDLE hThread;
        DWORD retval;

        hThread = CreateThread(NULL, 0, shcore_thread_wrapper, &thread_data, 0, &retval);
        if (hThread)
        {
            /* Wait for the thread to signal us to continue */
            WaitForSingleObject(thread_data.hEvent, INFINITE);
            CloseHandle(hThread);
            called = TRUE;
        }
        CloseHandle(thread_data.hEvent);
    }

    if (!called)
    {
        if (!thread_data.callback && flags & CTF_INSIST)
        {
            /* Couldn't call, call synchronously */
            thread_data.thread_proc(data);
            called = TRUE;
        }
        else
        {
            if (thread_data.thread_ref)
                IUnknown_Release(thread_data.thread_ref);

            if (thread_data.process_ref)
                IUnknown_Release(thread_data.process_ref);
        }
    }

    return called;
}

/*************************************************************************
 * SHStrDupW    [SHCORE.@]
 */
HRESULT WINAPI SHStrDupW(const WCHAR *src, WCHAR **dest)
{
    size_t len;

    TRACE("(%s, %p)\n", debugstr_w(src), dest);

    *dest = NULL;

    if (!src)
        return E_INVALIDARG;

    len = (lstrlenW(src) + 1) * sizeof(WCHAR);
    *dest = CoTaskMemAlloc(len);
    if (!*dest)
        return E_OUTOFMEMORY;

    memcpy(*dest, src, len);

    return S_OK;
}

/*************************************************************************
 * SHStrDupA    [SHCORE.@]
 */
HRESULT WINAPI SHStrDupA(const char *src, WCHAR **dest)
{
    DWORD len;

    *dest = NULL;

    if (!src)
        return E_INVALIDARG;

    len = MultiByteToWideChar(CP_ACP, 0, src, -1, NULL, 0);
    *dest = CoTaskMemAlloc(len * sizeof(WCHAR));
    if (!*dest)
        return E_OUTOFMEMORY;

    MultiByteToWideChar(CP_ACP, 0, src, -1, *dest, len);

    return S_OK;
}

/*************************************************************************
 * SHAnsiToAnsi        [SHCORE.@]
 */
DWORD WINAPI SHAnsiToAnsi(const char *src, char *dest, int dest_len)
{
    DWORD ret;

    TRACE("(%s, %p, %d)\n", debugstr_a(src), dest, dest_len);

    if (!src || !dest || dest_len <= 0)
        return 0;

    lstrcpynA(dest, src, dest_len);
    ret = strlen(dest);

    return src[ret] ? 0 : ret + 1;
}

/*************************************************************************
 * SHUnicodeToAnsi        [SHCORE.@]
 */
DWORD WINAPI SHUnicodeToAnsi(const WCHAR *src, char *dest, int dest_len)
{
    int ret = 1;

    TRACE("(%s, %p, %d)\n", debugstr_w(src), dest, dest_len);

    if (!dest || !dest_len)
        return 0;

    if (src)
    {
        ret = WideCharToMultiByte(CP_ACP, 0, src, -1, dest, dest_len, NULL, NULL);
        if (!ret)
        {
            dest[dest_len - 1] = 0;
            ret = dest_len;
        }
    }
    else
        dest[0] = 0;

    return ret;
}

/*************************************************************************
 * SHUnicodeToUnicode        [SHCORE.@]
 */
DWORD WINAPI SHUnicodeToUnicode(const WCHAR *src, WCHAR *dest, int dest_len)
{
    DWORD ret;

    TRACE("(%s, %p, %d)\n", debugstr_w(src), dest, dest_len);

    if (!src || !dest || dest_len <= 0)
        return 0;

    lstrcpynW(dest, src, dest_len);
    ret = lstrlenW(dest);

    return src[ret] ? 0 : ret + 1;
}

/*************************************************************************
 * SHAnsiToUnicode        [SHCORE.@]
 */
DWORD WINAPI SHAnsiToUnicode(const char *src, WCHAR *dest, int dest_len)
{
    int ret = 1;

    TRACE("(%s, %p, %d)\n", debugstr_a(src), dest, dest_len);

    if (!dest || !dest_len)
        return 0;

    if (src)
    {
        ret = MultiByteToWideChar(CP_ACP, 0, src, -1, dest, dest_len);
        if (!ret)
        {
            dest[dest_len - 1] = 0;
            ret = dest_len;
        }
    }
    else
        dest[0] = 0;

    return ret;
}

/*************************************************************************
 * SHRegDuplicateHKey        [SHCORE.@]
 */
HKEY WINAPI SHRegDuplicateHKey(HKEY hKey)
{
    HKEY newKey = 0;

    RegOpenKeyExW(hKey, 0, 0, MAXIMUM_ALLOWED, &newKey);
    TRACE("new key is %p\n", newKey);
    return newKey;
}

/*************************************************************************
 * SHDeleteEmptyKeyW        [SHCORE.@]
 */
DWORD WINAPI SHDeleteEmptyKeyW(HKEY hkey, const WCHAR *subkey)
{
    DWORD ret, count = 0;
    HKEY hsubkey = 0;

    TRACE("(%p, %s)\n", hkey, debugstr_w(subkey));

    ret = RegOpenKeyExW(hkey, subkey, 0, KEY_READ, &hsubkey);
    if (!ret)
    {
        ret = RegQueryInfoKeyW(hsubkey, NULL, NULL, NULL, &count,
                             NULL, NULL, NULL, NULL, NULL, NULL, NULL);
        RegCloseKey(hsubkey);
        if (!ret)
        {
            if (count)
                ret = ERROR_KEY_HAS_CHILDREN;
            else
                ret = RegDeleteKeyW(hkey, subkey);
        }
    }

    return ret;
}

/*************************************************************************
 * SHDeleteEmptyKeyA        [SHCORE.@]
 */
DWORD WINAPI SHDeleteEmptyKeyA(HKEY hkey, const char *subkey)
{
    WCHAR *subkeyW = NULL;
    DWORD ret;

    TRACE("(%p, %s)\n", hkey, debugstr_a(subkey));

    if (subkey && FAILED(SHStrDupA(subkey, &subkeyW)))
        return ERROR_OUTOFMEMORY;

    ret = SHDeleteEmptyKeyW(hkey, subkeyW);
    CoTaskMemFree(subkeyW);
    return ret;
}

/*************************************************************************
 * SHDeleteKeyW        [SHCORE.@]
 */
DWORD WINAPI SHDeleteKeyW(HKEY hkey, const WCHAR *subkey)
{
    TRACE("(%p, %s)\n", hkey, debugstr_w(subkey));

    return RegDeleteTreeW(hkey, subkey);
}

/*************************************************************************
 * SHDeleteKeyA        [SHCORE.@]
 */
DWORD WINAPI SHDeleteKeyA(HKEY hkey, const char *subkey)
{
    TRACE("(%p, %s)\n", hkey, debugstr_a(subkey));

    return RegDeleteTreeA(hkey, subkey);
}

/*************************************************************************
 * SHDeleteValueW        [SHCORE.@]
 */
DWORD WINAPI SHDeleteValueW(HKEY hkey, const WCHAR *subkey, const WCHAR *value)
{
    HKEY hsubkey;
    DWORD ret;

    TRACE("(%p, %s, %s)\n", hkey, debugstr_w(subkey), debugstr_w(value));

    ret = RegOpenKeyExW(hkey, subkey, 0, KEY_SET_VALUE, &hsubkey);
    if (!ret)
    {
        ret = RegDeleteValueW(hsubkey, value);
        RegCloseKey(hsubkey);
    }

    return ret;
}

/*************************************************************************
 * SHDeleteValueA        [SHCORE.@]
 */
DWORD WINAPI SHDeleteValueA(HKEY hkey, const char *subkey, const char *value)
{
    WCHAR *subkeyW = NULL, *valueW = NULL;
    DWORD ret;

    TRACE("(%p, %s, %s)\n", hkey, debugstr_a(subkey), debugstr_a(value));

    if (subkey && FAILED(SHStrDupA(subkey, &subkeyW)))
        return ERROR_OUTOFMEMORY;
    if (value && FAILED(SHStrDupA(value, &valueW)))
    {
        CoTaskMemFree(subkeyW);
        return ERROR_OUTOFMEMORY;
    }

    ret = SHDeleteValueW(hkey, subkeyW, valueW);
    CoTaskMemFree(subkeyW);
    CoTaskMemFree(valueW);
    return ret;
}

/*************************************************************************
 * SHCopyKeyA        [SHCORE.@]
 */
DWORD WINAPI SHCopyKeyA(HKEY hkey_src, const char *subkey, HKEY hkey_dst, DWORD reserved)
{
    WCHAR *subkeyW = NULL;
    DWORD ret;

    TRACE("%p, %s, %p, %ld.\n", hkey_src, debugstr_a(subkey), hkey_dst, reserved);

    if (subkey && FAILED(SHStrDupA(subkey, &subkeyW)))
        return 0;

    ret = SHCopyKeyW(hkey_src, subkeyW, hkey_dst, reserved);
    CoTaskMemFree(subkeyW);
    return ret;
}

/*************************************************************************
 * SHCopyKeyW        [SHCORE.@]
 */
DWORD WINAPI SHCopyKeyW(HKEY hkey_src, const WCHAR *subkey, HKEY hkey_dst, DWORD reserved)
{
    DWORD key_count = 0, value_count = 0, max_key_len = 0;
    WCHAR name[MAX_PATH], *ptr_name = name;
    BYTE buff[1024], *ptr = buff;
    DWORD max_data_len = 0, i;
    DWORD ret = 0;

    TRACE("%p, %s, %p, %ld.\n", hkey_src, debugstr_w(subkey), hkey_dst, reserved);

    if (!hkey_dst || !hkey_src)
        return ERROR_INVALID_PARAMETER;

    if (subkey)
        ret = RegOpenKeyExW(hkey_src, subkey, 0, KEY_ALL_ACCESS, &hkey_src);

    if (ret)
        hkey_src = NULL; /* Don't close this key since we didn't open it */
    else
    {
        DWORD max_value_len;

        ret = RegQueryInfoKeyW(hkey_src, NULL, NULL, NULL, &key_count, &max_key_len,
                NULL, &value_count, &max_value_len, &max_data_len, NULL, NULL);
        if (!ret)
        {
            /* Get max size for key/value names */
            max_key_len = max(max_key_len, max_value_len);

            if (max_key_len++ > MAX_PATH - 1)
                ptr_name = malloc(max_key_len * sizeof(WCHAR));

            if (max_data_len > sizeof(buff))
                ptr = malloc(max_data_len);

            if (!ptr_name || !ptr)
                ret = ERROR_NOT_ENOUGH_MEMORY;
        }
    }

    for (i = 0; i < key_count && !ret; i++)
    {
        HKEY hsubkey_src, hsubkey_dst;
        DWORD length = max_key_len;

        ret = RegEnumKeyExW(hkey_src, i, ptr_name, &length, NULL, NULL, NULL, NULL);
        if (!ret)
        {
            ret = RegOpenKeyExW(hkey_src, ptr_name, 0, KEY_READ, &hsubkey_src);
            if (!ret)
            {
                /* Create destination sub key */
                ret = RegCreateKeyW(hkey_dst, ptr_name, &hsubkey_dst);
                if (!ret)
                {
                    /* Recursively copy keys and values from the sub key */
                    ret = SHCopyKeyW(hsubkey_src, NULL, hsubkey_dst, 0);
                    RegCloseKey(hsubkey_dst);
                }
            }
            RegCloseKey(hsubkey_src);
        }
    }

    /* Copy all the values in this key */
    for (i = 0; i < value_count && !ret; i++)
    {
        DWORD length = max_key_len, type, data_len = max_data_len;

        ret = RegEnumValueW(hkey_src, i, ptr_name, &length, NULL, &type, ptr, &data_len);
        if (!ret) {
            ret = SHSetValueW(hkey_dst, NULL, ptr_name, type, ptr, data_len);
        }
    }

    /* Free buffers if allocated */
    if (ptr_name != name)
        free(ptr_name);
    if (ptr != buff)
        free(ptr);

    if (subkey && hkey_src)
        RegCloseKey(hkey_src);

    return ret;
}


/*************************************************************************
 * SHEnumKeyExA        [SHCORE.@]
 */
LONG WINAPI SHEnumKeyExA(HKEY hkey, DWORD index, char *subkey, DWORD *length)
{
    TRACE("%p, %ld, %s, %p.\n", hkey, index, debugstr_a(subkey), length);

    return RegEnumKeyExA(hkey, index, subkey, length, NULL, NULL, NULL, NULL);
}

/*************************************************************************
 * SHEnumKeyExW        [SHCORE.@]
 */
LONG WINAPI SHEnumKeyExW(HKEY hkey, DWORD index, WCHAR *subkey, DWORD *length)
{
    TRACE("%p, %ld, %s, %p.\n", hkey, index, debugstr_w(subkey), length);

    return RegEnumKeyExW(hkey, index, subkey, length, NULL, NULL, NULL, NULL);
}

/*************************************************************************
 * SHEnumValueA        [SHCORE.@]
 */
LONG WINAPI SHEnumValueA(HKEY hkey, DWORD index, char *value, DWORD *length, DWORD *type,
        void *data, DWORD *data_len)
{
    TRACE("%p, %ld, %s, %p, %p, %p, %p.\n", hkey, index, debugstr_a(value), length, type, data, data_len);

    return RegEnumValueA(hkey, index, value, length, NULL, type, data, data_len);
}

/*************************************************************************
 * SHEnumValueW        [SHCORE.@]
 */
LONG WINAPI SHEnumValueW(HKEY hkey, DWORD index, WCHAR *value, DWORD *length, DWORD *type,
        void *data, DWORD *data_len)
{
    TRACE("%p, %ld, %s, %p, %p, %p, %p.\n", hkey, index, debugstr_w(value), length, type, data, data_len);

    return RegEnumValueW(hkey, index, value, length, NULL, type, data, data_len);
}

/*************************************************************************
 * SHQueryValueExW    [SHCORE.@]
 */
DWORD WINAPI SHQueryValueExW(HKEY hkey, const WCHAR *name, DWORD *reserved, DWORD *type,
        void *buff, DWORD *buff_len)
{
    DWORD ret, value_type, data_len = 0;

    TRACE("(%p, %s, %p, %p, %p, %p)\n", hkey, debugstr_w(name), reserved, type, buff, buff_len);

    if (buff_len)
        data_len = *buff_len;

    ret = RegQueryValueExW(hkey, name, reserved, &value_type, buff, &data_len);
    if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA)
        return ret;

    if (buff_len && value_type == REG_EXPAND_SZ)
    {
        DWORD length;
        WCHAR *value;

        if (!buff || ret == ERROR_MORE_DATA)
        {
            length = data_len;
            value = malloc(length);
            RegQueryValueExW(hkey, name, reserved, NULL, (BYTE *)value, &length);
            length = ExpandEnvironmentStringsW(value, NULL, 0);
        }
        else
        {
            length = (lstrlenW(buff) + 1) * sizeof(WCHAR);
            value = malloc(length);
            memcpy(value, buff, length);
            length = ExpandEnvironmentStringsW(value, buff, *buff_len / sizeof(WCHAR));
            if (length > *buff_len) ret = ERROR_MORE_DATA;
        }
        data_len = max(data_len, length);
        free(value);
    }

    if (type)
        *type = value_type == REG_EXPAND_SZ ? REG_SZ : value_type;
    if (buff_len)
        *buff_len = data_len;
    return ret;
}

/*************************************************************************
 * SHQueryValueExA    [SHCORE.@]
 */
DWORD WINAPI SHQueryValueExA(HKEY hkey, const char *name, DWORD *reserved, DWORD *type,
        void *buff, DWORD *buff_len)
{
    DWORD ret, value_type, data_len = 0;

    TRACE("(%p, %s, %p, %p, %p, %p)\n", hkey, debugstr_a(name), reserved, type, buff, buff_len);

    if (buff_len)
        data_len = *buff_len;

    ret = RegQueryValueExA(hkey, name, reserved, &value_type, buff, &data_len);
    if (ret != ERROR_SUCCESS && ret != ERROR_MORE_DATA)
        return ret;

    if (buff_len && value_type == REG_EXPAND_SZ)
    {
        DWORD length;
        char *value;

        if (!buff || ret == ERROR_MORE_DATA)
        {
            length = data_len;
            value = malloc(length);
            RegQueryValueExA(hkey, name, reserved, NULL, (BYTE *)value, &length);
            length = ExpandEnvironmentStringsA(value, NULL, 0);
        }
        else
        {
            length = strlen(buff) + 1;
            value = malloc(length);
            memcpy(value, buff, length);
            length = ExpandEnvironmentStringsA(value, buff, *buff_len);
            if (length > *buff_len) ret = ERROR_MORE_DATA;
        }
        data_len = max(data_len, length);
        free(value);
    }

    if (type)
        *type = value_type == REG_EXPAND_SZ ? REG_SZ : value_type;
    if (buff_len)
        *buff_len = data_len;
    return ret;
}

/*************************************************************************
 * SHGetValueA        [SHCORE.@]
 */
DWORD WINAPI SHGetValueA(HKEY hkey, const char *subkey, const char *value,
       DWORD *type, void *data, DWORD *data_len)
{
    HKEY hsubkey = 0;
    DWORD ret = 0;

    TRACE("(%p, %s, %s, %p, %p, %p)\n", hkey, debugstr_a(subkey), debugstr_a(value),
            type, data, data_len);

    if (subkey)
        ret = RegOpenKeyExA(hkey, subkey, 0, KEY_QUERY_VALUE, &hsubkey);

    if (!ret)
    {
        ret = SHQueryValueExA(hsubkey ? hsubkey : hkey, value, 0, type, data, data_len);
        if (subkey)
            RegCloseKey(hsubkey);
    }

    return ret;
}

/*************************************************************************
 * SHGetValueW        [SHCORE.@]
 */
DWORD WINAPI SHGetValueW(HKEY hkey, const WCHAR *subkey, const WCHAR *value,
        DWORD *type, void *data, DWORD *data_len)
{
    HKEY hsubkey = 0;
    DWORD ret = 0;

    TRACE("(%p, %s, %s, %p, %p, %p)\n", hkey, debugstr_w(subkey), debugstr_w(value),
            type, data, data_len);

    if (subkey)
        ret = RegOpenKeyExW(hkey, subkey, 0, KEY_QUERY_VALUE, &hsubkey);

    if (!ret)
    {
        ret = SHQueryValueExW(hsubkey ? hsubkey : hkey, value, 0, type, data, data_len);
        if (subkey)
            RegCloseKey(hsubkey);
    }

    return ret;
}

/*************************************************************************
 * SHRegGetIntW        [SHCORE.280]
 */
int WINAPI SHRegGetIntW(HKEY hkey, const WCHAR *value, int default_value)
{
    WCHAR buff[32];
    DWORD buff_len;

    TRACE("(%p, %s, %d)\n", hkey, debugstr_w(value), default_value);

    buff[0] = 0;
    buff_len = sizeof(buff);
    if (SHQueryValueExW(hkey, value, 0, 0, buff, &buff_len))
        return default_value;

    if (*buff >= '0' && *buff <= '9')
        return wcstol(buff, NULL, 10);

    return default_value;
}

/*************************************************************************
 * SHRegGetPathA        [SHCORE.@]
 */
DWORD WINAPI SHRegGetPathA(HKEY hkey, const char *subkey, const char *value, char *path, DWORD flags)
{
    DWORD length = MAX_PATH;

    TRACE("%p, %s, %s, %p, %#lx.\n", hkey, debugstr_a(subkey), debugstr_a(value), path, flags);

    return SHGetValueA(hkey, subkey, value, 0, path, &length);
}

/*************************************************************************
 * SHRegGetPathW        [SHCORE.@]
 */
DWORD WINAPI SHRegGetPathW(HKEY hkey, const WCHAR *subkey, const WCHAR *value, WCHAR *path, DWORD flags)
{
    DWORD length = MAX_PATH;

    TRACE("%p, %s, %s, %p, %#lx.\n", hkey, debugstr_w(subkey), debugstr_w(value), path, flags);

    return SHGetValueW(hkey, subkey, value, 0, path, &length);
}

/*************************************************************************
 * SHSetValueW       [SHCORE.@]
 */
DWORD WINAPI SHSetValueW(HKEY hkey, const WCHAR *subkey, const WCHAR *value, DWORD type,
        const void *data, DWORD data_len)
{
    DWORD ret = ERROR_SUCCESS, dummy;
    HKEY hsubkey;

    TRACE("%p, %s, %s, %ld, %p, %ld.\n", hkey, debugstr_w(subkey), debugstr_w(value),
            type, data, data_len);

    if (subkey && *subkey)
        ret = RegCreateKeyExW(hkey, subkey, 0, NULL, 0, KEY_SET_VALUE, NULL, &hsubkey, &dummy);
    else
        hsubkey = hkey;

    if (!ret)
    {
        ret = RegSetValueExW(hsubkey, value, 0, type, data, data_len);
        if (hsubkey != hkey)
            RegCloseKey(hsubkey);
    }

    return ret;
}

/*************************************************************************
 * SHSetValueA        [SHCORE.@]
 */
DWORD WINAPI SHSetValueA(HKEY hkey, const char *subkey, const char *value,
        DWORD type, const void *data, DWORD data_len)
{
    DWORD ret = ERROR_SUCCESS, dummy;
    HKEY hsubkey;

    TRACE("%p, %s, %s, %ld, %p, %ld.\n", hkey, debugstr_a(subkey), debugstr_a(value),
            type, data, data_len);

    if (subkey && *subkey)
        ret = RegCreateKeyExA(hkey, subkey, 0, NULL, 0, KEY_SET_VALUE, NULL, &hsubkey, &dummy);
    else
        hsubkey = hkey;

    if (!ret)
    {
        ret = RegSetValueExA(hsubkey, value, 0, type, data, data_len);
        if (hsubkey != hkey)
            RegCloseKey(hsubkey);
    }

    return ret;
}

/*************************************************************************
 * SHRegSetPathA       [SHCORE.@]
 */
DWORD WINAPI SHRegSetPathA(HKEY hkey, const char *subkey, const char *value, const char *path, DWORD flags)
{
    FIXME("%p, %s, %s, %s, %#lx - semi-stub\n", hkey, debugstr_a(subkey),
            debugstr_a(value), debugstr_a(path), flags);

    /* FIXME: PathUnExpandEnvStringsA() */

    return SHSetValueA(hkey, subkey, value, REG_SZ, path, lstrlenA(path));
}

/*************************************************************************
 * SHRegSetPathW       [SHCORE.@]
 */
DWORD WINAPI SHRegSetPathW(HKEY hkey, const WCHAR *subkey, const WCHAR *value, const WCHAR *path, DWORD flags)
{
    FIXME("%p, %s, %s, %s, %#lx semi-stub\n", hkey, debugstr_w(subkey),
            debugstr_w(value), debugstr_w(path), flags);

    /* FIXME: PathUnExpandEnvStringsW(); */

    return SHSetValueW(hkey, subkey, value, REG_SZ, path, lstrlenW(path));
}

/*************************************************************************
 * SHQueryInfoKeyA       [SHCORE.@]
 */
LONG WINAPI SHQueryInfoKeyA(HKEY hkey, DWORD *subkeys, DWORD *subkey_max, DWORD *values, DWORD *value_max)
{
    TRACE("(%p, %p, %p, %p, %p)\n", hkey, subkeys, subkey_max, values, value_max);

    return RegQueryInfoKeyA(hkey, NULL, NULL, NULL, subkeys, subkey_max, NULL, values, value_max, NULL, NULL, NULL);
}

/*************************************************************************
 * SHQueryInfoKeyW       [SHCORE.@]
 */
LONG WINAPI SHQueryInfoKeyW(HKEY hkey, DWORD *subkeys, DWORD *subkey_max, DWORD *values, DWORD *value_max)
{
    TRACE("(%p, %p, %p, %p, %p)\n", hkey, subkeys, subkey_max, values, value_max);

    return RegQueryInfoKeyW(hkey, NULL, NULL, NULL, subkeys, subkey_max, NULL, values, value_max, NULL, NULL, NULL);
}

/*************************************************************************
 * IsOS        [SHCORE.@]
 */
BOOL WINAPI IsOS(DWORD feature)
{
    DWORD platform, majorv, minorv;
    OSVERSIONINFOA osvi;

    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
    if (!GetVersionExA(&osvi))
        return FALSE;

    majorv = osvi.dwMajorVersion;
    minorv = osvi.dwMinorVersion;
    platform = osvi.dwPlatformId;

#define ISOS_RETURN(x) \
    TRACE("(%#lx) ret %d\n",feature,(x)); \
    return (x)

    switch(feature)  {
    case OS_WIN32SORGREATER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32s
                 || platform == VER_PLATFORM_WIN32_WINDOWS);
    case OS_NT:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT);
    case OS_WIN95ORGREATER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_WINDOWS);
    case OS_NT4ORGREATER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && majorv >= 4);
    case OS_WIN2000ORGREATER_ALT:
    case OS_WIN2000ORGREATER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && majorv >= 5);
    case OS_WIN98ORGREATER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_WINDOWS && minorv >= 10);
    case OS_WIN98_GOLD:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_WINDOWS && minorv == 10);
    case OS_WIN2000PRO:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && majorv >= 5);
    case OS_WIN2000SERVER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && (minorv == 0 || minorv == 1));
    case OS_WIN2000ADVSERVER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && (minorv == 0 || minorv == 1));
    case OS_WIN2000DATACENTER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && (minorv == 0 || minorv == 1));
    case OS_WIN2000TERMINAL:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && (minorv == 0 || minorv == 1));
    case OS_EMBEDDED:
        FIXME("(OS_EMBEDDED) What should we return here?\n");
        return FALSE;
    case OS_TERMINALCLIENT:
        FIXME("(OS_TERMINALCLIENT) What should we return here?\n");
        return FALSE;
    case OS_TERMINALREMOTEADMIN:
        FIXME("(OS_TERMINALREMOTEADMIN) What should we return here?\n");
        return FALSE;
    case OS_WIN95_GOLD:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_WINDOWS && minorv == 0);
    case OS_MEORGREATER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_WINDOWS && minorv >= 90);
    case OS_XPORGREATER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && majorv >= 5 && minorv >= 1);
    case OS_HOME:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && majorv >= 5 && minorv >= 1);
    case OS_PROFESSIONAL:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT);
    case OS_DATACENTER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT);
    case OS_ADVSERVER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && majorv >= 5);
    case OS_SERVER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT);
    case OS_TERMINALSERVER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT);
    case OS_PERSONALTERMINALSERVER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && minorv >= 1 && majorv >= 5);
    case OS_FASTUSERSWITCHING:
        FIXME("(OS_FASTUSERSWITCHING) What should we return here?\n");
        return TRUE;
    case OS_WELCOMELOGONUI:
        FIXME("(OS_WELCOMELOGONUI) What should we return here?\n");
        return FALSE;
    case OS_DOMAINMEMBER:
        FIXME("(OS_DOMAINMEMBER) What should we return here?\n");
        return TRUE;
    case OS_ANYSERVER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT);
    case OS_WOW6432:
        {
            BOOL is_wow64;
            IsWow64Process(GetCurrentProcess(), &is_wow64);
            return is_wow64;
        }
    case OS_WEBSERVER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT);
    case OS_SMALLBUSINESSSERVER:
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT);
    case OS_TABLETPC:
        FIXME("(OS_TABLETPC) What should we return here?\n");
        return FALSE;
    case OS_SERVERADMINUI:
        FIXME("(OS_SERVERADMINUI) What should we return here?\n");
        return FALSE;
    case OS_MEDIACENTER:
        FIXME("(OS_MEDIACENTER) What should we return here?\n");
        return FALSE;
    case OS_APPLIANCE:
        FIXME("(OS_APPLIANCE) What should we return here?\n");
        return FALSE;
    case 0x25: /*OS_VISTAORGREATER*/
        ISOS_RETURN(platform == VER_PLATFORM_WIN32_NT && majorv >= 6);
    }

#undef ISOS_RETURN

    WARN("(%#lx) unknown parameter\n", feature);

    return FALSE;
}

/*************************************************************************
 * SubscribeFeatureStateChangeNotification        [SHCORE.@]
 */
void WINAPI SubscribeFeatureStateChangeNotification(FEATURE_STATE_CHANGE_SUBSCRIPTION *subscription,
                                                    FEATURE_STATE_CHANGE_CALLBACK *callback, void *context)
{
    FIXME("(%p, %p, %p) stub\n", subscription, callback, context);
}

/*************************************************************************
 * GetFeatureEnabledState        [SHCORE.@]
 */
FEATURE_ENABLED_STATE WINAPI GetFeatureEnabledState(UINT32 feature, FEATURE_CHANGE_TIME change_time)
{
    FIXME("(%u, %u) stub\n", feature, change_time);
    return FEATURE_ENABLED_STATE_DEFAULT;
}

/*************************************************************************
 * RegisterScaleChangeEvent        [SHCORE.@]
 */
HRESULT WINAPI RegisterScaleChangeEvent(HANDLE handle, DWORD_PTR *cookie)
{
    FIXME("(%p, %p) stub\n", handle, cookie);
    return E_NOTIMPL;
}

/*************************************************************************
 * RegisterScaleChangeNotifications        [SHCORE.@]
 */
HRESULT WINAPI RegisterScaleChangeNotifications(DISPLAY_DEVICE_TYPE display_device, HWND hwnd, UINT msg, DWORD *cookie)
{
    FIXME("(%d, %p, %u, %p) stub\n", display_device, hwnd, msg, cookie);

    if (cookie) *cookie = 0;

    return E_NOTIMPL;
}

/*************************************************************************
 * CreateRandomAccessStreamOverStream        [SHCORE.@]
 */
HRESULT WINAPI CreateRandomAccessStreamOverStream(IStream *stream, BSOS_OPTIONS options, REFIID riid, void **ppv)
{
    FIXME("(%p, %d, %s, %p) stub\n", stream, options, debugstr_guid(riid), ppv);
    return E_NOTIMPL;
}
