/*
 * Copyright (C) 2003-2004 Rok Mandeljc
 * Copyright 2023 RĂ©mi Bernon for CodeWeavers
 *
 * This program 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 program 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 program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "dmloader_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(dmloader);
WINE_DECLARE_DEBUG_CHANNEL(dmfileraw);

struct loader_stream
{
    IStream IStream_iface;
    IDirectMusicGetLoader IDirectMusicGetLoader_iface;
    LONG ref;

    IStream *stream;
    IDirectMusicLoader *loader;
};

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

static HRESULT WINAPI loader_stream_QueryInterface(IStream *iface, REFIID riid, void **ret_iface)
{
    struct loader_stream *This = impl_from_IStream(iface);

    TRACE("(%p, %s, %p)\n", This, debugstr_dmguid(riid), ret_iface);

    if (IsEqualGUID(riid, &IID_IUnknown)
            || IsEqualGUID(riid, &IID_IStream))
    {
        IStream_AddRef(&This->IStream_iface);
        *ret_iface = &This->IStream_iface;
        return S_OK;
    }

    if (IsEqualGUID(riid, &IID_IDirectMusicGetLoader))
    {
        IDirectMusicGetLoader_AddRef(&This->IDirectMusicGetLoader_iface);
        *ret_iface = &This->IDirectMusicGetLoader_iface;
        return S_OK;
    }

    WARN("(%p, %s, %p): not found\n", iface, debugstr_dmguid(riid), ret_iface);
    *ret_iface = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI loader_stream_AddRef(IStream *iface)
{
    struct loader_stream *This = impl_from_IStream(iface);
    ULONG ref = InterlockedIncrement(&This->ref);
    TRACE("(%p): new ref = %lu\n", This, ref);
    return ref;
}

static ULONG WINAPI loader_stream_Release(IStream *iface)
{
    struct loader_stream *This = impl_from_IStream(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p): new ref = %lu\n", This, ref);

    if (!ref)
    {
        IDirectMusicLoader_Release(This->loader);
        IStream_Release(This->stream);
        free(This);
    }

    return ref;
}

static HRESULT WINAPI loader_stream_Read(IStream *iface, void *data, ULONG size, ULONG *ret_size)
{
    struct loader_stream *This = impl_from_IStream(iface);
    TRACE("(%p, %p, %#lx, %p)\n", This, data, size, ret_size);
    return IStream_Read(This->stream, data, size, ret_size);
}

static HRESULT WINAPI loader_stream_Write(IStream *iface, const void *data, ULONG size, ULONG *ret_size)
{
    struct loader_stream *This = impl_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI loader_stream_Seek(IStream *iface, LARGE_INTEGER offset, DWORD method, ULARGE_INTEGER *ret_offset)
{
    struct loader_stream *This = impl_from_IStream(iface);
    TRACE("(%p, %I64d, %#lx, %p)\n", This, offset.QuadPart, method, ret_offset);
    return IStream_Seek(This->stream, offset, method, ret_offset);
}

static HRESULT WINAPI loader_stream_SetSize(IStream *iface, ULARGE_INTEGER size)
{
    struct loader_stream *This = impl_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI loader_stream_CopyTo(IStream *iface, IStream *dest, ULARGE_INTEGER size,
        ULARGE_INTEGER *read_size, ULARGE_INTEGER *write_size)
{
    struct loader_stream *This = impl_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI loader_stream_Commit(IStream *iface, DWORD flags)
{
    struct loader_stream *This = impl_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI loader_stream_Revert(IStream *iface)
{
    struct loader_stream *This = impl_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI loader_stream_LockRegion(IStream *iface, ULARGE_INTEGER offset, ULARGE_INTEGER size, DWORD type)
{
    struct loader_stream *This = impl_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI loader_stream_UnlockRegion(IStream *iface, ULARGE_INTEGER offset,
        ULARGE_INTEGER size, DWORD type)
{
    struct loader_stream *This = impl_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI loader_stream_Stat(IStream *iface, STATSTG *stat, DWORD flags)
{
    struct loader_stream *This = impl_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI loader_stream_Clone(IStream *iface, IStream **ret_iface)
{
    struct loader_stream *This = impl_from_IStream(iface);
    IStream *stream;
    HRESULT hr;

    TRACE("(%p, %p)\n", This, ret_iface);

    if (SUCCEEDED(hr = IStream_Clone(This->stream, &stream)))
    {
        hr = loader_stream_create(This->loader, stream, ret_iface);
        IStream_Release(stream);
    }

    return hr;
}

static const IStreamVtbl loader_stream_vtbl =
{
    loader_stream_QueryInterface,
    loader_stream_AddRef,
    loader_stream_Release,
    loader_stream_Read,
    loader_stream_Write,
    loader_stream_Seek,
    loader_stream_SetSize,
    loader_stream_CopyTo,
    loader_stream_Commit,
    loader_stream_Revert,
    loader_stream_LockRegion,
    loader_stream_UnlockRegion,
    loader_stream_Stat,
    loader_stream_Clone,
};

static struct loader_stream *impl_from_IDirectMusicGetLoader(IDirectMusicGetLoader *iface)
{
    return CONTAINING_RECORD(iface, struct loader_stream, IDirectMusicGetLoader_iface);
}

static HRESULT WINAPI loader_stream_getter_QueryInterface(IDirectMusicGetLoader *iface, REFIID iid, void **out)
{
    struct loader_stream *This = impl_from_IDirectMusicGetLoader(iface);
    return IStream_QueryInterface(&This->IStream_iface, iid, out);
}

static ULONG WINAPI loader_stream_getter_AddRef(IDirectMusicGetLoader *iface)
{
    struct loader_stream *This = impl_from_IDirectMusicGetLoader(iface);
    return IStream_AddRef(&This->IStream_iface);
}

static ULONG WINAPI loader_stream_getter_Release(IDirectMusicGetLoader *iface)
{
    struct loader_stream *This = impl_from_IDirectMusicGetLoader(iface);
    return IStream_Release(&This->IStream_iface);
}

static HRESULT WINAPI loader_stream_getter_GetLoader(IDirectMusicGetLoader *iface, IDirectMusicLoader **ret_loader)
{
    struct loader_stream *This = impl_from_IDirectMusicGetLoader(iface);

    TRACE("(%p, %p)\n", This, ret_loader);

    *ret_loader = This->loader;
    IDirectMusicLoader_AddRef(This->loader);
    return S_OK;
}

static const IDirectMusicGetLoaderVtbl loader_stream_getter_vtbl =
{
    loader_stream_getter_QueryInterface,
    loader_stream_getter_AddRef,
    loader_stream_getter_Release,
    loader_stream_getter_GetLoader,
};

HRESULT loader_stream_create(IDirectMusicLoader *loader, IStream *stream,
        IStream **ret_iface)
{
    struct loader_stream *obj;

    *ret_iface = NULL;
    if (!(obj = calloc(1, sizeof(*obj)))) return E_OUTOFMEMORY;
    obj->IStream_iface.lpVtbl = &loader_stream_vtbl;
    obj->IDirectMusicGetLoader_iface.lpVtbl = &loader_stream_getter_vtbl;
    obj->ref = 1;

    obj->stream = stream;
    IStream_AddRef(stream);
    obj->loader = loader;
    IDirectMusicLoader_AddRef(loader);

    *ret_iface = &obj->IStream_iface;
    return S_OK;
}

struct file_stream
{
    IStream IStream_iface;
    LONG ref;

    WCHAR path[MAX_PATH];
    HANDLE file;
};

static struct file_stream *file_stream_from_IStream(IStream *iface)
{
    return CONTAINING_RECORD(iface, struct file_stream, IStream_iface);
}

static HRESULT WINAPI file_stream_QueryInterface(IStream *iface, REFIID riid, void **ret_iface)
{
    struct file_stream *This = file_stream_from_IStream(iface);

    TRACE("(%p, %s, %p)\n", This, debugstr_dmguid(riid), ret_iface);

    if (IsEqualGUID(riid, &IID_IUnknown)
            || IsEqualGUID(riid, &IID_IStream))
    {
        IStream_AddRef(&This->IStream_iface);
        *ret_iface = &This->IStream_iface;
        return S_OK;
    }

    WARN("(%p, %s, %p): not found\n", iface, debugstr_dmguid(riid), ret_iface);
    *ret_iface = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI file_stream_AddRef(IStream *iface)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    ULONG ref = InterlockedIncrement(&This->ref);
    TRACE("(%p): new ref = %lu\n", This, ref);
    return ref;
}

static ULONG WINAPI file_stream_Release(IStream *iface)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    ULONG ref = InterlockedDecrement(&This->ref);

    TRACE("(%p): new ref = %lu\n", This, ref);

    if (!ref)
    {
        CloseHandle(This->file);
        free(This);
    }

    return ref;
}

static HRESULT WINAPI file_stream_Read(IStream *iface, void *data, ULONG size, ULONG *ret_size)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    DWORD dummy;

    TRACE("(%p, %p, %#lx, %p)\n", This, data, size, ret_size);

    if (!ret_size) ret_size = &dummy;
    if (!ReadFile(This->file, data, size, ret_size, NULL)) return HRESULT_FROM_WIN32(GetLastError());
    return *ret_size == size ? S_OK : S_FALSE;
}

static HRESULT WINAPI file_stream_Write(IStream *iface, const void *data, ULONG size, ULONG *ret_size)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI file_stream_Seek(IStream *iface, LARGE_INTEGER offset, DWORD method, ULARGE_INTEGER *ret_offset)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    DWORD position;

    TRACE("(%p, %I64d, %#lx, %p)\n", This, offset.QuadPart, method, ret_offset);

    position = SetFilePointer(This->file, offset.u.LowPart, NULL, method);
    if (position == INVALID_SET_FILE_POINTER) return HRESULT_FROM_WIN32(GetLastError());
    if (ret_offset) ret_offset->QuadPart = position;
    return S_OK;
}

static HRESULT WINAPI file_stream_SetSize(IStream *iface, ULARGE_INTEGER size)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI file_stream_CopyTo(IStream *iface, IStream *dest, ULARGE_INTEGER size,
        ULARGE_INTEGER *read_size, ULARGE_INTEGER *write_size)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI file_stream_Commit(IStream *iface, DWORD flags)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI file_stream_Revert(IStream *iface)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI file_stream_LockRegion(IStream *iface, ULARGE_INTEGER offset, ULARGE_INTEGER size, DWORD type)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI file_stream_UnlockRegion(IStream *iface, ULARGE_INTEGER offset,
        ULARGE_INTEGER size, DWORD type)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI file_stream_Stat(IStream *iface, STATSTG *stat, DWORD flags)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    FIXME("(%p): stub\n", This);
    return E_NOTIMPL;
}

static HRESULT WINAPI file_stream_Clone(IStream *iface, IStream **ret_iface)
{
    struct file_stream *This = file_stream_from_IStream(iface);
    HRESULT hr;

    TRACE("(%p, %p)\n", This, ret_iface);

    if (SUCCEEDED(hr = file_stream_create(This->path, ret_iface)))
    {
        LARGE_INTEGER position = {0};
        position.LowPart = SetFilePointer(This->file, 0, NULL, SEEK_CUR);
        hr = IStream_Seek(*ret_iface, position, SEEK_SET, NULL);
    }

    return hr;
}

static const IStreamVtbl file_stream_vtbl =
{
    file_stream_QueryInterface,
    file_stream_AddRef,
    file_stream_Release,
    file_stream_Read,
    file_stream_Write,
    file_stream_Seek,
    file_stream_SetSize,
    file_stream_CopyTo,
    file_stream_Commit,
    file_stream_Revert,
    file_stream_LockRegion,
    file_stream_UnlockRegion,
    file_stream_Stat,
    file_stream_Clone,
};

HRESULT file_stream_create(const WCHAR *path, IStream **ret_iface)
{
    struct file_stream *stream;

    *ret_iface = NULL;
    if (!(stream = calloc(1, sizeof(*stream)))) return E_OUTOFMEMORY;
    stream->IStream_iface.lpVtbl = &file_stream_vtbl;
    stream->ref = 1;

    wcscpy(stream->path, path);
    stream->file = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
            OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (stream->file == INVALID_HANDLE_VALUE)
    {
        free(stream);
        return DMUS_E_LOADER_FAILEDOPEN;
    }

    *ret_iface = &stream->IStream_iface;
    return S_OK;
}

static ULONG WINAPI IDirectMusicLoaderResourceStream_IStream_AddRef (LPSTREAM iface);

/*****************************************************************************
 * IDirectMusicLoaderResourceStream implementation
 */
