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

#include <stdarg.h>
#include <stdio.h>
#include <mpg123.h>
#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "mmreg.h"
#define COBJMACROS
#include "objbase.h"
#include "dmo.h"
#include "rpcproxy.h"
#include "wmcodecdsp.h"
#include "mfapi.h"
#include "mferror.h"
#include "mftransform.h"
#include "wine/debug.h"

#include "initguid.h"
DEFINE_GUID(WMMEDIATYPE_Audio, 0x73647561,0x0000,0x0010,0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71);
DEFINE_GUID(WMMEDIASUBTYPE_MP3,0x00000055,0x0000,0x0010,0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71);
DEFINE_GUID(WMMEDIASUBTYPE_PCM,0x00000001,0x0000,0x0010,0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71);
DEFINE_GUID(WMFORMAT_WaveFormatEx,  0x05589f81,0xc356,0x11ce,0xbf,0x01,0x00,0xaa,0x00,0x55,0x59,0x5a);

WINE_DEFAULT_DEBUG_CHANNEL(mp3dmod);

struct mp3_decoder
{
    IUnknown IUnknown_inner;
    IMediaObject IMediaObject_iface;
    IMFTransform IMFTransform_iface;
    IUnknown *outer;
    LONG ref;
    mpg123_handle *mh;

    DMO_MEDIA_TYPE intype, outtype;
    BOOL intype_set, outtype_set;

    IMediaBuffer *buffer;
    REFERENCE_TIME timestamp;

    BOOL draining;
};

static inline struct mp3_decoder *impl_from_IUnknown(IUnknown *iface)
{
    return CONTAINING_RECORD(iface, struct mp3_decoder, IUnknown_inner);
}

static HRESULT WINAPI Unknown_QueryInterface(IUnknown *iface, REFIID iid, void **obj)
{
    struct mp3_decoder *This = impl_from_IUnknown(iface);

    TRACE("(%p)->(%s, %p)\n", This, debugstr_guid(iid), obj);

    if (IsEqualGUID(iid, &IID_IUnknown))
        *obj = &This->IUnknown_inner;
    else if (IsEqualGUID(iid, &IID_IMediaObject))
        *obj = &This->IMediaObject_iface;
    else if (IsEqualGUID(iid, &IID_IMFTransform))
        *obj = &This->IMFTransform_iface;
    else
    {
        FIXME("no interface for %s\n", debugstr_guid(iid));
        *obj = NULL;
        return E_NOINTERFACE;
    }

    IUnknown_AddRef((IUnknown *)*obj);
    return S_OK;
}

static ULONG WINAPI Unknown_AddRef(IUnknown *iface)
{
    struct mp3_decoder *This = impl_from_IUnknown(iface);
    ULONG refcount = InterlockedIncrement(&This->ref);

    TRACE("(%p) AddRef from %ld\n", This, refcount - 1);

    return refcount;
}

static ULONG WINAPI Unknown_Release(IUnknown *iface)
{
    struct mp3_decoder *This = impl_from_IUnknown(iface);
    ULONG refcount = InterlockedDecrement(&This->ref);

    TRACE("(%p) Release from %ld\n", This, refcount + 1);

    if (!refcount)
    {
        if (This->buffer)
            IMediaBuffer_Release(This->buffer);
        if (This->intype_set)
            MoFreeMediaType(&This->intype);
        MoFreeMediaType(&This->outtype);
        mpg123_delete(This->mh);
        free(This);
    }
    return refcount;
}

static const IUnknownVtbl Unknown_vtbl = {
    Unknown_QueryInterface,
    Unknown_AddRef,
    Unknown_Release,
};

static inline struct mp3_decoder *impl_from_IMediaObject(IMediaObject *iface)
{
    return CONTAINING_RECORD(iface, struct mp3_decoder, IMediaObject_iface);
}

static HRESULT WINAPI MediaObject_QueryInterface(IMediaObject *iface, REFIID iid, void **obj)
{
    struct mp3_decoder *This = impl_from_IMediaObject(iface);
    return IUnknown_QueryInterface(This->outer, iid, obj);
}

static ULONG WINAPI MediaObject_AddRef(IMediaObject *iface)
{
    struct mp3_decoder *This = impl_from_IMediaObject(iface);
    return IUnknown_AddRef(This->outer);
}

static ULONG WINAPI MediaObject_Release(IMediaObject *iface)
{
    struct mp3_decoder *This = impl_from_IMediaObject(iface);
    return IUnknown_Release(This->outer);
}

static HRESULT WINAPI MediaObject_GetStreamCount(IMediaObject *iface, DWORD *input, DWORD *output)
{
    TRACE("iface %p, input %p, output %p.\n", iface, input, output);

    *input = *output = 1;

    return S_OK;
}

static HRESULT WINAPI MediaObject_GetInputStreamInfo(IMediaObject *iface, DWORD index, DWORD *flags)
{
    TRACE("iface %p, index %lu, flags %p.\n", iface, index, flags);

    *flags = 0;

    return S_OK;
}

static HRESULT WINAPI MediaObject_GetOutputStreamInfo(IMediaObject *iface, DWORD index, DWORD *flags)
{
    TRACE("iface %p, index %lu, flags %p.\n", iface, index, flags);

    *flags = 0;

    return S_OK;
}

static HRESULT WINAPI MediaObject_GetInputType(IMediaObject *iface, DWORD index, DWORD type_index, DMO_MEDIA_TYPE *type)
{
    TRACE("iface %p, index %lu, type_index %lu, type %p.\n", iface, index, type_index, type);

    if (index)
        return DMO_E_INVALIDSTREAMINDEX;

    if (type_index)
        return DMO_E_NO_MORE_ITEMS;

    type->majortype = WMMEDIATYPE_Audio;
    type->subtype = WMMEDIASUBTYPE_MP3;
    type->formattype = GUID_NULL;
    type->pUnk = NULL;
    type->cbFormat = 0;
    type->pbFormat = NULL;

    return S_OK;
}