/* Custom : */

static void IDirectMusicLoaderResourceStream_Detach (LPSTREAM iface) {
	ICOM_THIS_MULTI(IDirectMusicLoaderResourceStream, StreamVtbl, iface);
	TRACE("(%p)\n", This);

	This->pbMemData = NULL;
	This->llMemLength = 0;
}

HRESULT WINAPI IDirectMusicLoaderResourceStream_Attach (LPSTREAM iface, LPBYTE pbMemData, LONGLONG llMemLength, LONGLONG llPos) {
	ICOM_THIS_MULTI(IDirectMusicLoaderResourceStream, StreamVtbl, iface);
    
	TRACE("(%p, %p, %s, %s)\n", This, pbMemData, wine_dbgstr_longlong(llMemLength), wine_dbgstr_longlong(llPos));
	if (!pbMemData || !llMemLength) {
		WARN(": invalid pbMemData or llMemLength\n");
		return E_FAIL;
	}
	IDirectMusicLoaderResourceStream_Detach (iface);
	This->pbMemData = pbMemData;
	This->llMemLength = llMemLength;
	This->llPos = llPos;
	
    return S_OK;
}


/* IUnknown/IStream part: */
static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_QueryInterface (LPSTREAM iface, REFIID riid, void** ppobj) {
	ICOM_THIS_MULTI(IDirectMusicLoaderResourceStream, StreamVtbl, iface);
	
	TRACE("(%p, %s, %p)\n", This, debugstr_dmguid(riid), ppobj);
	if (IsEqualIID (riid, &IID_IUnknown) ||
		IsEqualIID (riid, &IID_IStream)) {
		*ppobj = &This->StreamVtbl;
		IDirectMusicLoaderResourceStream_IStream_AddRef ((LPSTREAM)&This->StreamVtbl);
		return S_OK;
	}

	WARN(": not found\n");
	return E_NOINTERFACE;
}