static HRESULT WINAPI MediaObject_GetOutputType(IMediaObject *iface, DWORD index, DWORD type_index, DMO_MEDIA_TYPE *type)
{
    struct mp3_decoder *dmo = impl_from_IMediaObject(iface);
    const WAVEFORMATEX *input_format;
    WAVEFORMATEX *format;

    TRACE("iface %p, index %lu, type_index %lu, type %p.\n", iface, index, type_index, type);

    if (index)
        return DMO_E_INVALIDSTREAMINDEX;

    if (!dmo->intype_set)
        return DMO_E_TYPE_NOT_SET;

    input_format = (WAVEFORMATEX *)dmo->intype.pbFormat;

    if (type_index >= (2 * input_format->nChannels))
        return DMO_E_NO_MORE_ITEMS;

    type->majortype = WMMEDIATYPE_Audio;
    type->subtype = WMMEDIASUBTYPE_PCM;
    type->formattype = WMFORMAT_WaveFormatEx;
    type->pUnk = NULL;
    type->cbFormat = sizeof(WAVEFORMATEX);
    if (!(type->pbFormat = CoTaskMemAlloc(sizeof(WAVEFORMATEX))))
        return E_OUTOFMEMORY;
    format = (WAVEFORMATEX *)type->pbFormat;
    format->wFormatTag = WAVE_FORMAT_PCM;
    format->nSamplesPerSec = input_format->nSamplesPerSec;
    format->nChannels = (type_index / 2) ? 1 : input_format->nChannels;
    format->wBitsPerSample = (type_index % 2) ? 8 : 16;
    format->nBlockAlign = format->nChannels * format->wBitsPerSample / 8;
    format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
    format->cbSize = 0;

    return S_OK;
}

static HRESULT WINAPI MediaObject_SetInputType(IMediaObject *iface, DWORD index, const DMO_MEDIA_TYPE *type, DWORD flags)
{
    struct mp3_decoder *dmo = impl_from_IMediaObject(iface);
    const WAVEFORMATEX *format;

    TRACE("iface %p, index %lu, type %p, flags %#lx.\n", iface, index, type, flags);

    if (index)
        return DMO_E_INVALIDSTREAMINDEX;

    if (flags & DMO_SET_TYPEF_CLEAR)
    {
        if (dmo->intype_set)
            MoFreeMediaType(&dmo->intype);
        dmo->intype_set = FALSE;
        return S_OK;
    }

    if (!IsEqualGUID(&type->majortype, &WMMEDIATYPE_Audio)
            || !IsEqualGUID(&type->subtype, &WMMEDIASUBTYPE_MP3)
            || !IsEqualGUID(&type->formattype, &WMFORMAT_WaveFormatEx))
        return DMO_E_TYPE_NOT_ACCEPTED;

    format = (WAVEFORMATEX *) type->pbFormat;

    if (!format->nChannels)
        return DMO_E_TYPE_NOT_ACCEPTED;

    if (!(flags & DMO_SET_TYPEF_TEST_ONLY))
    {
        if (dmo->intype_set)
            MoFreeMediaType(&dmo->intype);
        MoCopyMediaType(&dmo->intype, type);
        dmo->intype_set = TRUE;
    }

    return S_OK;
}

static HRESULT WINAPI MediaObject_SetOutputType(IMediaObject *iface, DWORD index, const DMO_MEDIA_TYPE *type, DWORD flags)
{
    struct mp3_decoder *This = impl_from_IMediaObject(iface);
    WAVEFORMATEX *format, *in_format;
    long enc;
    int err;

    TRACE("(%p)->(%ld, %p, %#lx)\n", iface, index, type, flags);

    if (index)
        return DMO_E_INVALIDSTREAMINDEX;

    if (!This->intype_set)
        return DMO_E_TYPE_NOT_SET;

    if (flags & DMO_SET_TYPEF_CLEAR)
    {
        MoFreeMediaType(&This->outtype);
        This->outtype_set = FALSE;
        return S_OK;
    }

    if (!IsEqualGUID(&type->formattype, &WMFORMAT_WaveFormatEx))
        return DMO_E_TYPE_NOT_ACCEPTED;

    format = (WAVEFORMATEX *)type->pbFormat;

    if (format->wBitsPerSample == 8)
        enc = MPG123_ENC_UNSIGNED_8;
    else if (format->wBitsPerSample == 16)
        enc = MPG123_ENC_SIGNED_16;
    else if (format->wBitsPerSample == 32)
        enc = MPG123_ENC_FLOAT_32;
    else
    {
        ERR("Cannot decode to bit depth %u.\n", format->wBitsPerSample);
        return DMO_E_TYPE_NOT_ACCEPTED;
    }

    if (format->nChannels * format->wBitsPerSample/8 != format->nBlockAlign
            || format->nSamplesPerSec * format->nBlockAlign != format->nAvgBytesPerSec)
        return E_INVALIDARG;

    in_format = (WAVEFORMATEX *)This->intype.pbFormat;

    if (format->nSamplesPerSec != in_format->nSamplesPerSec &&
            format->nSamplesPerSec*2 != in_format->nSamplesPerSec &&
            format->nSamplesPerSec*4 != in_format->nSamplesPerSec)
    {
        ERR("Cannot decode to %lu samples per second (input %lu).\n", format->nSamplesPerSec, in_format->nSamplesPerSec);
        return DMO_E_TYPE_NOT_ACCEPTED;
    }

    if (!(flags & DMO_SET_TYPEF_TEST_ONLY))
    {
        err = mpg123_format(This->mh, format->nSamplesPerSec, format->nChannels, enc);
        if (err != MPG123_OK)
        {
            ERR("Failed to set format: %u channels, %lu samples/sec, %u bits/sample.\n",
                format->nChannels, format->nSamplesPerSec, format->wBitsPerSample);
            return DMO_E_TYPE_NOT_ACCEPTED;
        }
        if (This->outtype_set)
            MoFreeMediaType(&This->outtype);
        MoCopyMediaType(&This->outtype, type);
        This->outtype_set = TRUE;
    }

    return S_OK;
}

static HRESULT WINAPI MediaObject_GetInputCurrentType(IMediaObject *iface, DWORD index, DMO_MEDIA_TYPE *type)
{
    struct mp3_decoder *dmo = impl_from_IMediaObject(iface);
    TRACE("(%p)->(%ld, %p)\n", iface, index, type);

    if (index)
        return DMO_E_INVALIDSTREAMINDEX;

    if (!dmo->intype_set)
        return DMO_E_TYPE_NOT_SET;

    MoCopyMediaType(type, &dmo->intype);

    return S_OK;
}

static HRESULT WINAPI MediaObject_GetOutputCurrentType(IMediaObject *iface, DWORD index, DMO_MEDIA_TYPE *type)
{
    struct mp3_decoder *dmo = impl_from_IMediaObject(iface);
    TRACE("(%p)->(%ld, %p)\n", iface, index, type);

    if (index)
        return DMO_E_INVALIDSTREAMINDEX;

    if (!dmo->outtype_set)
        return DMO_E_TYPE_NOT_SET;

    MoCopyMediaType(type, &dmo->outtype);

    return S_OK;
}

static HRESULT WINAPI MediaObject_GetInputSizeInfo(IMediaObject *iface,
        DWORD index, DWORD *size, DWORD *lookahead, DWORD *alignment)
{
    struct mp3_decoder *dmo = impl_from_IMediaObject(iface);

    TRACE("iface %p, index %lu, size %p, lookahead %p, alignment %p.\n", iface, index, size, lookahead, alignment);

    if (index)
        return DMO_E_INVALIDSTREAMINDEX;

    if (!dmo->intype_set || !dmo->outtype_set)
        return DMO_E_TYPE_NOT_SET;

    *size = 0;
    *alignment = 1;
    return S_OK;
}

static HRESULT WINAPI MediaObject_GetOutputSizeInfo(IMediaObject *iface, DWORD index, DWORD *size, DWORD *alignment)
{
    struct mp3_decoder *dmo = impl_from_IMediaObject(iface);

    TRACE("iface %p, index %lu, size %p, alignment %p.\n", iface, index, size, alignment);

    if (index)
        return DMO_E_INVALIDSTREAMINDEX;

    if (!dmo->intype_set || !dmo->outtype_set)
        return DMO_E_TYPE_NOT_SET;

    *size = 2 * 1152 * ((WAVEFORMATEX *)dmo->outtype.pbFormat)->wBitsPerSample / 8;
    *alignment = 1;
    return S_OK;
}

static HRESULT WINAPI MediaObject_GetInputMaxLatency(IMediaObject *iface, DWORD index, REFERENCE_TIME *latency)
{
    FIXME("(%p)->(%ld, %p) stub!\n", iface, index, latency);

    return E_NOTIMPL;
}

static HRESULT WINAPI MediaObject_SetInputMaxLatency(IMediaObject *iface, DWORD index, REFERENCE_TIME latency)
{
    FIXME("(%p)->(%ld, %s) stub!\n", iface, index, wine_dbgstr_longlong(latency));

    return E_NOTIMPL;
}

static HRESULT WINAPI MediaObject_Flush(IMediaObject *iface)
{
    struct mp3_decoder *dmo = impl_from_IMediaObject(iface);

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

    if (dmo->buffer)
        IMediaBuffer_Release(dmo->buffer);
    dmo->buffer = NULL;
    dmo->timestamp = 0;

    /* mpg123 doesn't give us a way to flush, so just close and reopen the feed. */
    mpg123_close(dmo->mh);
    mpg123_open_feed(dmo->mh);

    return S_OK;
}

static HRESULT WINAPI MediaObject_Discontinuity(IMediaObject *iface, DWORD index)
{
    TRACE("iface %p.\n", iface);

    return S_OK;
}

static HRESULT WINAPI MediaObject_AllocateStreamingResources(IMediaObject *iface)
{
    TRACE("(%p)->()\n", iface);

    return S_OK;
}

static HRESULT WINAPI MediaObject_FreeStreamingResources(IMediaObject *iface)
{
    TRACE("(%p)->()\n", iface);

    return S_OK;
}

static HRESULT WINAPI MediaObject_GetInputStatus(IMediaObject *iface, DWORD index, DWORD *flags)
{
    FIXME("(%p)->(%ld, %p) stub!\n", iface, index, flags);

    return E_NOTIMPL;
}

static HRESULT WINAPI MediaObject_ProcessInput(IMediaObject *iface, DWORD index,
    IMediaBuffer *buffer, DWORD flags, REFERENCE_TIME timestamp, REFERENCE_TIME timelength)
{
    struct mp3_decoder *This = impl_from_IMediaObject(iface);
    HRESULT hr;
    BYTE *data;
    DWORD len;
    int err;

    TRACE("(%p)->(%ld, %p, %#lx, %s, %s)\n", iface, index, buffer, flags,
          wine_dbgstr_longlong(timestamp), wine_dbgstr_longlong(timelength));

    if (index)
        return DMO_E_INVALIDSTREAMINDEX;

    if (This->buffer)
    {
        ERR("Already have a buffer.\n");
        return DMO_E_NOTACCEPTING;
    }

    IMediaBuffer_AddRef(buffer);
    This->buffer = buffer;

    hr = IMediaBuffer_GetBufferAndLength(buffer, &data, &len);
    if (FAILED(hr))
        return hr;

    err = mpg123_feed(This->mh, data, len);
    if (err != MPG123_OK)
    {
        ERR("mpg123_feed() failed: %s\n", mpg123_strerror(This->mh));
        return E_FAIL;
    }

    return S_OK;
}

static DWORD get_framesize(DMO_MEDIA_TYPE *type)
{
    WAVEFORMATEX *format = (WAVEFORMATEX *)type->pbFormat;
    return 1152 * format->nBlockAlign;
}

static REFERENCE_TIME get_frametime(DMO_MEDIA_TYPE *type)
{
    WAVEFORMATEX *format = (WAVEFORMATEX *)type->pbFormat;
    return (REFERENCE_TIME) 10000000 * 1152 / format->nSamplesPerSec;
}