static ULONG WINAPI IDirectMusicLoaderResourceStream_IStream_AddRef (LPSTREAM iface) {
	ICOM_THIS_MULTI(IDirectMusicLoaderResourceStream, StreamVtbl, iface);
	TRACE("(%p): AddRef from %ld\n", This, This->dwRef);
	return InterlockedIncrement (&This->dwRef);
}

static ULONG WINAPI IDirectMusicLoaderResourceStream_IStream_Release (LPSTREAM iface) {
	ICOM_THIS_MULTI(IDirectMusicLoaderResourceStream, StreamVtbl, iface);
	
	DWORD dwRef = InterlockedDecrement (&This->dwRef);
	TRACE("(%p): ReleaseRef to %ld\n", This, dwRef);
	if (dwRef == 0) {
		IDirectMusicLoaderResourceStream_Detach (iface);
		free(This);
	}
	
	return dwRef;
}

static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_Read (LPSTREAM iface, void* pv, ULONG cb, ULONG* pcbRead) {
	LPBYTE pByte;
	ICOM_THIS_MULTI(IDirectMusicLoaderResourceStream, StreamVtbl, iface);
	
	TRACE_(dmfileraw)("(%p, %p, %#lx, %p)\n", This, pv, cb, pcbRead);
	if ((This->llPos + cb) > This->llMemLength) {
		WARN_(dmfileraw)(": requested size out of range\n");
		return E_FAIL;
	}
	
	pByte = &This->pbMemData[This->llPos];
	memcpy (pv, pByte, cb);
	This->llPos += cb; /* move pointer */
	/* FIXME: error checking would be nice */
	if (pcbRead) *pcbRead = cb;
	
	TRACE_(dmfileraw)(": data (size = %#lx): %s\n", cb, debugstr_an(pv, cb));
    return S_OK;
}