static HRESULT WINAPI MediaObject_ProcessOutput(IMediaObject *iface, DWORD flags, DWORD count, DMO_OUTPUT_DATA_BUFFER *buffers, DWORD *status)
{
    struct mp3_decoder *This = impl_from_IMediaObject(iface);
    REFERENCE_TIME time = 0, frametime;
    DWORD len, maxlen, framesize;
    int got_data = 0;
    size_t written;
    HRESULT hr;
    BYTE *data;
    int err;

    TRACE("(%p)->(%#lx, %ld, %p, %p)\n", iface, flags, count, buffers, status);

    if (count > 1)
        FIXME("Multiple buffers not handled.\n");

    buffers[0].dwStatus = 0;

    if (!buffers[0].pBuffer)
    {
        while ((err = mpg123_read(This->mh, NULL, 0, &written)) == MPG123_NEW_FORMAT);
        if (err == MPG123_NEED_MORE)
            return S_OK;
        else if (err == MPG123_ERR)
            ERR("mpg123_read() failed: %s\n", mpg123_strerror(This->mh));
        else if (err != MPG123_OK)
            ERR("mpg123_read() returned %d\n", err);

        buffers[0].dwStatus = DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE;
        return S_OK;
    }

    if (!This->buffer)
        return S_FALSE;

    buffers[0].dwStatus |= DMO_OUTPUT_DATA_BUFFERF_SYNCPOINT;

    hr = IMediaBuffer_GetBufferAndLength(buffers[0].pBuffer, &data, &len);
    if (FAILED(hr)) return hr;

    hr = IMediaBuffer_GetMaxLength(buffers[0].pBuffer, &maxlen);
    if (FAILED(hr)) return hr;

    framesize = get_framesize(&This->outtype);
    frametime = get_frametime(&This->outtype);

    while (1)
    {
        if (maxlen - len < framesize)
        {
            buffers[0].dwStatus |= DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE;
            break;
        }

        while ((err = mpg123_read(This->mh, data + len, framesize, &written)) == MPG123_NEW_FORMAT);
        if (err == MPG123_NEED_MORE)
        {
            IMediaBuffer_Release(This->buffer);
            This->buffer = NULL;
            break;
        }
        else if (err == MPG123_ERR)
            ERR("mpg123_read() failed: %s\n", mpg123_strerror(This->mh));
        else if (err != MPG123_OK)
            ERR("mpg123_read() returned %d\n", err);
        if (written < framesize)
            ERR("short write: %Id/%lu\n", written, framesize);

        got_data = 1;

        len += framesize;
        hr = IMediaBuffer_SetLength(buffers[0].pBuffer, len);
        if (FAILED(hr)) return hr;

        time += frametime;
    }

    if (got_data)
    {
        buffers[0].dwStatus |= (DMO_OUTPUT_DATA_BUFFERF_TIME | DMO_OUTPUT_DATA_BUFFERF_TIMELENGTH);
        buffers[0].rtTimelength = time;
        buffers[0].rtTimestamp = This->timestamp;
        This->timestamp += time;
        return S_OK;
    }
    return S_FALSE;
}

static HRESULT WINAPI MediaObject_Lock(IMediaObject *iface, LONG lock)
{
    FIXME("(%p)->(%ld) stub!\n", iface, lock);

    return E_NOTIMPL;
}

static const IMediaObjectVtbl MediaObject_vtbl = {
    MediaObject_QueryInterface,
    MediaObject_AddRef,
    MediaObject_Release,
    MediaObject_GetStreamCount,
    MediaObject_GetInputStreamInfo,
    MediaObject_GetOutputStreamInfo,
    MediaObject_GetInputType,
    MediaObject_GetOutputType,
    MediaObject_SetInputType,
    MediaObject_SetOutputType,
    MediaObject_GetInputCurrentType,
    MediaObject_GetOutputCurrentType,
    MediaObject_GetInputSizeInfo,
    MediaObject_GetOutputSizeInfo,
    MediaObject_GetInputMaxLatency,
    MediaObject_SetInputMaxLatency,
    MediaObject_Flush,
    MediaObject_Discontinuity,
    MediaObject_AllocateStreamingResources,
    MediaObject_FreeStreamingResources,
    MediaObject_GetInputStatus,
    MediaObject_ProcessInput,
    MediaObject_ProcessOutput,
    MediaObject_Lock,
};

static HRESULT convert_dmo_to_mf_error(HRESULT dmo_error)
{
    switch (dmo_error)
    {
        case DMO_E_INVALIDSTREAMINDEX:
            return MF_E_INVALIDSTREAMNUMBER;

        case DMO_E_INVALIDTYPE:
        case DMO_E_TYPE_NOT_ACCEPTED:
            return MF_E_INVALIDMEDIATYPE;

        case DMO_E_TYPE_NOT_SET:
            return MF_E_TRANSFORM_TYPE_NOT_SET;

        case DMO_E_NOTACCEPTING:
            return MF_E_NOTACCEPTING;

        case DMO_E_NO_MORE_ITEMS:
            return MF_E_NO_MORE_TYPES;
    }

    return dmo_error;
}

static inline struct mp3_decoder *impl_from_IMFTransform(IMFTransform *iface)
{
    return CONTAINING_RECORD(iface, struct mp3_decoder, IMFTransform_iface);
}

static HRESULT WINAPI MFTransform_QueryInterface(IMFTransform *iface, REFIID iid, void **obj)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    return IUnknown_QueryInterface(decoder->outer, iid, obj);
}

static ULONG WINAPI MFTransform_AddRef(IMFTransform *iface)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    return IUnknown_AddRef(decoder->outer);
}

static ULONG WINAPI MFTransform_Release(IMFTransform *iface)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    return IUnknown_Release(decoder->outer);
}

static HRESULT WINAPI MFTransform_GetStreamLimits(IMFTransform *iface, DWORD *input_minimum,
        DWORD *input_maximum, DWORD *output_minimum, DWORD *output_maximum)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);

    TRACE("iface %p, input_minimum %p, input_maximum %p, output_minimum %p, output_maximum %p.\n",
            iface, input_minimum, input_maximum, output_minimum, output_maximum);
    IMediaObject_GetStreamCount(&decoder->IMediaObject_iface, input_minimum, output_minimum);
    *input_maximum = *input_minimum;
    *output_maximum = *output_minimum;
    return S_OK;
}

static HRESULT WINAPI MFTransform_GetStreamCount(IMFTransform *iface, DWORD *inputs, DWORD *outputs)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);

    TRACE("iface %p, inputs %p, outputs %p.\n", iface, inputs, outputs);
    IMediaObject_GetStreamCount(&decoder->IMediaObject_iface, inputs, outputs);
    return S_OK;
}

static HRESULT WINAPI MFTransform_GetStreamIDs(IMFTransform *iface, DWORD input_size, DWORD *inputs,
        DWORD output_size, DWORD *outputs)
{
    FIXME("iface %p, input_size %lu, inputs %p, output_size %lu, outputs %p stub!\n", iface,
            input_size, inputs, output_size, outputs);

    return E_NOTIMPL;
}

static HRESULT WINAPI MFTransform_GetInputStreamInfo(IMFTransform *iface, DWORD id, MFT_INPUT_STREAM_INFO *info)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    HRESULT hr;

    TRACE("iface %p, id %#lx, info %p.\n", iface, id, info);

    memset(info, 0, sizeof(*info));

    /* Doc says: If not implemented, assume zero latency. */
    if (FAILED(hr = IMediaObject_GetInputMaxLatency(&decoder->IMediaObject_iface, id, &info->hnsMaxLatency)) && hr != E_NOTIMPL)
        return convert_dmo_to_mf_error(hr);

    if (FAILED(hr = IMediaObject_GetInputStreamInfo(&decoder->IMediaObject_iface, id, &info->dwFlags)))
        return convert_dmo_to_mf_error(hr);

    if (FAILED(hr = IMediaObject_GetInputSizeInfo(&decoder->IMediaObject_iface, id, &info->cbSize, &info->cbMaxLookahead, &info->cbAlignment)))
        return convert_dmo_to_mf_error(hr);

    return S_OK;
}

static HRESULT WINAPI MFTransform_GetOutputStreamInfo(IMFTransform *iface, DWORD id, MFT_OUTPUT_STREAM_INFO *info)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    HRESULT hr;

    TRACE("iface %p, id %#lx, info %p.\n", iface, id, info);

    memset(info, 0, sizeof(*info));

    if (FAILED(hr = IMediaObject_GetOutputStreamInfo(&decoder->IMediaObject_iface, id, &info->dwFlags)))
        return convert_dmo_to_mf_error(hr);

    if (FAILED(hr = IMediaObject_GetOutputSizeInfo(&decoder->IMediaObject_iface, id, &info->cbSize, &info->cbAlignment)))
        return convert_dmo_to_mf_error(hr);

    return S_OK;
}

static HRESULT WINAPI MFTransform_GetAttributes(IMFTransform *iface, IMFAttributes **attributes)
{
    TRACE("iface %p, attributes %p.\n", iface, attributes);
    return E_NOTIMPL;
}

static HRESULT WINAPI MFTransform_GetInputStreamAttributes(IMFTransform *iface, DWORD id, IMFAttributes **attributes)
{
    TRACE("iface %p, id %#lx, attributes %p.\n", iface, id, attributes);
    return E_NOTIMPL;
}

static HRESULT WINAPI MFTransform_GetOutputStreamAttributes(IMFTransform *iface, DWORD id, IMFAttributes **attributes)
{
    TRACE("iface %p, id %#lx, attributes %p.\n", iface, id, attributes);
    return E_NOTIMPL;
}

static HRESULT WINAPI MFTransform_DeleteInputStream(IMFTransform *iface, DWORD id)
{
    TRACE("iface %p, id %#lx.\n", iface, id);
    return E_NOTIMPL;
}

static HRESULT WINAPI MFTransform_AddInputStreams(IMFTransform *iface, DWORD streams, DWORD *ids)
{
    TRACE("iface %p, streams %lu, ids %p.\n", iface, streams, ids);
    return E_NOTIMPL;
}

static HRESULT WINAPI MFTransform_GetInputAvailableType(IMFTransform *iface, DWORD id, DWORD index,
        IMFMediaType **type)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    DMO_MEDIA_TYPE pt = {0};
    HRESULT hr;

    TRACE("iface %p, id %#lx, index %#lx, type %p.\n", iface, id, index, type);

    if (FAILED(hr = IMediaObject_GetInputType(&decoder->IMediaObject_iface, id, index, &pt)))
        return convert_dmo_to_mf_error(hr);

    if (FAILED(hr = MFCreateMediaType(type)))
        return hr;

    hr = MFInitMediaTypeFromAMMediaType(*type, (AM_MEDIA_TYPE*)&pt);
    MoFreeMediaType(&pt);
    return hr;
}