static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_Seek (LPSTREAM iface, LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER* plibNewPosition) {
	ICOM_THIS_MULTI(IDirectMusicLoaderResourceStream, StreamVtbl, iface);	
	TRACE_(dmfileraw)("(%p, %s, %s, %p)\n", This, wine_dbgstr_longlong(dlibMove.QuadPart), resolve_STREAM_SEEK(dwOrigin), plibNewPosition);
	
	switch (dwOrigin) {
		case STREAM_SEEK_CUR: {
			if ((This->llPos + dlibMove.QuadPart) > This->llMemLength) {
				WARN_(dmfileraw)(": requested offset out of range\n");
				return E_FAIL;
			}
			break;
		}
		case STREAM_SEEK_SET: {
			if (dlibMove.QuadPart > This->llMemLength) {
				WARN_(dmfileraw)(": requested offset out of range\n");
				return E_FAIL;
			}
			/* set to the beginning of the stream */
			This->llPos = 0;
			break;
		}
		case STREAM_SEEK_END: {
			/* TODO: check if this is true... I do think offset should be negative in this case */
			if (dlibMove.QuadPart > 0) {
				WARN_(dmfileraw)(": requested offset out of range\n");
				return E_FAIL;
			}
			/* set to the end of the stream */
			This->llPos = This->llMemLength;
			break;
		}
		default: {
			ERR_(dmfileraw)(": invalid dwOrigin\n");
			return E_FAIL;
		}
	}
	/* now simply add */
	This->llPos += dlibMove.QuadPart;

	if (plibNewPosition) plibNewPosition->QuadPart = This->llPos;
    	
    return S_OK;
}