static HRESULT WINAPI MFTransform_GetOutputAvailableType(IMFTransform *iface, DWORD id, DWORD index,
        IMFMediaType **type)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    UINT32 bps, block_align, num_channels;
    const WAVEFORMATEX *input_format;
    HRESULT hr;

    TRACE("iface %p, id %#lx, index %#lx, type %p.\n", iface, id, index, type);

    if (id)
        return MF_E_INVALIDSTREAMNUMBER;

    if (!decoder->intype_set)
        return MF_E_TRANSFORM_TYPE_NOT_SET;

    input_format = (WAVEFORMATEX *)decoder->intype.pbFormat;

    if (index >= 6 || (input_format->nChannels != 2 && index >= 3))
        return MF_E_NO_MORE_TYPES;

    if (FAILED(hr = MFCreateMediaType(type)))
        return hr;

    if (FAILED(hr = IMFMediaType_SetGUID(*type, &MF_MT_MAJOR_TYPE, &MFMediaType_Audio)))
        goto fail;

    if (FAILED(hr = IMFMediaType_SetGUID(*type, &MF_MT_SUBTYPE, index % 3 ? &MFAudioFormat_PCM : &MFAudioFormat_Float)))
        goto fail;

    if (index % 3 == 0)
        bps = 32;
    else if (index % 3 == 1)
        bps = 16;
    else
        bps = 8;

    num_channels = index / 3 ? 1 : input_format->nChannels;
    block_align = bps/8 * num_channels;

    if (FAILED(hr = IMFMediaType_SetUINT32(*type, &MF_MT_AUDIO_BITS_PER_SAMPLE, bps)))
        goto fail;

    if (FAILED(hr = IMFMediaType_SetUINT32(*type, &MF_MT_AUDIO_NUM_CHANNELS, num_channels)))
        goto fail;

    if (FAILED(hr = IMFMediaType_SetUINT32(*type, &MF_MT_AUDIO_SAMPLES_PER_SECOND, input_format->nSamplesPerSec)))
        goto fail;

    if (FAILED(hr = IMFMediaType_SetUINT32(*type, &MF_MT_AUDIO_AVG_BYTES_PER_SECOND, input_format->nSamplesPerSec * block_align)))
        goto fail;

    if (FAILED(hr = IMFMediaType_SetUINT32(*type, &MF_MT_AUDIO_BLOCK_ALIGNMENT, block_align)))
        goto fail;

    if (FAILED(hr = IMFMediaType_SetUINT32(*type, &MF_MT_ALL_SAMPLES_INDEPENDENT, 1)))
        goto fail;

    return S_OK;

fail:
    IMFMediaType_Release(*type);
    return hr;
}

static HRESULT WINAPI MFTransform_SetInputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    DMO_MEDIA_TYPE mt;
    HRESULT hr;

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

    if (!type)
        flags |= DMO_SET_TYPEF_CLEAR;
    else if (FAILED(hr = MFInitAMMediaTypeFromMFMediaType(type, GUID_NULL, (AM_MEDIA_TYPE*)&mt)))
        return hr;

    hr = IMediaObject_SetInputType(&decoder->IMediaObject_iface, id, &mt, flags);
    MoFreeMediaType(&mt);

    if (hr == S_FALSE)
        return MF_E_INVALIDMEDIATYPE;
    else if (FAILED(hr))
        return convert_dmo_to_mf_error(hr);

    return S_OK;
}

static HRESULT WINAPI MFTransform_SetOutputType(IMFTransform *iface, DWORD id, IMFMediaType *type, DWORD flags)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    DMO_MEDIA_TYPE mt;
    HRESULT hr;

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

    if (!type)
        flags |= DMO_SET_TYPEF_CLEAR;
    else if (FAILED(hr = MFInitAMMediaTypeFromMFMediaType(type, GUID_NULL, (AM_MEDIA_TYPE*)&mt)))
        return hr;

    hr = IMediaObject_SetOutputType(&decoder->IMediaObject_iface, id, &mt, flags);
    MoFreeMediaType(&mt);

    if (hr == S_FALSE)
        return MF_E_INVALIDMEDIATYPE;
    else if (FAILED(hr))
        return convert_dmo_to_mf_error(hr);

    return S_OK;
}

static HRESULT WINAPI MFTransform_GetInputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **out)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    DMO_MEDIA_TYPE mt = {0};
    HRESULT hr;

    TRACE("iface %p, id %#lx, out %p.\n", iface, id, out);

    if (FAILED(hr = IMediaObject_GetInputCurrentType(&decoder->IMediaObject_iface, id, &mt)))
        return convert_dmo_to_mf_error(hr);

    if (FAILED(hr = MFCreateMediaType(out)))
        return hr;

    hr = MFInitMediaTypeFromAMMediaType(*out, (AM_MEDIA_TYPE*)&mt);
    MoFreeMediaType(&mt);
    return hr;
}

static HRESULT WINAPI MFTransform_GetOutputCurrentType(IMFTransform *iface, DWORD id, IMFMediaType **out)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    DMO_MEDIA_TYPE mt = {0};
    HRESULT hr;

    TRACE("iface %p, id %#lx, out %p.\n", iface, id, out);

    if (FAILED(hr = IMediaObject_GetOutputCurrentType(&decoder->IMediaObject_iface, id, &mt)))
        return convert_dmo_to_mf_error(hr);

    if (FAILED(hr = MFCreateMediaType(out)))
        return hr;

    hr = MFInitMediaTypeFromAMMediaType(*out, (AM_MEDIA_TYPE*)&mt);
    MoFreeMediaType(&mt);
    return hr;
}

static HRESULT WINAPI MFTransform_GetInputStatus(IMFTransform *iface, DWORD id, DWORD *flags)
{

    FIXME("iface %p, id %#lx, flags %p stub!\n", iface, id, flags);

    return E_NOTIMPL;
}

static HRESULT WINAPI MFTransform_GetOutputStatus(IMFTransform *iface, DWORD *flags)
{
    FIXME("iface %p, flags %p stub!\n", iface, flags);
    return E_NOTIMPL;
}

static HRESULT WINAPI MFTransform_SetOutputBounds(IMFTransform *iface, LONGLONG lower, LONGLONG upper)
{
    FIXME("iface %p, lower %I64d, upper %I64d stub!\n", iface, lower, upper);
    return E_NOTIMPL;
}

static HRESULT WINAPI MFTransform_ProcessEvent(IMFTransform *iface, DWORD id, IMFMediaEvent *event)
{
    FIXME("iface %p, id %#lx, event %p stub!\n", iface, id, event);
    return E_NOTIMPL;
}

static HRESULT WINAPI MFTransform_ProcessMessage(IMFTransform *iface, MFT_MESSAGE_TYPE message, ULONG_PTR param)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    HRESULT hr;

    TRACE("iface %p, message %#x, param %p stub!\n", iface, message, (void *)param);

    switch(message)
    {
        case MFT_MESSAGE_COMMAND_FLUSH:
            if (FAILED(hr = IMediaObject_Flush(&decoder->IMediaObject_iface)))
                return convert_dmo_to_mf_error(hr);
            break;

        case MFT_MESSAGE_COMMAND_DRAIN:
            decoder->draining = TRUE;
            break;

        case MFT_MESSAGE_NOTIFY_BEGIN_STREAMING:
        case MFT_MESSAGE_NOTIFY_START_OF_STREAM:
            if (FAILED(hr = IMediaObject_AllocateStreamingResources(&decoder->IMediaObject_iface)))
                return convert_dmo_to_mf_error(hr);
            break;

        case MFT_MESSAGE_NOTIFY_END_OF_STREAM:
        case MFT_MESSAGE_NOTIFY_END_STREAMING:
            if (FAILED(hr = IMediaObject_FreeStreamingResources(&decoder->IMediaObject_iface)))
                return convert_dmo_to_mf_error(hr);
            break;

        default:
            FIXME("message %#x stub!\n", message);
            break;
    }

    return S_OK;
}

static HRESULT WINAPI MFTransform_ProcessInput(IMFTransform *iface, DWORD id, IMFSample *sample, DWORD flags)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    LONGLONG sample_time, duration;
    IMediaBuffer *dmo_buffer;
    IMFMediaBuffer *buffer;
    UINT32 discontinuity;
    HRESULT hr;

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

    if (decoder->draining)
        return MF_E_NOTACCEPTING;

    if (SUCCEEDED(IMFSample_GetUINT32(sample, &MFSampleExtension_Discontinuity, &discontinuity)) && discontinuity)
    {
        if (FAILED(hr = IMediaObject_Discontinuity(&decoder->IMediaObject_iface, id)))
            return convert_dmo_to_mf_error(hr);
    }

    if (FAILED(hr = IMFSample_ConvertToContiguousBuffer(sample, &buffer)))
        return hr;

    hr = MFCreateLegacyMediaBufferOnMFMediaBuffer(sample, buffer, 0, &dmo_buffer);
    IMFMediaBuffer_Release(buffer);
    if (FAILED(hr))
        return hr;

    flags = 0;

    if (SUCCEEDED(IMFSample_GetSampleTime(sample, &sample_time)))
        flags |= DMO_INPUT_DATA_BUFFERF_TIME;

    if (SUCCEEDED(IMFSample_GetSampleDuration(sample, &duration)))
        flags |= DMO_INPUT_DATA_BUFFERF_TIMELENGTH;

    hr = IMediaObject_ProcessInput(&decoder->IMediaObject_iface, id, dmo_buffer, flags, sample_time, duration);
    IMediaBuffer_Release(dmo_buffer);
    if (FAILED(hr))
        return convert_dmo_to_mf_error(hr);

    return S_OK;
}

static HRESULT WINAPI MFTransform_ProcessOutput(IMFTransform *iface, DWORD flags, DWORD count,
        MFT_OUTPUT_DATA_BUFFER *samples, DWORD *status)
{
    struct mp3_decoder *decoder = impl_from_IMFTransform(iface);
    DMO_OUTPUT_DATA_BUFFER *dmo_output;
    IMFMediaBuffer *buffer;
    HRESULT hr;
    int i;

    TRACE("iface %p, flags %#lx, count %lu, samples %p, status %p.\n", iface, flags, count, samples, status);

    if (count > 1)
    {
        FIXME("Multiple buffers not handled.\n");
        return E_INVALIDARG;
    }

    dmo_output = calloc(count, sizeof(*dmo_output));
    if (!dmo_output)
        return E_OUTOFMEMORY;

    for (i = 0; i < count; i++)
    {
        if (FAILED(hr = IMFSample_ConvertToContiguousBuffer(samples[i].pSample, &buffer)))
            goto fail;

        hr = MFCreateLegacyMediaBufferOnMFMediaBuffer(samples[i].pSample, buffer, 0, &dmo_output[i].pBuffer);
        IMFMediaBuffer_Release(buffer);
        if (FAILED(hr))
            goto fail;
    }

    hr = IMediaObject_ProcessOutput(&decoder->IMediaObject_iface, flags, count, dmo_output, status);

    for (i = 0; i < count; i++)
        IMediaBuffer_Release(dmo_output[i].pBuffer);

    if (FAILED(hr))
    {
        free(dmo_output);
        return convert_dmo_to_mf_error(hr);
    }

    for (i = 0; i < count; i++)
    {
        if (dmo_output[i].dwStatus & DMO_OUTPUT_DATA_BUFFERF_SYNCPOINT)
            IMFSample_SetUINT32(samples[i].pSample, &MFSampleExtension_CleanPoint, 1);

        if (dmo_output[i].dwStatus & DMO_OUTPUT_DATA_BUFFERF_TIME)
            IMFSample_SetSampleTime(samples[i].pSample, dmo_output[i].rtTimestamp);

        if (dmo_output[i].dwStatus & DMO_OUTPUT_DATA_BUFFERF_TIMELENGTH)
            IMFSample_SetSampleDuration(samples[i].pSample, dmo_output[i].rtTimelength);

        if (dmo_output[i].dwStatus & DMO_OUTPUT_DATA_BUFFERF_INCOMPLETE)
            samples[i].dwStatus |= MFT_OUTPUT_DATA_BUFFER_INCOMPLETE;
    }

    free(dmo_output);

    if (hr == S_FALSE)
    {
        decoder->draining = FALSE;
        return MF_E_TRANSFORM_NEED_MORE_INPUT;
    }

    return S_OK;

fail:
    for (i = 0; i < count; i++)
    {
        if (dmo_output[i].pBuffer)
            IMediaBuffer_Release(dmo_output[i].pBuffer);
    }
    free(dmo_output);

    return hr;
}