static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_Clone (LPSTREAM iface, IStream** ppstm) {
	ICOM_THIS_MULTI(IDirectMusicLoaderResourceStream, StreamVtbl, iface);
	LPSTREAM pOther = NULL;
	HRESULT result;

	TRACE("(%p, %p)\n", iface, ppstm);
	result = DMUSIC_CreateDirectMusicLoaderResourceStream ((LPVOID*)&pOther);
	if (FAILED(result)) return result;
	
	IDirectMusicLoaderResourceStream_Attach (pOther, This->pbMemData, This->llMemLength, This->llPos);

	TRACE(": succeeded\n");
	*ppstm = pOther;
	return S_OK;
}

static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_Write (LPSTREAM iface, const void* pv, ULONG cb, ULONG* pcbWritten) {
	ERR(": should not be needed\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_SetSize (LPSTREAM iface, ULARGE_INTEGER libNewSize) {
	ERR(": should not be needed\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_CopyTo (LPSTREAM iface, IStream* pstm, ULARGE_INTEGER cb, ULARGE_INTEGER* pcbRead, ULARGE_INTEGER* pcbWritten) {
	ERR(": should not be needed\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_Commit (LPSTREAM iface, DWORD grfCommitFlags) {
	ERR(": should not be needed\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_Revert (LPSTREAM iface) {
	ERR(": should not be needed\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_LockRegion (LPSTREAM iface, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) {
	ERR(": should not be needed\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_UnlockRegion (LPSTREAM iface, ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType) {
	ERR(": should not be needed\n");
    return E_NOTIMPL;
}

static HRESULT WINAPI IDirectMusicLoaderResourceStream_IStream_Stat (LPSTREAM iface, STATSTG* pstatstg, DWORD grfStatFlag) {
	ERR(": should not be needed\n");
    return E_NOTIMPL;
}

static const IStreamVtbl DirectMusicLoaderResourceStream_Stream_Vtbl = {
	IDirectMusicLoaderResourceStream_IStream_QueryInterface,
	IDirectMusicLoaderResourceStream_IStream_AddRef,
	IDirectMusicLoaderResourceStream_IStream_Release,
	IDirectMusicLoaderResourceStream_IStream_Read,
	IDirectMusicLoaderResourceStream_IStream_Write,
	IDirectMusicLoaderResourceStream_IStream_Seek,
	IDirectMusicLoaderResourceStream_IStream_SetSize,
	IDirectMusicLoaderResourceStream_IStream_CopyTo,
	IDirectMusicLoaderResourceStream_IStream_Commit,
	IDirectMusicLoaderResourceStream_IStream_Revert,
	IDirectMusicLoaderResourceStream_IStream_LockRegion,
	IDirectMusicLoaderResourceStream_IStream_UnlockRegion,
	IDirectMusicLoaderResourceStream_IStream_Stat,
	IDirectMusicLoaderResourceStream_IStream_Clone
};

HRESULT DMUSIC_CreateDirectMusicLoaderResourceStream (void** ppobj) {
	IDirectMusicLoaderResourceStream *obj;

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

	*ppobj = NULL;
	if (!(obj = calloc(1, sizeof(*obj)))) return E_OUTOFMEMORY;
	obj->StreamVtbl = &DirectMusicLoaderResourceStream_Stream_Vtbl;
	obj->dwRef = 0; /* will be inited with QueryInterface */

	return IDirectMusicLoaderResourceStream_IStream_QueryInterface ((LPSTREAM)&obj->StreamVtbl, &IID_IStream, ppobj);
}