static const IMFTransformVtbl IMFTransform_vtbl =
{
    MFTransform_QueryInterface,
    MFTransform_AddRef,
    MFTransform_Release,
    MFTransform_GetStreamLimits,
    MFTransform_GetStreamCount,
    MFTransform_GetStreamIDs,
    MFTransform_GetInputStreamInfo,
    MFTransform_GetOutputStreamInfo,
    MFTransform_GetAttributes,
    MFTransform_GetInputStreamAttributes,
    MFTransform_GetOutputStreamAttributes,
    MFTransform_DeleteInputStream,
    MFTransform_AddInputStreams,
    MFTransform_GetInputAvailableType,
    MFTransform_GetOutputAvailableType,
    MFTransform_SetInputType,
    MFTransform_SetOutputType,
    MFTransform_GetInputCurrentType,
    MFTransform_GetOutputCurrentType,
    MFTransform_GetInputStatus,
    MFTransform_GetOutputStatus,
    MFTransform_SetOutputBounds,
    MFTransform_ProcessEvent,
    MFTransform_ProcessMessage,
    MFTransform_ProcessInput,
    MFTransform_ProcessOutput,
};

static HRESULT create_mp3_decoder(IUnknown *outer, REFIID iid, void **obj)
{
    struct mp3_decoder *This;
    HRESULT hr;
    int err;

    if (!(This = calloc(1, sizeof(*This))))
        return E_OUTOFMEMORY;

    This->IUnknown_inner.lpVtbl = &Unknown_vtbl;
    This->IMediaObject_iface.lpVtbl = &MediaObject_vtbl;
    This->IMFTransform_iface.lpVtbl = &IMFTransform_vtbl;
    This->ref = 1;
    This->outer = outer ? outer : &This->IUnknown_inner;

    mpg123_init();
    This->mh = mpg123_new(NULL, &err);
    mpg123_open_feed(This->mh);
    mpg123_format_none(This->mh);

    hr = IUnknown_QueryInterface(&This->IUnknown_inner, iid, obj);
    IUnknown_Release(&This->IUnknown_inner);
    return hr;
}

static HRESULT WINAPI ClassFactory_QueryInterface(IClassFactory *iface, REFIID iid, void **obj)
{
    TRACE("(%p, %s, %p)\n", iface, debugstr_guid(iid), obj);

    if (IsEqualGUID(&IID_IUnknown, iid) ||
        IsEqualGUID(&IID_IClassFactory, iid))
    {
        IClassFactory_AddRef(iface);
        *obj = iface;
        return S_OK;
    }

    *obj = NULL;
    WARN("no interface for %s\n", debugstr_guid(iid));
    return E_NOINTERFACE;
}

static ULONG WINAPI ClassFactory_AddRef(IClassFactory *iface)
{
    return 2;
}

static ULONG WINAPI ClassFactory_Release(IClassFactory *iface)
{
    return 1;
}

static HRESULT WINAPI ClassFactory_CreateInstance(IClassFactory *iface, IUnknown *outer, REFIID iid, void **obj)
{
    TRACE("(%p, %s, %p)\n", outer, debugstr_guid(iid), obj);

    if (outer && !IsEqualGUID(iid, &IID_IUnknown))
    {
        *obj = NULL;
        return E_NOINTERFACE;
    }

    return create_mp3_decoder(outer, iid, obj);
}

static HRESULT WINAPI ClassFactory_LockServer(IClassFactory *iface, BOOL lock)
{
    FIXME("(%d) stub\n", lock);
    return S_OK;
}

static const IClassFactoryVtbl classfactory_vtbl = {
    ClassFactory_QueryInterface,
    ClassFactory_AddRef,
    ClassFactory_Release,
    ClassFactory_CreateInstance,
    ClassFactory_LockServer
};

static IClassFactory mp3_decoder_cf = { &classfactory_vtbl };

/*************************************************************************
 *              DllGetClassObject (DSDMO.@)
 */
HRESULT WINAPI DllGetClassObject(REFCLSID clsid, REFIID iid, void **obj)
{
    TRACE("%s, %s, %p\n", debugstr_guid(clsid), debugstr_guid(iid), obj);

    if (IsEqualGUID(clsid, &CLSID_CMP3DecMediaObject))
        return IClassFactory_QueryInterface(&mp3_decoder_cf, iid, obj);

    FIXME("class %s not available\n", debugstr_guid(clsid));
    return CLASS_E_CLASSNOTAVAILABLE;
}

/***********************************************************************
 *              DllRegisterServer (DSDMO.@)
 */
HRESULT WINAPI DllRegisterServer(void)
{
    MFT_REGISTER_TYPE_INFO mf_in, mf_out;
    DMO_PARTIAL_MEDIATYPE in, out;
    HRESULT hr;

    in.type = WMMEDIATYPE_Audio;
    in.subtype = WMMEDIASUBTYPE_MP3;
    out.type = WMMEDIATYPE_Audio;
    out.subtype = WMMEDIASUBTYPE_PCM;
    mf_in.guidMajorType = MFMediaType_Audio;
    mf_in.guidSubtype = MFAudioFormat_MP3;
    mf_out.guidMajorType = MFMediaType_Audio;
    mf_out.guidSubtype = MFAudioFormat_PCM;
    hr = DMORegister(L"MP3 Decoder DMO", &CLSID_CMP3DecMediaObject, &DMOCATEGORY_AUDIO_DECODER,
        0, 1, &in, 1, &out);
    if (FAILED(hr)) return hr;
    hr = MFTRegister(CLSID_CMP3DecMediaObject, MFT_CATEGORY_AUDIO_DECODER, (LPWSTR) L"MP3 Decoder MFT",
        0, 1, &mf_in, 1, &mf_out, NULL);
    if (FAILED(hr)) return hr;

    return __wine_register_resources();
}

/***********************************************************************
 *              DllUnregisterServer (DSDMO.@)
 */
HRESULT WINAPI DllUnregisterServer(void)
{
    HRESULT hr;

    hr = DMOUnregister(&CLSID_CMP3DecMediaObject, &DMOCATEGORY_AUDIO_DECODER);
    if (FAILED(hr)) return hr;
    hr = MFTUnregister(CLSID_CMP3DecMediaObject);
    if (FAILED(hr)) return hr;

    return __wine_unregister_resources();
}
