/* DirectPlay Conformance Tests
 *
 * Copyright 2007 - Alessandro Pignotti
 *
 * 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
 */

#define COBJMACROS
#include "wine/test.h"
#include <stdio.h>
#define INITGUID
#include <dplay.h>
#include <dplobby.h>
#include <netfw.h>

static HRESULT (WINAPI *pDirectPlayEnumerateA)( LPDPENUMDPCALLBACKA, void* );
static HRESULT (WINAPI *pDirectPlayEnumerateW)( LPDPENUMDPCALLBACKW, void* );
static HRESULT (WINAPI *pDirectPlayCreate)( GUID *GUID, LPDIRECTPLAY *lplpDP, IUnknown *pUnk );

#define check(expected, result)                 \
    ok( (expected) == (result),                 \
        "expected=%d got=%d\n",                 \
        (int)(expected), (int)(result) );
#define checkLP(expected, result)               \
    ok( (expected) == (result),                 \
        "expected=%p got=%p\n",                 \
        expected, result );
#define checkHR(expected, result)                       \
    ok( (expected) == (result),                         \
        "expected=%s got=%s\n",                         \
        dpResult2str(expected), dpResult2str(result) );
#define checkStr(expected, result)                              \
    ok( (result != NULL) && (!strcmp(expected, result)),        \
        "expected=%s got=%s\n",                                 \
        expected, result );
#define checkFlags(expected, result, flags)     \
    ok( (expected) == (result),                 \
        "expected=0x%08x(%s) got=0x%08x(%s)\n", \
        expected, dwFlags2str(expected, flags), \
        result, dwFlags2str(result, flags) );
#define checkGuid(expected, result)             \
    ok( IsEqualGUID(expected, result),          \
        "expected=%s got=%s\n",                 \
        Guid2str(expected), Guid2str(result) );
#define checkConv(expected, result, function)   \
    ok( (expected) == (result),                 \
        "expected=0x%08x(%s) got=0x%08x(%s)\n", \
        expected, function(expected),           \
        result, function(result) );


DEFINE_GUID(appGuid, 0xbdcfe03e, 0xf0ec, 0x415b, 0x82, 0x11, 0x6f, 0x86, 0xd8, 0x19, 0x7f, 0xe1);
DEFINE_GUID(appGuid2, 0x93417d3f, 0x7d26, 0x46ba, 0xb5, 0x76, 0xfe, 0x4b, 0x20, 0xbb, 0xad, 0x70);
DEFINE_GUID(GUID_NULL,0,0,0,0,0,0,0,0,0,0,0);
DEFINE_GUID(invalid_guid, 0x7b48b707, 0x0034, 0xc000, 0x02, 0x00, 0x00, 0x00, 0xec, 0xf6, 0x32, 0x00);


typedef struct tagCallbackData
{
    IDirectPlay4 *pDP;
    UINT dwCounter1, dwCounter2;
    DWORD dwFlags;
    char szTrace1[1024], szTrace2[1024];
    DPID *dpid;
    UINT dpidSize;
} CallbackData, *lpCallbackData;

struct provider_data
{
    int call_count;
    GUID *guid_ptr[10];
    GUID guid_data[10];
    BOOL ret_value;
};

static LPSTR get_temp_buffer(void)
{
    static UINT index = 0;
    static char buff[10][256];

    index = (index + 1) % 10;
    *buff[index] = 0;

    return buff[index];
}


static LPCSTR Guid2str(const GUID *guid)
{
    if (!guid) return "(null)";

    /* Service providers */
    if (IsEqualGUID(guid, &DPSPGUID_IPX))
        return "DPSPGUID_IPX";
    if (IsEqualGUID(guid, &DPSPGUID_TCPIP))
        return "DPSPGUID_TCPIP";
    if (IsEqualGUID(guid, &DPSPGUID_SERIAL))
        return "DPSPGUID_SERIAL";
    if (IsEqualGUID(guid, &DPSPGUID_MODEM))
        return "DPSPGUID_MODEM";
    /* DirectPlay Address IDs */
    if (IsEqualGUID(guid, &DPAID_TotalSize))
        return "DPAID_TotalSize";
    if (IsEqualGUID(guid, &DPAID_ServiceProvider))
        return "DPAID_ServiceProvider";
    if (IsEqualGUID(guid, &DPAID_LobbyProvider))
        return "DPAID_LobbyProvider";
    if (IsEqualGUID(guid, &DPAID_Phone))
        return "DPAID_Phone";
    if (IsEqualGUID(guid, &DPAID_PhoneW))
        return "DPAID_PhoneW";
    if (IsEqualGUID(guid, &DPAID_Modem))
        return "DPAID_Modem";
    if (IsEqualGUID(guid, &DPAID_ModemW))
        return "DPAID_ModemW";
    if (IsEqualGUID(guid, &DPAID_INet))
        return "DPAID_INet";
    if (IsEqualGUID(guid, &DPAID_INetW))
        return "DPAID_INetW";
    if (IsEqualGUID(guid, &DPAID_INetPort))
        return "DPAID_INetPort";
    if (IsEqualGUID(guid, &DPAID_ComPort))
        return "DPAID_ComPort";

    return wine_dbgstr_guid(guid);
}


static LPCSTR dpResult2str(HRESULT hr)
{
    switch (hr)
    {
    case DP_OK:                          return "DP_OK";
    case DPERR_ALREADYINITIALIZED:       return "DPERR_ALREADYINITIALIZED";
    case DPERR_ACCESSDENIED:             return "DPERR_ACCESSDENIED";
    case DPERR_ACTIVEPLAYERS:            return "DPERR_ACTIVEPLAYERS";
    case DPERR_BUFFERTOOSMALL:           return "DPERR_BUFFERTOOSMALL";
    case DPERR_CANTADDPLAYER:            return "DPERR_CANTADDPLAYER";
    case DPERR_CANTCREATEGROUP:          return "DPERR_CANTCREATEGROUP";
    case DPERR_CANTCREATEPLAYER:         return "DPERR_CANTCREATEPLAYER";
    case DPERR_CANTCREATESESSION:        return "DPERR_CANTCREATESESSION";
    case DPERR_CAPSNOTAVAILABLEYET:      return "DPERR_CAPSNOTAVAILABLEYET";
    case DPERR_EXCEPTION:                return "DPERR_EXCEPTION";
    case DPERR_GENERIC:                  return "DPERR_GENERIC";
    case DPERR_INVALIDFLAGS:             return "DPERR_INVALIDFLAGS";
    case DPERR_INVALIDOBJECT:            return "DPERR_INVALIDOBJECT";
    case DPERR_INVALIDPARAMS:            return "DPERR_INVALIDPARAMS";
        /*           symbol with the same value: DPERR_INVALIDPARAM */
    case DPERR_INVALIDPLAYER:            return "DPERR_INVALIDPLAYER";
    case DPERR_INVALIDGROUP:             return "DPERR_INVALIDGROUP";
    case DPERR_NOCAPS:                   return "DPERR_NOCAPS";
    case DPERR_NOCONNECTION:             return "DPERR_NOCONNECTION";
    case DPERR_NOMEMORY:                 return "DPERR_NOMEMORY";
        /*           symbol with the same value: DPERR_OUTOFMEMORY */
    case DPERR_NOMESSAGES:               return "DPERR_NOMESSAGES";
    case DPERR_NONAMESERVERFOUND:        return "DPERR_NONAMESERVERFOUND";
    case DPERR_NOPLAYERS:                return "DPERR_NOPLAYERS";
    case DPERR_NOSESSIONS:               return "DPERR_NOSESSIONS";
    case DPERR_PENDING:                  return "DPERR_PENDING";
    case DPERR_SENDTOOBIG:               return "DPERR_SENDTOOBIG";
    case DPERR_TIMEOUT:                  return "DPERR_TIMEOUT";
    case DPERR_UNAVAILABLE:              return "DPERR_UNAVAILABLE";
    case DPERR_UNSUPPORTED:              return "DPERR_UNSUPPORTED";
    case DPERR_BUSY:                     return "DPERR_BUSY";
    case DPERR_USERCANCEL:               return "DPERR_USERCANCEL";
    case DPERR_NOINTERFACE:              return "DPERR_NOINTERFACE";
    case DPERR_CANNOTCREATESERVER:       return "DPERR_CANNOTCREATESERVER";
    case DPERR_PLAYERLOST:               return "DPERR_PLAYERLOST";
    case DPERR_SESSIONLOST:              return "DPERR_SESSIONLOST";
    case DPERR_UNINITIALIZED:            return "DPERR_UNINITIALIZED";
    case DPERR_NONEWPLAYERS:             return "DPERR_NONEWPLAYERS";
    case DPERR_INVALIDPASSWORD:          return "DPERR_INVALIDPASSWORD";
    case DPERR_CONNECTING:               return "DPERR_CONNECTING";
    case DPERR_CONNECTIONLOST:           return "DPERR_CONNECTIONLOST";
    case DPERR_UNKNOWNMESSAGE:           return "DPERR_UNKNOWNMESSAGE";
    case DPERR_CANCELFAILED:             return "DPERR_CANCELFAILED";
    case DPERR_INVALIDPRIORITY:          return "DPERR_INVALIDPRIORITY";
    case DPERR_NOTHANDLED:               return "DPERR_NOTHANDLED";
    case DPERR_CANCELLED:                return "DPERR_CANCELLED";
    case DPERR_ABORTED:                  return "DPERR_ABORTED";
    case DPERR_BUFFERTOOLARGE:           return "DPERR_BUFFERTOOLARGE";
    case DPERR_CANTCREATEPROCESS:        return "DPERR_CANTCREATEPROCESS";
    case DPERR_APPNOTSTARTED:            return "DPERR_APPNOTSTARTED";
    case DPERR_INVALIDINTERFACE:         return "DPERR_INVALIDINTERFACE";
    case DPERR_NOSERVICEPROVIDER:        return "DPERR_NOSERVICEPROVIDER";
    case DPERR_UNKNOWNAPPLICATION:       return "DPERR_UNKNOWNAPPLICATION";
    case DPERR_NOTLOBBIED:               return "DPERR_NOTLOBBIED";
    case DPERR_SERVICEPROVIDERLOADED:    return "DPERR_SERVICEPROVIDERLOADED";
    case DPERR_ALREADYREGISTERED:        return "DPERR_ALREADYREGISTERED";
    case DPERR_NOTREGISTERED:            return "DPERR_NOTREGISTERED";
    case DPERR_AUTHENTICATIONFAILED:     return "DPERR_AUTHENTICATIONFAILED";
    case DPERR_CANTLOADSSPI:             return "DPERR_CANTLOADSSPI";
    case DPERR_ENCRYPTIONFAILED:         return "DPERR_ENCRYPTIONFAILED";
    case DPERR_SIGNFAILED:               return "DPERR_SIGNFAILED";
    case DPERR_CANTLOADSECURITYPACKAGE:  return "DPERR_CANTLOADSECURITYPACKAGE";
    case DPERR_ENCRYPTIONNOTSUPPORTED:   return "DPERR_ENCRYPTIONNOTSUPPORTED";
    case DPERR_CANTLOADCAPI:             return "DPERR_CANTLOADCAPI";
    case DPERR_NOTLOGGEDIN:              return "DPERR_NOTLOGGEDIN";
    case DPERR_LOGONDENIED:              return "DPERR_LOGONDENIED";
    case CLASS_E_NOAGGREGATION:          return "CLASS_E_NOAGGREGATION";

    default:
    {
        LPSTR buffer = get_temp_buffer();
        sprintf( buffer, "%d", HRESULT_CODE(hr) );
        return buffer;
    }
    }
}

static LPCSTR dpMsgType2str(DWORD dwType)
{
    switch(dwType)
    {
    case DPSYS_CREATEPLAYERORGROUP:      return "DPSYS_CREATEPLAYERORGROUP";
    case DPSYS_DESTROYPLAYERORGROUP:     return "DPSYS_DESTROYPLAYERORGROUP";
    case DPSYS_ADDPLAYERTOGROUP:         return "DPSYS_ADDPLAYERTOGROUP";
    case DPSYS_DELETEPLAYERFROMGROUP:    return "DPSYS_DELETEPLAYERFROMGROUP";
    case DPSYS_SESSIONLOST:              return "DPSYS_SESSIONLOST";
    case DPSYS_HOST:                     return "DPSYS_HOST";
    case DPSYS_SETPLAYERORGROUPDATA:     return "DPSYS_SETPLAYERORGROUPDATA";
    case DPSYS_SETPLAYERORGROUPNAME:     return "DPSYS_SETPLAYERORGROUPNAME";
    case DPSYS_SETSESSIONDESC:           return "DPSYS_SETSESSIONDESC";
    case DPSYS_ADDGROUPTOGROUP:          return "DPSYS_ADDGROUPTOGROUP";
    case DPSYS_DELETEGROUPFROMGROUP:     return "DPSYS_DELETEGROUPFROMGROUP";
    case DPSYS_SECUREMESSAGE:            return "DPSYS_SECUREMESSAGE";
    case DPSYS_STARTSESSION:             return "DPSYS_STARTSESSION";
    case DPSYS_CHAT:                     return "DPSYS_DPSYS_CHAT";
    case DPSYS_SETGROUPOWNER:            return "DPSYS_SETGROUPOWNER";
    case DPSYS_SENDCOMPLETE:             return "DPSYS_SENDCOMPLETE";

    default:                             return "UNKNOWN";
    }
}

static LPCSTR dwFlags2str(DWORD dwFlags, DWORD flagType)
{

#define FLAGS_DPCONNECTION     (1<<0)
#define FLAGS_DPENUMPLAYERS    (1<<1)
#define FLAGS_DPENUMGROUPS     (1<<2)
#define FLAGS_DPPLAYER         (1<<3)
#define FLAGS_DPGROUP          (1<<4)
#define FLAGS_DPENUMSESSIONS   (1<<5)
#define FLAGS_DPGETCAPS        (1<<6)
#define FLAGS_DPGET            (1<<7)
#define FLAGS_DPRECEIVE        (1<<8)
#define FLAGS_DPSEND           (1<<9)
#define FLAGS_DPSET            (1<<10)
#define FLAGS_DPMESSAGEQUEUE   (1<<11)
#define FLAGS_DPCONNECT        (1<<12)
#define FLAGS_DPOPEN           (1<<13)
#define FLAGS_DPSESSION        (1<<14)
#define FLAGS_DPLCONNECTION    (1<<15)
#define FLAGS_DPESC            (1<<16)
#define FLAGS_DPCAPS           (1<<17)

    LPSTR flags = get_temp_buffer();

    /* EnumConnections */

    if (flagType & FLAGS_DPCONNECTION)
    {
        if (dwFlags & DPCONNECTION_DIRECTPLAY)
            strcat(flags, "DPCONNECTION_DIRECTPLAY,");
        if (dwFlags & DPCONNECTION_DIRECTPLAYLOBBY)
            strcat(flags, "DPCONNECTION_DIRECTPLAYLOBBY,");
    }

    /* EnumPlayers,
       EnumGroups */

    if (flagType & FLAGS_DPENUMPLAYERS)
    {
        if (dwFlags == DPENUMPLAYERS_ALL)
            strcat(flags, "DPENUMPLAYERS_ALL,");
        if (dwFlags & DPENUMPLAYERS_LOCAL)
            strcat(flags, "DPENUMPLAYERS_LOCAL,");
        if (dwFlags & DPENUMPLAYERS_REMOTE)
            strcat(flags, "DPENUMPLAYERS_REMOTE,");
        if (dwFlags & DPENUMPLAYERS_GROUP)
            strcat(flags, "DPENUMPLAYERS_GROUP,");
        if (dwFlags & DPENUMPLAYERS_SESSION)
            strcat(flags, "DPENUMPLAYERS_SESSION,");
        if (dwFlags & DPENUMPLAYERS_SERVERPLAYER)
            strcat(flags, "DPENUMPLAYERS_SERVERPLAYER,");
        if (dwFlags & DPENUMPLAYERS_SPECTATOR)
            strcat(flags, "DPENUMPLAYERS_SPECTATOR,");
        if (dwFlags & DPENUMPLAYERS_OWNER)
            strcat(flags, "DPENUMPLAYERS_OWNER,");
    }
    if (flagType & FLAGS_DPENUMGROUPS)
    {
        if (dwFlags == DPENUMGROUPS_ALL)
            strcat(flags, "DPENUMGROUPS_ALL,");
        if (dwFlags & DPENUMPLAYERS_LOCAL)
            strcat(flags, "DPENUMGROUPS_LOCAL,");
        if (dwFlags & DPENUMPLAYERS_REMOTE)
            strcat(flags, "DPENUMGROUPS_REMOTE,");
        if (dwFlags & DPENUMPLAYERS_GROUP)
            strcat(flags, "DPENUMGROUPS_GROUP,");
        if (dwFlags & DPENUMPLAYERS_SESSION)
            strcat(flags, "DPENUMGROUPS_SESSION,");
        if (dwFlags & DPENUMGROUPS_SHORTCUT)
            strcat(flags, "DPENUMGROUPS_SHORTCUT,");
        if (dwFlags & DPENUMGROUPS_STAGINGAREA)
            strcat(flags, "DPENUMGROUPS_STAGINGAREA,");
        if (dwFlags & DPENUMGROUPS_HIDDEN)
            strcat(flags, "DPENUMGROUPS_HIDDEN,");
    }

    /* CreatePlayer */

    if (flagType & FLAGS_DPPLAYER)
    {
        if (dwFlags & DPPLAYER_SERVERPLAYER)
            strcat(flags, "DPPLAYER_SERVERPLAYER,");
        if (dwFlags & DPPLAYER_SPECTATOR)
            strcat(flags, "DPPLAYER_SPECTATOR,");
        if (dwFlags & DPPLAYER_LOCAL)
            strcat(flags, "DPPLAYER_LOCAL,");
        if (dwFlags & DPPLAYER_OWNER)
            strcat(flags, "DPPLAYER_OWNER,");
    }

    /* CreateGroup */

    if (flagType & FLAGS_DPGROUP)
    {
        if (dwFlags & DPGROUP_STAGINGAREA)
            strcat(flags, "DPGROUP_STAGINGAREA,");
        if (dwFlags & DPGROUP_LOCAL)
            strcat(flags, "DPGROUP_LOCAL,");
        if (dwFlags & DPGROUP_HIDDEN)
            strcat(flags, "DPGROUP_HIDDEN,");
    }

    /* EnumSessions */

    if (flagType & FLAGS_DPENUMSESSIONS)
    {
        if (dwFlags & DPENUMSESSIONS_AVAILABLE)
            strcat(flags, "DPENUMSESSIONS_AVAILABLE,");
        if (dwFlags &  DPENUMSESSIONS_ALL)
            strcat(flags, "DPENUMSESSIONS_ALL,");
        if (dwFlags & DPENUMSESSIONS_ASYNC)
            strcat(flags, "DPENUMSESSIONS_ASYNC,");
        if (dwFlags & DPENUMSESSIONS_STOPASYNC)
            strcat(flags, "DPENUMSESSIONS_STOPASYNC,");
        if (dwFlags & DPENUMSESSIONS_PASSWORDREQUIRED)
            strcat(flags, "DPENUMSESSIONS_PASSWORDREQUIRED,");
        if (dwFlags & DPENUMSESSIONS_RETURNSTATUS)
            strcat(flags, "DPENUMSESSIONS_RETURNSTATUS,");
    }

    /* GetCaps,
       GetPlayerCaps */

    if (flagType & FLAGS_DPGETCAPS)
    {
        if (dwFlags & DPGETCAPS_GUARANTEED)
            strcat(flags, "DPGETCAPS_GUARANTEED,");
    }

    /* GetGroupData,
       GetPlayerData */

    if (flagType & FLAGS_DPGET)
    {
        if (dwFlags == DPGET_REMOTE)
            strcat(flags, "DPGET_REMOTE,");
        if (dwFlags & DPGET_LOCAL)
            strcat(flags, "DPGET_LOCAL,");
    }

    /* Receive */

    if (flagType & FLAGS_DPRECEIVE)
    {
        if (dwFlags & DPRECEIVE_ALL)
            strcat(flags, "DPRECEIVE_ALL,");
        if (dwFlags & DPRECEIVE_TOPLAYER)
            strcat(flags, "DPRECEIVE_TOPLAYER,");
        if (dwFlags & DPRECEIVE_FROMPLAYER)
            strcat(flags, "DPRECEIVE_FROMPLAYER,");
        if (dwFlags & DPRECEIVE_PEEK)
            strcat(flags, "DPRECEIVE_PEEK,");
    }

    /* Send */

    if (flagType & FLAGS_DPSEND)
    {
        /*if (dwFlags == DPSEND_NONGUARANTEED)
          strcat(flags, "DPSEND_NONGUARANTEED,");*/
        if (dwFlags == DPSEND_MAX_PRIORITY) /* = DPSEND_MAX_PRI */
        {
            strcat(flags, "DPSEND_MAX_PRIORITY,");
        }
        else
        {
            if (dwFlags & DPSEND_GUARANTEED)
                strcat(flags, "DPSEND_GUARANTEED,");
            if (dwFlags & DPSEND_HIGHPRIORITY)
                strcat(flags, "DPSEND_HIGHPRIORITY,");
            if (dwFlags & DPSEND_OPENSTREAM)
                strcat(flags, "DPSEND_OPENSTREAM,");
            if (dwFlags & DPSEND_CLOSESTREAM)
                strcat(flags, "DPSEND_CLOSESTREAM,");
            if (dwFlags & DPSEND_SIGNED)
                strcat(flags, "DPSEND_SIGNED,");
            if (dwFlags & DPSEND_ENCRYPTED)
                strcat(flags, "DPSEND_ENCRYPTED,");
            if (dwFlags & DPSEND_LOBBYSYSTEMMESSAGE)
                strcat(flags, "DPSEND_LOBBYSYSTEMMESSAGE,");
            if (dwFlags & DPSEND_ASYNC)
                strcat(flags, "DPSEND_ASYNC,");
            if (dwFlags & DPSEND_NOSENDCOMPLETEMSG)
                strcat(flags, "DPSEND_NOSENDCOMPLETEMSG,");
        }
    }

    /* SetGroupData,
       SetGroupName,
       SetPlayerData,
       SetPlayerName,
       SetSessionDesc */

    if (flagType & FLAGS_DPSET)
    {
        if (dwFlags == DPSET_REMOTE)
            strcat(flags, "DPSET_REMOTE,");
        if (dwFlags & DPSET_LOCAL)
            strcat(flags, "DPSET_LOCAL,");
        if (dwFlags & DPSET_GUARANTEED)
            strcat(flags, "DPSET_GUARANTEED,");
    }

    /* GetMessageQueue */

    if (flagType & FLAGS_DPMESSAGEQUEUE)
    {
        if (dwFlags & DPMESSAGEQUEUE_SEND)
            strcat(flags, "DPMESSAGEQUEUE_SEND,");
        if (dwFlags & DPMESSAGEQUEUE_RECEIVE)
            strcat(flags, "DPMESSAGEQUEUE_RECEIVE,");
    }

    /* Connect */

    if (flagType & FLAGS_DPCONNECT)
    {
        if (dwFlags & DPCONNECT_RETURNSTATUS)
            strcat(flags, "DPCONNECT_RETURNSTATUS,");
    }

    /* Open */

    if (flagType & FLAGS_DPOPEN)
    {
        if (dwFlags & DPOPEN_JOIN)
            strcat(flags, "DPOPEN_JOIN,");
        if (dwFlags & DPOPEN_CREATE)
            strcat(flags, "DPOPEN_CREATE,");
        if (dwFlags & DPOPEN_RETURNSTATUS)
            strcat(flags, "DPOPEN_RETURNSTATUS,");
    }

    /* DPSESSIONDESC2 */

    if (flagType & FLAGS_DPSESSION)
    {
        if (dwFlags & DPSESSION_NEWPLAYERSDISABLED)
            strcat(flags, "DPSESSION_NEWPLAYERSDISABLED,");
        if (dwFlags & DPSESSION_MIGRATEHOST)
            strcat(flags, "DPSESSION_MIGRATEHOST,");
        if (dwFlags & DPSESSION_NOMESSAGEID)
            strcat(flags, "DPSESSION_NOMESSAGEID,");
        if (dwFlags & DPSESSION_JOINDISABLED)
            strcat(flags, "DPSESSION_JOINDISABLED,");
        if (dwFlags & DPSESSION_KEEPALIVE)
            strcat(flags, "DPSESSION_KEEPALIVE,");
        if (dwFlags & DPSESSION_NODATAMESSAGES)
            strcat(flags, "DPSESSION_NODATAMESSAGES,");
        if (dwFlags & DPSESSION_SECURESERVER)
            strcat(flags, "DPSESSION_SECURESERVER,");
        if (dwFlags & DPSESSION_PRIVATE)
            strcat(flags, "DPSESSION_PRIVATE,");
        if (dwFlags & DPSESSION_PASSWORDREQUIRED)
            strcat(flags, "DPSESSION_PASSWORDREQUIRED,");
        if (dwFlags & DPSESSION_MULTICASTSERVER)
            strcat(flags, "DPSESSION_MULTICASTSERVER,");
        if (dwFlags & DPSESSION_CLIENTSERVER)
            strcat(flags, "DPSESSION_CLIENTSERVER,");

        if (dwFlags & DPSESSION_DIRECTPLAYPROTOCOL)
            strcat(flags, "DPSESSION_DIRECTPLAYPROTOCOL,");
        if (dwFlags & DPSESSION_NOPRESERVEORDER)
            strcat(flags, "DPSESSION_NOPRESERVEORDER,");
        if (dwFlags & DPSESSION_OPTIMIZELATENCY)
            strcat(flags, "DPSESSION_OPTIMIZELATENCY,");

    }

    /* DPLCONNECTION */

    if (flagType & FLAGS_DPLCONNECTION)
    {
        if (dwFlags & DPLCONNECTION_CREATESESSION)
            strcat(flags, "DPLCONNECTION_CREATESESSION,");
        if (dwFlags & DPLCONNECTION_JOINSESSION)
            strcat(flags, "DPLCONNECTION_JOINSESSION,");
    }

    /* EnumSessionsCallback2 */

    if (flagType & FLAGS_DPESC)
    {
        if (dwFlags & DPESC_TIMEDOUT)
            strcat(flags, "DPESC_TIMEDOUT,");
    }

    /* GetCaps,
       GetPlayerCaps */

    if (flagType & FLAGS_DPCAPS)
    {
        if (dwFlags & DPCAPS_ISHOST)
            strcat(flags, "DPCAPS_ISHOST,");
        if (dwFlags & DPCAPS_GROUPOPTIMIZED)
            strcat(flags, "DPCAPS_GROUPOPTIMIZED,");
        if (dwFlags & DPCAPS_KEEPALIVEOPTIMIZED)
            strcat(flags, "DPCAPS_KEEPALIVEOPTIMIZED,");
        if (dwFlags & DPCAPS_GUARANTEEDOPTIMIZED)
            strcat(flags, "DPCAPS_GUARANTEEDOPTIMIZED,");
        if (dwFlags & DPCAPS_GUARANTEEDSUPPORTED)
            strcat(flags, "DPCAPS_GUARANTEEDSUPPORTED,");
        if (dwFlags & DPCAPS_SIGNINGSUPPORTED)
            strcat(flags, "DPCAPS_SIGNINGSUPPORTED,");
        if (dwFlags & DPCAPS_ENCRYPTIONSUPPORTED)
            strcat(flags, "DPCAPS_ENCRYPTIONSUPPORTED,");
        if (dwFlags & DPCAPS_ASYNCCANCELSUPPORTED)
            strcat(flags, "DPCAPS_ASYNCCANCELSUPPORTED,");
        if (dwFlags & DPCAPS_ASYNCCANCELALLSUPPORTED)
            strcat(flags, "DPCAPS_ASYNCCANCELALLSUPPORTED,");
        if (dwFlags & DPCAPS_SENDTIMEOUTSUPPORTED)
            strcat(flags, "DPCAPS_SENDTIMEOUTSUPPORTED,");
        if (dwFlags & DPCAPS_SENDPRIORITYSUPPORTED)
            strcat(flags, "DPCAPS_SENDPRIORITYSUPPORTED,");
        if (dwFlags & DPCAPS_ASYNCSUPPORTED)
            strcat(flags, "DPCAPS_ASYNCSUPPORTED,");

        if (dwFlags & DPPLAYERCAPS_LOCAL)
            strcat(flags, "DPPLAYERCAPS_LOCAL,");
    }

    if ((strlen(flags) == 0) && (dwFlags != 0))
        strcpy(flags, "UNKNOWN");
    else
        flags[strlen(flags)-1] = '\0';

    return flags;
}

static char dpid2char(DPID* dpid, DWORD dpidSize, DPID idPlayer)
{
    UINT i;
    if ( idPlayer == DPID_SYSMSG )
        return 'S';
    for (i=0; i<dpidSize; i++)
    {
        if ( idPlayer == dpid[i] )
            return (char)(i+48);
    }
    return '?';
}

static void check_messages( IDirectPlay4 *pDP, DPID *dpid, DWORD dpidSize,
        lpCallbackData callbackData )
{
    /* Retrieves all messages from the queue of pDP, performing tests
     * to check if we are receiving what we expect.
     *
     * Information about the messages is stores in callbackData:
     *
     * callbackData->dwCounter1: Number of messages received.
     * callbackData->szTrace1: Traces for sender and receiver.
     *     We store the position a dpid holds in the dpid array.
     *     Example:
     *
     *       trace string: "01,02,03,14"
     *           expanded: [ '01', '02', '03', '14' ]
     *                         \     \     \     \
     *                          \     \     \     ) message 3: from 1 to 4
     *                           \     \     ) message 2: from 0 to 3
     *                            \     ) message 1: from 0 to 2
     *                             ) message 0: from 0 to 1
     *
     *     In general terms:
     *       sender of message i   = character in place 3*i of the array
     *       receiver of message i = character in place 3*i+1 of the array
     *
     *     A sender value of 'S' means DPID_SYSMSG, this is, a system message.
     *
     * callbackData->szTrace2: Traces for message sizes.
     */

    DPID idFrom, idTo;
    UINT i;
    DWORD dwDataSize = 1024;
    LPVOID lpData = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwDataSize );
    HRESULT hr;
    char temp[5];

    callbackData->szTrace2[0] = '\0';

    i = 0;
    while ( DP_OK == (hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0,
                                                 lpData, &dwDataSize )) )
    {

        callbackData->szTrace1[ 3*i   ] = dpid2char( dpid, dpidSize, idFrom );
        callbackData->szTrace1[ 3*i+1 ] = dpid2char( dpid, dpidSize, idTo );
        callbackData->szTrace1[ 3*i+2 ] = ',';

        sprintf( temp, "%d,", dwDataSize );
        strcat( callbackData->szTrace2, temp );

        dwDataSize = 1024;
        ++i;
    }

    checkHR( DPERR_NOMESSAGES, hr );

    callbackData->szTrace1[ 3*i ] = '\0';
    callbackData->dwCounter1 = i;


    HeapFree( GetProcessHeap(), 0, lpData );
}

static void init_TCPIP_provider( IDirectPlay4 *pDP, LPCSTR strIPAddressString, WORD port )
{

    DPCOMPOUNDADDRESSELEMENT addressElements[3];
    LPVOID pAddress = NULL;
    DWORD dwAddressSize = 0;
    IDirectPlayLobby3 *pDPL;
    HRESULT hr;

    hr = CoCreateInstance( &CLSID_DirectPlayLobby, NULL, CLSCTX_ALL,
                           &IID_IDirectPlayLobby3A, (LPVOID*) &pDPL );
    ok (SUCCEEDED (hr), "CCI of CLSID_DirectPlayLobby / IID_IDirectPlayLobby3A failed\n");
    if (FAILED (hr)) return;

    /* Service provider */
    addressElements[0].guidDataType = DPAID_ServiceProvider;
    addressElements[0].dwDataSize   = sizeof(GUID);
    addressElements[0].lpData       = (LPVOID) &DPSPGUID_TCPIP;

    /* IP address string */
    addressElements[1].guidDataType = DPAID_INet;
    addressElements[1].dwDataSize   = lstrlenA(strIPAddressString) + 1;
    addressElements[1].lpData       = (LPVOID) strIPAddressString;

    /* Optional Port number */
    if( port > 0 )
    {
        addressElements[2].guidDataType = DPAID_INetPort;
        addressElements[2].dwDataSize   = sizeof(WORD);
        addressElements[2].lpData       = &port;
    }


    hr = IDirectPlayLobby_CreateCompoundAddress( pDPL, addressElements, 2,
                                                 NULL, &dwAddressSize );
    checkHR( DPERR_BUFFERTOOSMALL, hr );

    if( hr == DPERR_BUFFERTOOSMALL )
    {
        pAddress = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwAddressSize );
        hr = IDirectPlayLobby_CreateCompoundAddress( pDPL, addressElements, 2,
                                                     pAddress, &dwAddressSize );
        checkHR( DP_OK, hr );
    }

    hr = IDirectPlayX_InitializeConnection( pDP, pAddress, 0 );
    checkHR( DP_OK, hr );

    HeapFree( GetProcessHeap(), 0, pAddress );
    IDirectPlayLobby_Release(pDPL);
}

static BOOL CALLBACK EnumSessions_cb_join( LPCDPSESSIONDESC2 lpThisSD,
                                           LPDWORD lpdwTimeOut,
                                           DWORD dwFlags,
                                           LPVOID lpContext )
{
    IDirectPlay4 *pDP = lpContext;
    DPSESSIONDESC2 dpsd;
    HRESULT hr;

    if (dwFlags & DPESC_TIMEDOUT)
    {
        return FALSE;
    }

    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.guidInstance = lpThisSD->guidInstance;

    hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_JOIN );
    checkHR( DP_OK, hr );

    return TRUE;
}


/* DirectPlayCreate */

static void test_DirectPlayCreate(void)
{

    IDirectPlay *pDP;
    HRESULT hr;

    /* TODO: Check how it behaves with pUnk!=NULL */

    /* pDP==NULL */
    hr = pDirectPlayCreate( NULL, NULL, NULL );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = pDirectPlayCreate( (LPGUID) &GUID_NULL, NULL, NULL );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = pDirectPlayCreate( (LPGUID) &DPSPGUID_TCPIP, NULL, NULL );
    checkHR( DPERR_INVALIDPARAMS, hr );

    /* pUnk==NULL, pDP!=NULL */
    hr = pDirectPlayCreate( NULL, &pDP, NULL );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = pDirectPlayCreate( (LPGUID) &GUID_NULL, &pDP, NULL );
    checkHR( DP_OK, hr );
    if ( hr == DP_OK )
        IDirectPlayX_Release( pDP );
    hr = pDirectPlayCreate( (LPGUID) &DPSPGUID_TCPIP, &pDP, NULL );
    checkHR( DP_OK, hr );
    if ( hr == DP_OK )
        IDirectPlayX_Release( pDP );

}

static BOOL CALLBACK callback_providersA(GUID* guid, char *name, DWORD major, DWORD minor, void *arg)
{
    struct provider_data *prov = arg;

    if (!prov) return TRUE;

    if (prov->call_count < sizeof(prov->guid_data) / sizeof(prov->guid_data[0]))
    {
        prov->guid_ptr[prov->call_count] = guid;
        prov->guid_data[prov->call_count] = *guid;

        prov->call_count++;
    }

    if (prov->ret_value) /* Only trace when looping all providers */
        trace("Provider #%d '%s' (%d.%d)\n", prov->call_count, name, major, minor);
    return prov->ret_value;
}

static BOOL CALLBACK callback_providersW(GUID* guid, WCHAR *name, DWORD major, DWORD minor, void *arg)
{
    struct provider_data *prov = arg;

    if (!prov) return TRUE;

    if (prov->call_count < sizeof(prov->guid_data) / sizeof(prov->guid_data[0]))
    {
        prov->guid_ptr[prov->call_count] = guid;
        prov->guid_data[prov->call_count] = *guid;

        prov->call_count++;
    }

    return prov->ret_value;
}

static void test_EnumerateProviders(void)
{
    HRESULT hr;
    int i;
    struct provider_data arg;

    memset(&arg, 0, sizeof(arg));
    arg.ret_value = TRUE;

    hr = pDirectPlayEnumerateA(callback_providersA, NULL);
    ok(SUCCEEDED(hr), "DirectPlayEnumerateA failed\n");

    SetLastError(0xdeadbeef);
    hr = pDirectPlayEnumerateA(NULL, &arg);
    ok(FAILED(hr), "DirectPlayEnumerateA expected to fail\n");
    ok(GetLastError() == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%x\n", GetLastError());

    SetLastError(0xdeadbeef);
    hr = pDirectPlayEnumerateA(NULL, NULL);
    ok(FAILED(hr), "DirectPlayEnumerateA expected to fail\n");
    ok(GetLastError() == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%x\n", GetLastError());

    hr = pDirectPlayEnumerateA(callback_providersA, &arg);
    ok(SUCCEEDED(hr), "DirectPlayEnumerateA failed\n");
    ok(arg.call_count > 0, "Expected at least one valid provider\n");
    trace("Found %d providers\n", arg.call_count);

    /* The returned GUID values must have persisted after enumeration (bug 37185) */
    for(i = 0; i < arg.call_count; i++)
    {
        ok(IsEqualGUID(arg.guid_ptr[i], &arg.guid_data[i]), "#%d Expected equal GUID values\n", i);
    }

    memset(&arg, 0, sizeof(arg));
    arg.ret_value = FALSE;
    hr = pDirectPlayEnumerateA(callback_providersA, &arg);
    ok(SUCCEEDED(hr), "DirectPlayEnumerateA failed\n");
    ok(arg.call_count == 1, "Expected 1, got %d\n", arg.call_count);

    hr = pDirectPlayEnumerateW(callback_providersW, NULL);
    ok(SUCCEEDED(hr), "DirectPlayEnumerateW failed\n");

    SetLastError(0xdeadbeef);
    hr = pDirectPlayEnumerateW(NULL, &arg);
    ok(FAILED(hr), "DirectPlayEnumerateW expected to fail\n");
    ok(GetLastError() == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%x\n", GetLastError());

    SetLastError(0xdeadbeef);
    hr = pDirectPlayEnumerateW(NULL, NULL);
    ok(FAILED(hr), "DirectPlayEnumerateW expected to fail\n");
    ok(GetLastError() == 0xdeadbeef, "Expected 0xdeadbeef, got 0x%x\n", GetLastError());

    memset(&arg, 0, sizeof(arg));
    arg.ret_value = TRUE;
    hr = pDirectPlayEnumerateW(callback_providersW, &arg);
    ok(SUCCEEDED(hr), "DirectPlayEnumerateW failed\n");
    ok(arg.call_count > 0, "Expected at least one valid provider\n");

    /* The returned GUID values must have persisted after enumeration (bug 37185) */
    for(i = 0; i < arg.call_count; i++)
    {
        ok(IsEqualGUID(arg.guid_ptr[i], &arg.guid_data[i]), "#%d Expected equal GUID values\n", i);
    }

    memset(&arg, 0, sizeof(arg));
    arg.ret_value = FALSE;
    hr = pDirectPlayEnumerateW(callback_providersW, &arg);
    ok(SUCCEEDED(hr), "DirectPlayEnumerateW failed\n");
    ok(arg.call_count == 1, "Expected 1, got %d\n", arg.call_count);
}

/* EnumConnections */

static BOOL CALLBACK EnumAddress_cb2( REFGUID guidDataType,
                                      DWORD dwDataSize,
                                      LPCVOID lpData,
                                      LPVOID lpContext )
{
    lpCallbackData callbackData = lpContext;

    static REFGUID types[] = { &DPAID_TotalSize,
                               &DPAID_ServiceProvider,
                               &GUID_NULL };
    static DWORD sizes[] = { 4, 16, 0  };
    static REFGUID sps[] = { &DPSPGUID_SERIAL, &DPSPGUID_MODEM,
                             &DPSPGUID_IPX, &DPSPGUID_TCPIP };


    checkGuid( types[ callbackData->dwCounter2 ], guidDataType );
    check( sizes[ callbackData->dwCounter2 ], dwDataSize );

    if ( IsEqualGUID( types[0], guidDataType ) )
    {
        todo_wine check( 80, *((LPDWORD) lpData) );
    }
    else if ( IsEqualGUID( types[1], guidDataType ) )
    {
        BOOL found = FALSE;
        int i;
        for( i=0; i < sizeof(sps) / sizeof(sps[0]) && !found; i++ )
            found = IsEqualGUID( sps[i], lpData );
        ok( found, "Unknown Address type found %s\n", wine_dbgstr_guid(lpData) );
    }

    callbackData->dwCounter2++;

    return TRUE;
}

static BOOL CALLBACK EnumConnections_cb( LPCGUID lpguidSP,
                                         LPVOID lpConnection,
                                         DWORD dwConnectionSize,
                                         LPCDPNAME lpName,
                                         DWORD dwFlags,
                                         LPVOID lpContext )
{

    lpCallbackData callbackData = lpContext;
    IDirectPlayLobby *pDPL;
    HRESULT hr;


    if (!callbackData->dwFlags)
    {
        callbackData->dwFlags = DPCONNECTION_DIRECTPLAY;
    }

    checkFlags( callbackData->dwFlags, dwFlags, FLAGS_DPCONNECTION );

    /* Get info from lpConnection */
    hr = CoCreateInstance( &CLSID_DirectPlayLobby, NULL, CLSCTX_ALL,
                           &IID_IDirectPlayLobby3A, (LPVOID*) &pDPL );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlayLobby / IID_IDirectPlayLobby3A failed\n");
    if (FAILED(hr))
        return FALSE;

    callbackData->dwCounter2 = 0;
    IDirectPlayLobby_EnumAddress( pDPL, EnumAddress_cb2, lpConnection,
                                  dwConnectionSize, callbackData );
    todo_wine check( 3, callbackData->dwCounter2 );

    callbackData->dwCounter1++;

    IDirectPlayLobby_Release(pDPL);

    return TRUE;
}

static void test_EnumConnections(void)
{

    IDirectPlay4 *pDP;
    CallbackData callbackData;
    HRESULT hr;


    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP );

    ok (SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n");
    if (FAILED(hr)) return;

    callbackData.dwCounter1 = 0;
    callbackData.dwFlags = 0;
    hr = IDirectPlayX_EnumConnections( pDP, &appGuid, EnumConnections_cb,
                                       &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    ok( callbackData.dwCounter1 == 4 || callbackData.dwCounter1 == 3, "got=%d\n", callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    callbackData.dwFlags = 0;
    hr = IDirectPlayX_EnumConnections( pDP, NULL, EnumConnections_cb,
                                       &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    ok( callbackData.dwCounter1 == 4 || callbackData.dwCounter1 == 3, "got=%d\n", callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    callbackData.dwFlags = 0;
    hr = IDirectPlayX_EnumConnections( pDP, &appGuid, NULL,
                                       &callbackData, callbackData.dwFlags );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check( 0, callbackData.dwCounter1 );


    /* Flag tests */
    callbackData.dwCounter1 = 0;
    callbackData.dwFlags = DPCONNECTION_DIRECTPLAY;
    hr = IDirectPlayX_EnumConnections( pDP, &appGuid, EnumConnections_cb,
                                       &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    ok( callbackData.dwCounter1 == 4 || callbackData.dwCounter1 == 3, "got=%d\n", callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    callbackData.dwFlags = DPCONNECTION_DIRECTPLAYLOBBY;
    hr = IDirectPlayX_EnumConnections( pDP, &appGuid, EnumConnections_cb,
                                       &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    callbackData.dwFlags = ( DPCONNECTION_DIRECTPLAY |
                             DPCONNECTION_DIRECTPLAYLOBBY );
    hr = IDirectPlayX_EnumConnections( pDP, &appGuid, EnumConnections_cb,
                                       &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    ok( callbackData.dwCounter1 == 4 || callbackData.dwCounter1 == 3, "got=%d\n", callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    callbackData.dwFlags = ~( DPCONNECTION_DIRECTPLAY |
                              DPCONNECTION_DIRECTPLAYLOBBY );
    hr = IDirectPlayX_EnumConnections( pDP, &appGuid, EnumConnections_cb,
                                       &callbackData, callbackData.dwFlags );
    checkHR( DPERR_INVALIDFLAGS, hr );
    check( 0, callbackData.dwCounter1 );


    IDirectPlayX_Release( pDP );
}

/* InitializeConnection */

static BOOL CALLBACK EnumConnections_cb2( LPCGUID lpguidSP,
                                          LPVOID lpConnection,
                                          DWORD dwConnectionSize,
                                          LPCDPNAME lpName,
                                          DWORD dwFlags,
                                          LPVOID lpContext )
{
    IDirectPlay4 *pDP = lpContext;
    HRESULT hr;

    /* Incorrect parameters */
    hr = IDirectPlayX_InitializeConnection( pDP, NULL, 1 );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_InitializeConnection( pDP, lpConnection, 1 );
    checkHR( DPERR_INVALIDFLAGS, hr );

    /* Normal operation.
       We're only interested in ensuring that the TCP/IP provider works */

    if( IsEqualGUID(lpguidSP, &DPSPGUID_TCPIP) )
    {
        hr = IDirectPlayX_InitializeConnection( pDP, lpConnection, 0 );
        checkHR( DP_OK, hr );
        hr = IDirectPlayX_InitializeConnection( pDP, lpConnection, 0 );
        checkHR( DPERR_ALREADYINITIALIZED, hr );
    }

    return TRUE;
}

static void test_InitializeConnection(void)
{

    IDirectPlay4 *pDP;
    HRESULT hr;

    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP );

    ok (SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n");
    if (FAILED(hr)) return;

    IDirectPlayX_EnumConnections( pDP, &appGuid, EnumConnections_cb2, pDP, 0 );

    IDirectPlayX_Release( pDP );
}

/* GetCaps */

static void test_GetCaps(void)
{

    IDirectPlay4 *pDP;
    DPCAPS dpcaps;
    DWORD dwFlags;
    HRESULT hr;


    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP );
    ok (SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n");
    if (FAILED(hr)) return;

    ZeroMemory( &dpcaps, sizeof(DPCAPS) );

    /* Service provider not ininitialized */
    hr = IDirectPlayX_GetCaps( pDP, &dpcaps, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );

    init_TCPIP_provider( pDP, "127.0.0.1", 0 );

    /* dpcaps not ininitialized */
    hr = IDirectPlayX_GetCaps( pDP, &dpcaps, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    dpcaps.dwSize = sizeof(DPCAPS);

    for (dwFlags=0;
         dwFlags<=DPGETCAPS_GUARANTEED;
         dwFlags+=DPGETCAPS_GUARANTEED)
    {

        hr = IDirectPlayX_GetCaps( pDP, &dpcaps, dwFlags );
        checkHR( DP_OK, hr );
        check( sizeof(DPCAPS), dpcaps.dwSize );
        check( DPCAPS_ASYNCSUPPORTED |
               DPCAPS_GUARANTEEDOPTIMIZED |
               DPCAPS_GUARANTEEDSUPPORTED,
               dpcaps.dwFlags );
        check( 0,     dpcaps.dwMaxQueueSize );
        check( 0,     dpcaps.dwHundredBaud );
        check( 500,   dpcaps.dwLatency );
        check( 65536, dpcaps.dwMaxLocalPlayers );
        check( 20,    dpcaps.dwHeaderLength );
        check( 5000,  dpcaps.dwTimeout );

        switch (dwFlags)
        {
        case 0:
            check( 65479,   dpcaps.dwMaxBufferSize );
            check( 65536,   dpcaps.dwMaxPlayers );
            break;
        case DPGETCAPS_GUARANTEED:
            check( 1048547, dpcaps.dwMaxBufferSize );
            check( 64,      dpcaps.dwMaxPlayers );
            break;
        default: break;
        }
    }

    IDirectPlayX_Release( pDP );
}

static void test_EnumAddressTypes(void)
{
    IDirectPlay4 *pDP;
    HRESULT hr;
    DPCOMPOUNDADDRESSELEMENT addressElements[2];
    LPVOID pAddress = NULL;
    DWORD dwAddressSize = 0;
    IDirectPlayLobby3 *pDPL;
    WORD port = 6001;

    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
    if (FAILED(hr))
        return;

    hr = CoCreateInstance( &CLSID_DirectPlayLobby, NULL, CLSCTX_ALL,
                           &IID_IDirectPlayLobby3A, (LPVOID*) &pDPL );
    ok (SUCCEEDED (hr), "CCI of CLSID_DirectPlayLobby / IID_IDirectPlayLobby3A failed\n");
    if (FAILED (hr)) return;

    addressElements[0].guidDataType = DPAID_ServiceProvider;
    addressElements[0].dwDataSize   = sizeof(GUID);
    addressElements[0].lpData       = (void*) &DPSPGUID_TCPIP;

    addressElements[1].guidDataType = invalid_guid;
    addressElements[1].dwDataSize   = sizeof(WORD);
    addressElements[1].lpData       = &port;

    hr = IDirectPlayLobby_CreateCompoundAddress( pDPL, addressElements, 2, NULL, &dwAddressSize );
    checkHR( DPERR_BUFFERTOOSMALL, hr );

    if( hr == DPERR_BUFFERTOOSMALL )
    {
        pAddress = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwAddressSize );
        hr = IDirectPlayLobby_CreateCompoundAddress( pDPL, addressElements, 2,
                                                     pAddress, &dwAddressSize );
        checkHR( DP_OK, hr );
    }

    IDirectPlayX_Close(pDP);
    IDirectPlayX_Release(pDP);
    IDirectPlayLobby_Release(pDPL);

    HeapFree( GetProcessHeap(), 0, pAddress );
}

/* Open */

static BOOL CALLBACK EnumSessions_cb2( LPCDPSESSIONDESC2 lpThisSD,
                                       LPDWORD lpdwTimeOut,
                                       DWORD dwFlags,
                                       LPVOID lpContext )
{
    IDirectPlay4 *pDP = lpContext;
    DPSESSIONDESC2 dpsd;
    HRESULT hr;

    if (dwFlags & DPESC_TIMEDOUT)
        return FALSE;


    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.guidInstance = lpThisSD->guidInstance;

    if ( lpThisSD->dwFlags & DPSESSION_PASSWORDREQUIRED )
    {
        /* Incorrect password */
        U2(dpsd).lpszPasswordA = (LPSTR) "sonic boom";
        hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_JOIN );
        checkHR( DPERR_INVALIDPASSWORD, hr );

        /* Correct password */
        U2(dpsd).lpszPasswordA = (LPSTR) "hadouken";
        hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_JOIN );
        checkHR( DP_OK, hr );
    }
    else
    {
        hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_JOIN );
        checkHR( DP_OK, hr );
    }

    hr = IDirectPlayX_Close( pDP );
    checkHR( DP_OK, hr );

    return TRUE;
}

static void test_Open(void)
{

    IDirectPlay4 *pDP, *pDP_server;
    DPSESSIONDESC2 dpsd, dpsd_server;
    HRESULT hr;


    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP_server );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
    if (FAILED(hr)) return;

    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
    if (FAILED(hr)) return;

    ZeroMemory( &dpsd_server, sizeof(DPSESSIONDESC2) );
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );

    /* Service provider not initialized */
    hr = IDirectPlayX_Open( pDP_server, &dpsd_server, DPOPEN_CREATE );
    todo_wine checkHR( DPERR_INVALIDPARAMS, hr );

    init_TCPIP_provider( pDP_server, "127.0.0.1", 0 );
    init_TCPIP_provider( pDP, "127.0.0.1", 0 );

    /* Uninitialized  dpsd */
    hr = IDirectPlayX_Open( pDP_server, &dpsd_server, DPOPEN_CREATE );
    checkHR( DPERR_INVALIDPARAMS, hr );


    dpsd_server.dwSize = sizeof(DPSESSIONDESC2);
    dpsd_server.guidApplication = appGuid;
    dpsd_server.dwMaxPlayers = 10;


    /* Regular operation */
    hr = IDirectPlayX_Open( pDP_server, &dpsd_server, DPOPEN_CREATE );
    todo_wine checkHR( DP_OK, hr );

    /* Opening twice */
    hr = IDirectPlayX_Open( pDP_server, &dpsd_server, DPOPEN_CREATE );
    todo_wine checkHR( DPERR_ALREADYINITIALIZED, hr );

    /* Session flags */
    IDirectPlayX_Close( pDP_server );

    dpsd_server.dwFlags = DPSESSION_CLIENTSERVER | DPSESSION_MIGRATEHOST;
    hr = IDirectPlayX_Open( pDP_server, &dpsd_server, DPOPEN_CREATE );
    todo_wine checkHR( DPERR_INVALIDFLAGS, hr );

    dpsd_server.dwFlags = DPSESSION_MULTICASTSERVER | DPSESSION_MIGRATEHOST;
    hr = IDirectPlayX_Open( pDP_server, &dpsd_server, DPOPEN_CREATE );
    todo_wine checkHR( DPERR_INVALIDFLAGS, hr );

    dpsd_server.dwFlags = DPSESSION_SECURESERVER | DPSESSION_MIGRATEHOST;
    hr = IDirectPlayX_Open( pDP_server, &dpsd_server, DPOPEN_CREATE );
    todo_wine checkHR( DPERR_INVALIDFLAGS, hr );


    /* Joining sessions */
    /* - Checking how strict dplay is with sizes */
    dpsd.dwSize = 0;
    hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_JOIN );
    checkHR( DPERR_INVALIDPARAMS, hr );

    dpsd.dwSize = sizeof(DPSESSIONDESC2)-1;
    hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_JOIN );
    checkHR( DPERR_INVALIDPARAMS, hr );

    dpsd.dwSize = sizeof(DPSESSIONDESC2)+1;
    hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_JOIN );
    checkHR( DPERR_INVALIDPARAMS, hr );

    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_JOIN );
    todo_wine checkHR( DPERR_NOSESSIONS, hr ); /* Only checks for size, not guids */


    dpsd.guidApplication = appGuid;
    dpsd.guidInstance = appGuid;


    hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_JOIN );
    todo_wine checkHR( DPERR_NOSESSIONS, hr );
    hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_JOIN | DPOPEN_CREATE );
    todo_wine checkHR( DPERR_NOSESSIONS, hr ); /* Second flag is ignored */

    dpsd_server.dwFlags = 0;


    /* Join to normal session */
    hr = IDirectPlayX_Open( pDP_server, &dpsd_server, DPOPEN_CREATE );
    todo_wine checkHR( DP_OK, hr );

    IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb2, pDP, 0 );


    /* Already initialized session */
    hr = IDirectPlayX_Open( pDP_server, &dpsd_server, DPOPEN_CREATE );
    todo_wine checkHR( DPERR_ALREADYINITIALIZED, hr );


    /* Checking which is the error checking order */
    dpsd_server.dwSize = 0;

    hr = IDirectPlayX_Open( pDP_server, &dpsd_server, DPOPEN_CREATE );
    checkHR( DPERR_INVALIDPARAMS, hr );

    dpsd_server.dwSize = sizeof(DPSESSIONDESC2);


    /* Join to protected session */
    IDirectPlayX_Close( pDP_server );
    U2(dpsd_server).lpszPasswordA = (LPSTR) "hadouken";
    hr = IDirectPlayX_Open( pDP_server, &dpsd_server, DPOPEN_CREATE );
    todo_wine checkHR( DP_OK, hr );

    IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb2,
                               pDP, DPENUMSESSIONS_PASSWORDREQUIRED );


    IDirectPlayX_Release( pDP );
    IDirectPlayX_Release( pDP_server );

}

/* EnumSessions */

static BOOL CALLBACK EnumSessions_cb( LPCDPSESSIONDESC2 lpThisSD,
                                      LPDWORD lpdwTimeOut,
                                      DWORD dwFlags,
                                      LPVOID lpContext )
{
    lpCallbackData callbackData = lpContext;
    callbackData->dwCounter1++;

    if ( dwFlags & DPESC_TIMEDOUT )
    {
        check( TRUE, lpThisSD == NULL );
        return FALSE;
    }
    check( FALSE, lpThisSD == NULL );


    if ( U2(*lpThisSD).lpszPasswordA != NULL )
    {
        check( TRUE, (lpThisSD->dwFlags & DPSESSION_PASSWORDREQUIRED) != 0 );
    }

    if ( lpThisSD->dwFlags & DPSESSION_NEWPLAYERSDISABLED )
    {
        check( 0, lpThisSD->dwCurrentPlayers );
    }

    check( sizeof(*lpThisSD), lpThisSD->dwSize );
    checkLP( NULL, U2(*lpThisSD).lpszPasswordA );

    return TRUE;
}

static IDirectPlay4 *create_session(DPSESSIONDESC2 *lpdpsd)
{

    IDirectPlay4 *pDP;
    DPNAME name;
    DPID dpid;
    HRESULT hr;

    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
    if (FAILED(hr)) return NULL;

    init_TCPIP_provider( pDP, "127.0.0.1", 0 );

    hr = IDirectPlayX_Open( pDP, lpdpsd, DPOPEN_CREATE );
    todo_wine checkHR( DP_OK, hr );

    if ( ! (lpdpsd->dwFlags & DPSESSION_NEWPLAYERSDISABLED) )
    {
        ZeroMemory( &name, sizeof(DPNAME) );
        name.dwSize = sizeof(DPNAME);
        U1(name).lpszShortNameA = (LPSTR) "bofh";

        hr = IDirectPlayX_CreatePlayer( pDP, &dpid, &name, NULL, NULL,
                                        0, DPPLAYER_SERVERPLAYER );
        todo_wine checkHR( DP_OK, hr );
    }

    return pDP;

}

static void test_EnumSessions(void)
{

#define N_SESSIONS 6

    IDirectPlay4 *pDP, *pDPserver[N_SESSIONS];
    DPSESSIONDESC2 dpsd, dpsd_server[N_SESSIONS];
    CallbackData callbackData;
    HRESULT hr;
    UINT i;


    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
    if (FAILED(hr)) return;

    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    callbackData.dwCounter1 = -1; /* So that after a call to EnumSessions
                                     we get the exact number of sessions */
    callbackData.dwFlags = 0;


    /* Service provider not initialized */
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );


    init_TCPIP_provider( pDP, "127.0.0.1", 0 );


    /* Session with no size */
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, 0 );
    todo_wine checkHR( DPERR_INVALIDPARAMS, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "EnumSessions not implemented\n" );
        return;
    }

    dpsd.dwSize = sizeof(DPSESSIONDESC2);


    /* No sessions */
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 );


    dpsd.guidApplication = appGuid;

    /* Set up sessions */
    for (i=0; i<N_SESSIONS; i++)
    {
        memcpy( &dpsd_server[i], &dpsd, sizeof(DPSESSIONDESC2) );
    }

    U1(dpsd_server[0]).lpszSessionNameA = (LPSTR) "normal";
    dpsd_server[0].dwFlags = ( DPSESSION_CLIENTSERVER |
                               DPSESSION_DIRECTPLAYPROTOCOL );
    dpsd_server[0].dwMaxPlayers = 10;

    U1(dpsd_server[1]).lpszSessionNameA = (LPSTR) "full";
    dpsd_server[1].dwFlags = ( DPSESSION_CLIENTSERVER |
                               DPSESSION_DIRECTPLAYPROTOCOL );
    dpsd_server[1].dwMaxPlayers = 1;

    U1(dpsd_server[2]).lpszSessionNameA = (LPSTR) "no new";
    dpsd_server[2].dwFlags = ( DPSESSION_CLIENTSERVER |
                               DPSESSION_DIRECTPLAYPROTOCOL |
                               DPSESSION_NEWPLAYERSDISABLED );
    dpsd_server[2].dwMaxPlayers = 10;

    U1(dpsd_server[3]).lpszSessionNameA = (LPSTR) "no join";
    dpsd_server[3].dwFlags = ( DPSESSION_CLIENTSERVER |
                               DPSESSION_DIRECTPLAYPROTOCOL |
                               DPSESSION_JOINDISABLED );
    dpsd_server[3].dwMaxPlayers = 10;

    U1(dpsd_server[4]).lpszSessionNameA = (LPSTR) "private";
    dpsd_server[4].dwFlags = ( DPSESSION_CLIENTSERVER |
                               DPSESSION_DIRECTPLAYPROTOCOL |
                               DPSESSION_PRIVATE );
    dpsd_server[4].dwMaxPlayers = 10;
    U2(dpsd_server[4]).lpszPasswordA = (LPSTR) "password";

    U1(dpsd_server[5]).lpszSessionNameA = (LPSTR) "protected";
    dpsd_server[5].dwFlags = ( DPSESSION_CLIENTSERVER |
                               DPSESSION_DIRECTPLAYPROTOCOL |
                               DPSESSION_PASSWORDREQUIRED );
    dpsd_server[5].dwMaxPlayers = 10;
    U2(dpsd_server[5]).lpszPasswordA = (LPSTR) "password";


    for (i=0; i<N_SESSIONS; i++)
    {
        pDPserver[i] = create_session( &dpsd_server[i] );
        if (!pDPserver[i]) return;
    }


    /* Invalid params */
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, -1 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    hr = IDirectPlayX_EnumSessions( pDP, NULL, 0, EnumSessions_cb,
                                    &callbackData, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    check( -1, callbackData.dwCounter1 );


    /* Flag tests */
    callbackData.dwFlags = DPENUMSESSIONS_ALL; /* Doesn't list private,
                                                  protected */
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( N_SESSIONS-2, callbackData.dwCounter1 );

    /* Doesn't list private */
    callbackData.dwFlags = ( DPENUMSESSIONS_ALL |
                             DPENUMSESSIONS_PASSWORDREQUIRED );
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( N_SESSIONS-1, callbackData.dwCounter1 );

    /* Doesn't list full, no new, no join, private, protected */
    callbackData.dwFlags = DPENUMSESSIONS_AVAILABLE;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( N_SESSIONS-5, callbackData.dwCounter1 );

    /* Like with DPENUMSESSIONS_AVAILABLE */
    callbackData.dwFlags = 0;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( N_SESSIONS-5, callbackData.dwCounter1 );

    /* Doesn't list full, no new, no join, private */
    callbackData.dwFlags = DPENUMSESSIONS_PASSWORDREQUIRED;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( N_SESSIONS-4, callbackData.dwCounter1 );


    /* Async enumeration */
    callbackData.dwFlags = DPENUMSESSIONS_ASYNC;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( N_SESSIONS-4, callbackData.dwCounter1 ); /* Read cache of last
                                                       sync enumeration */

    callbackData.dwFlags = DPENUMSESSIONS_STOPASYNC;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 ); /* Stop enumeration */

    callbackData.dwFlags = DPENUMSESSIONS_ASYNC;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 ); /* Start enumeration */

    Sleep(500); /* Give time to fill the cache */

    callbackData.dwFlags = DPENUMSESSIONS_ASYNC;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( N_SESSIONS-5, callbackData.dwCounter1 ); /* Retrieve results */

    callbackData.dwFlags = DPENUMSESSIONS_STOPASYNC;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 ); /* Stop enumeration */


    /* Specific tests for passworded sessions */

    for (i=0; i<N_SESSIONS; i++)
    {
        IDirectPlayX_Release( pDPserver[i] );
    }

    /* - Only session password set */
    for (i=4;i<=5;i++)
    {
        U2(dpsd_server[i]).lpszPasswordA = (LPSTR) "password";
        dpsd_server[i].dwFlags = 0;
        pDPserver[i] = create_session( &dpsd_server[i] );
    }

    callbackData.dwFlags = 0;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwFlags = DPENUMSESSIONS_PASSWORDREQUIRED;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 ); /* Both sessions automatically
                                            set DPSESSION_PASSWORDREQUIRED */

    /* - Only session flag set */
    for (i=4; i<=5; i++)
    {
        IDirectPlayX_Release( pDPserver[i] );
        U2(dpsd_server[i]).lpszPasswordA = NULL;
    }
    dpsd_server[4].dwFlags = DPSESSION_PRIVATE;
    dpsd_server[5].dwFlags = DPSESSION_PASSWORDREQUIRED;
    for (i=4; i<=5; i++)
    {
        pDPserver[i] = create_session( &dpsd_server[i] );
    }

    callbackData.dwFlags = 0;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 ); /* Without password,
                                            the flag is ignored */

    /* - Both session flag and password set */
    for (i=4; i<=5; i++)
    {
        IDirectPlayX_Release( pDPserver[i] );
        U2(dpsd_server[i]).lpszPasswordA = (LPSTR) "password";
    }
    dpsd_server[4].dwFlags = DPSESSION_PRIVATE;
    dpsd_server[5].dwFlags = DPSESSION_PASSWORDREQUIRED;
    for (i=4; i<=5; i++)
    {
        pDPserver[i] = create_session( &dpsd_server[i] );
    }

    /* - Listing without password */
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwFlags = DPENUMSESSIONS_PASSWORDREQUIRED;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 );

    /* - Listing with incorrect password */
    U2(dpsd).lpszPasswordA = (LPSTR) "bad_password";
    callbackData.dwFlags = 0;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwFlags = DPENUMSESSIONS_PASSWORDREQUIRED;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 );

    /* - Listing with  correct password */
    U2(dpsd).lpszPasswordA = (LPSTR) "password";
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 );


    U2(dpsd).lpszPasswordA = NULL;
    callbackData.dwFlags = DPENUMSESSIONS_ASYNC;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 ); /* Read cache of last sync enumeration,
                                            even private sessions */


    /* GUID tests */

    /* - Creating two servers with different application GUIDs */
    for (i=4; i<=5; i++)
    {
        IDirectPlayX_Release( pDPserver[i] );
        dpsd_server[i].dwFlags = ( DPSESSION_CLIENTSERVER |
                                   DPSESSION_DIRECTPLAYPROTOCOL );
        U2(dpsd_server[i]).lpszPasswordA = NULL;
        dpsd_server[i].dwMaxPlayers = 10;
    }
    U1(dpsd_server[4]).lpszSessionNameA = (LPSTR) "normal1";
    dpsd_server[4].guidApplication = appGuid;
    U1(dpsd_server[5]).lpszSessionNameA = (LPSTR) "normal2";
    dpsd_server[5].guidApplication = appGuid2;
    for (i=4; i<=5; i++)
    {
        pDPserver[i] = create_session( &dpsd_server[i] );
    }

    callbackData.dwFlags = 0;

    dpsd.guidApplication = appGuid2;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 ); /* Only one of the sessions */

    dpsd.guidApplication = appGuid;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 ); /* The other session */
    /* FIXME:
       For some reason, if we enum 1st with appGuid and 2nd with appGuid2,
       in the second enum we get the 2 sessions. Dplay fault? Elves? */

    dpsd.guidApplication = GUID_NULL;
    callbackData.dwCounter1 = -1;
    hr = IDirectPlayX_EnumSessions( pDP, &dpsd, 0, EnumSessions_cb,
                                    &callbackData, callbackData.dwFlags );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 ); /* Both sessions */

    for (i=4; i<=5; i++)
    {
        IDirectPlayX_Release( pDPserver[i] );
    }
    IDirectPlayX_Release( pDP );

}

/* SetSessionDesc
   GetSessionDesc */

static void test_SessionDesc(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    LPDPSESSIONDESC2 lpData[2];
    LPVOID lpDataMsg;
    DPID dpid[2];
    DWORD dwDataSize;
    HRESULT hr;
    UINT i;
    CallbackData callbackData;


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );

    /* Service provider not initialized */
    hr = IDirectPlayX_SetSessionDesc( pDP[0], NULL, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );

    hr = IDirectPlayX_GetSessionDesc( pDP[0], NULL, NULL );
    checkHR( DPERR_UNINITIALIZED, hr );


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );


    /* No sessions open */
    hr = IDirectPlayX_SetSessionDesc( pDP[0], NULL, 0 );
    todo_wine checkHR( DPERR_NOSESSIONS, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip("Get/SetSessionDesc not implemented\n");
        return;
    }

    hr = IDirectPlayX_GetSessionDesc( pDP[0], NULL, NULL );
    checkHR( DPERR_NOSESSIONS, hr );


    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;


    /* Host */
    IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    /* Peer */
    IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                               pDP[1], 0 );

    for (i=0; i<2; i++)
    {
        /* Players, only to receive messages */
        IDirectPlayX_CreatePlayer( pDP[i], &dpid[i], NULL, NULL, NULL, 0, 0 );

        lpData[i] = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 );
    }
    lpDataMsg = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 );


    /* Incorrect parameters */
    hr = IDirectPlayX_SetSessionDesc( pDP[0], NULL, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_GetSessionDesc( pDP[0], NULL, NULL );
    checkHR( DPERR_INVALIDPARAM, hr );
if(0)
{
    /* Crashes under Win7 */
    hr = IDirectPlayX_GetSessionDesc( pDP[0], lpData[0], NULL );
    checkHR( DPERR_INVALIDPARAM, hr );
    dwDataSize=-1;
    hr = IDirectPlayX_GetSessionDesc( pDP[0], lpData[0], &dwDataSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check( -1, dwDataSize );
}

    /* Get: Insufficient buffer size */
    dwDataSize=0;
    hr = IDirectPlayX_GetSessionDesc( pDP[0], lpData[0], &dwDataSize );
    checkHR( DPERR_BUFFERTOOSMALL, hr );
    check( dpsd.dwSize, dwDataSize );
    dwDataSize=4;
    hr = IDirectPlayX_GetSessionDesc( pDP[0], lpData[0], &dwDataSize );
    checkHR( DPERR_BUFFERTOOSMALL, hr );
    check( dpsd.dwSize, dwDataSize );
    dwDataSize=1024;
    hr = IDirectPlayX_GetSessionDesc( pDP[0], NULL, &dwDataSize );
    checkHR( DPERR_BUFFERTOOSMALL, hr );
    check( dpsd.dwSize, dwDataSize );

    /* Get: Regular operation
     *  i=0: Local session
     *  i=1: Remote session */
    for (i=0; i<2; i++)
    {
        hr = IDirectPlayX_GetSessionDesc( pDP[i], lpData[i], &dwDataSize );
        checkHR( DP_OK, hr );
        check( sizeof(DPSESSIONDESC2), dwDataSize );
        check( sizeof(DPSESSIONDESC2), lpData[i]->dwSize );
        checkGuid( &appGuid, &lpData[i]->guidApplication );
        check( dpsd.dwMaxPlayers, lpData[i]->dwMaxPlayers );
    }

    checkGuid( &lpData[0]->guidInstance, &lpData[1]->guidInstance );

    /* Set: Regular operation */
    U1(dpsd).lpszSessionNameA = (LPSTR) "Wahaa";
    hr = IDirectPlayX_SetSessionDesc( pDP[0], &dpsd, 0 );
    checkHR( DP_OK, hr );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetSessionDesc( pDP[1], lpData[1], &dwDataSize );
    checkHR( DP_OK, hr );
    checkStr( U1(dpsd).lpszSessionNameA, U1(*lpData[1]).lpszSessionNameA );


    /* Set: Failing to modify a remote session */
    hr = IDirectPlayX_SetSessionDesc( pDP[1], &dpsd, 0 );
    checkHR( DPERR_ACCESSDENIED, hr );

    /* Trying to change immutable properties */
    /*  Flags */
    hr = IDirectPlayX_SetSessionDesc( pDP[0], &dpsd, 0 );
    checkHR( DP_OK, hr );
    dpsd.dwFlags = DPSESSION_SECURESERVER;
    hr = IDirectPlayX_SetSessionDesc( pDP[0], &dpsd, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );
    dpsd.dwFlags = 0;
    hr = IDirectPlayX_SetSessionDesc( pDP[0], &dpsd, 0 );
    checkHR( DP_OK, hr );
    /*  Size */
    dpsd.dwSize = 2048;
    hr = IDirectPlayX_SetSessionDesc( pDP[0], &dpsd, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    hr = IDirectPlayX_SetSessionDesc( pDP[0], &dpsd, 0 );
    checkHR( DP_OK, hr );

    /* Changing the GUIDs and size is ignored */
    dpsd.guidApplication = appGuid2;
    hr = IDirectPlayX_SetSessionDesc( pDP[0], &dpsd, 0 );
    checkHR( DP_OK, hr );
    dpsd.guidInstance = appGuid2;
    hr = IDirectPlayX_SetSessionDesc( pDP[0], &dpsd, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_GetSessionDesc( pDP[0], lpData[0], &dwDataSize );
    checkHR( DP_OK, hr );
    checkGuid( &appGuid, &lpData[0]->guidApplication );
    checkGuid( &lpData[1]->guidInstance, &lpData[0]->guidInstance );
    check( sizeof(DPSESSIONDESC2), lpData[0]->dwSize );


    /* Checking system messages */
    check_messages( pDP[0], dpid, 2, &callbackData );
    checkStr( "S0,S0,S0,S0,S0,S0,S0,", callbackData.szTrace1 );
    checkStr( "48,90,90,90,90,90,90,", callbackData.szTrace2 );
    check_messages( pDP[1], dpid, 2, &callbackData );
    checkStr( "S1,S1,S1,S1,S1,S1,", callbackData.szTrace1 );
    checkStr( "90,90,90,90,90,90,", callbackData.szTrace2 );

    HeapFree( GetProcessHeap(), 0, lpDataMsg );
    for (i=0; i<2; i++)
    {
        HeapFree( GetProcessHeap(), 0, lpData[i] );
        IDirectPlayX_Release( pDP[i] );
    }

}

/* CreatePlayer */

static void test_CreatePlayer(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPNAME name;
    DPID dpid;
    HRESULT hr;


    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP[0] );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
    if (FAILED(hr)) return;

    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP[1] );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
    if (FAILED(hr)) return;

    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    ZeroMemory( &name, sizeof(DPNAME) );


    /* Connection not initialized */
    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL, 0, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );


    /* Session not open */
    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL, 0, 0 );
    todo_wine checkHR( DPERR_INVALIDPARAMS, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "CreatePlayer not implemented\n" );
        return;
    }

    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );


    /* Player name */
    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );


    name.dwSize = -1;


    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, &name, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );


    name.dwSize = sizeof(DPNAME);
    U1(name).lpszShortNameA = (LPSTR) "test";
    U2(name).lpszLongNameA = NULL;


    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, &name, NULL, NULL,
                                    0, 0 );
    checkHR( DP_OK, hr );


    /* Null dpid */
    hr = IDirectPlayX_CreatePlayer( pDP[0], NULL, NULL, NULL, NULL,
                                    0, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );


    /* There can only be one server player */
    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, DPPLAYER_SERVERPLAYER );
    checkHR( DP_OK, hr );
    check( DPID_SERVERPLAYER, dpid );

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, DPPLAYER_SERVERPLAYER );
    checkHR( DPERR_CANTCREATEPLAYER, hr );

    IDirectPlayX_DestroyPlayer( pDP[0], dpid );

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, DPPLAYER_SERVERPLAYER );
    checkHR( DP_OK, hr );
    check( DPID_SERVERPLAYER, dpid );
    IDirectPlayX_DestroyPlayer( pDP[0], dpid );


    /* Flags */
    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, DPPLAYER_SERVERPLAYER );
    checkHR( DP_OK, hr );
    check( DPID_SERVERPLAYER, dpid );
    IDirectPlayX_DestroyPlayer( pDP[0], dpid );

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, DPPLAYER_SPECTATOR );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, ( DPPLAYER_SERVERPLAYER |
                                         DPPLAYER_SPECTATOR ) );
    checkHR( DP_OK, hr );
    check( DPID_SERVERPLAYER, dpid );
    IDirectPlayX_DestroyPlayer( pDP[0], dpid );


    /* Session with DPSESSION_NEWPLAYERSDISABLED */
    IDirectPlayX_Close( pDP[0] );
    dpsd.dwFlags = DPSESSION_NEWPLAYERSDISABLED;
    hr = IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    checkHR( DP_OK, hr );


    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, 0 );
    checkHR( DPERR_CANTCREATEPLAYER, hr );

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, DPPLAYER_SERVERPLAYER );
    checkHR( DPERR_CANTCREATEPLAYER, hr );

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, DPPLAYER_SPECTATOR );
    checkHR( DPERR_CANTCREATEPLAYER, hr );


    /* Creating players in a Client/Server session */
    IDirectPlayX_Close( pDP[0] );
    dpsd.dwFlags = DPSESSION_CLIENTSERVER;
    hr = IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                                    pDP[1], 0 );
    checkHR( DP_OK, hr );


    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, 0 );
    checkHR( DPERR_ACCESSDENIED, hr );

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid, NULL, NULL, NULL,
                                    0, DPPLAYER_SERVERPLAYER );
    checkHR( DP_OK, hr );
    check( DPID_SERVERPLAYER, dpid );

    hr = IDirectPlayX_CreatePlayer( pDP[1], &dpid, NULL, NULL, NULL,
                                    0, DPPLAYER_SERVERPLAYER );
    checkHR( DPERR_INVALIDFLAGS, hr );

    hr = IDirectPlayX_CreatePlayer( pDP[1], &dpid, NULL, NULL, NULL,
                                    0, 0 );
    checkHR( DP_OK, hr );


    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

/* GetPlayerCaps */

static void test_GetPlayerCaps(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID dpid[2];
    HRESULT hr;
    UINT i;

    DPCAPS playerCaps;
    DWORD dwFlags;


    for (i=0; i<2; i++)
    {
        hr= CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                              &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;

    ZeroMemory( &playerCaps, sizeof(DPCAPS) );


    /* Uninitialized service provider */
    playerCaps.dwSize = 0;
    hr = IDirectPlayX_GetPlayerCaps( pDP[0], 0, &playerCaps, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );

    playerCaps.dwSize = sizeof(DPCAPS);
    hr = IDirectPlayX_GetPlayerCaps( pDP[0], 0, &playerCaps, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );


    /* No session */
    playerCaps.dwSize = 0;

    hr = IDirectPlayX_GetPlayerCaps( pDP[0], 0, &playerCaps, 0 );
    todo_wine checkHR( DPERR_INVALIDPARAMS, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "GetPlayerCaps not implemented\n" );
        return;
    }

    playerCaps.dwSize = sizeof(DPCAPS);

    hr = IDirectPlayX_GetPlayerCaps( pDP[0], 0, &playerCaps, 0 );
    checkHR( DPERR_INVALIDPLAYER, hr );

    hr = IDirectPlayX_GetPlayerCaps( pDP[0], 2, &playerCaps, 0 );
    checkHR( DPERR_INVALIDPLAYER, hr );


    hr = IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                                    pDP[1], 0 );
    checkHR( DP_OK, hr );

    for (i=0; i<2; i++)
    {
        hr = IDirectPlayX_CreatePlayer( pDP[i], &dpid[i],
                                        NULL, NULL, NULL, 0, 0 );
        checkHR( DP_OK, hr );
    }


    /* Uninitialized playerCaps */
    playerCaps.dwSize = 0;

    hr = IDirectPlayX_GetPlayerCaps( pDP[0], 0, &playerCaps, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    hr = IDirectPlayX_GetPlayerCaps( pDP[0], 2, &playerCaps, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    hr = IDirectPlayX_GetPlayerCaps( pDP[0], dpid[0], &playerCaps, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    /* Invalid player */
    playerCaps.dwSize = sizeof(DPCAPS);

    hr = IDirectPlayX_GetPlayerCaps( pDP[0], 0, &playerCaps, 0 );
    checkHR( DPERR_INVALIDPLAYER, hr );

    hr = IDirectPlayX_GetPlayerCaps( pDP[0], 2, &playerCaps, 0 );
    checkHR( DPERR_INVALIDPLAYER, hr );

    hr = IDirectPlayX_GetPlayerCaps( pDP[0], dpid[0], &playerCaps, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_GetPlayerCaps( pDP[0], dpid[0], NULL, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    /* Regular parameters */
    for (i=0; i<2; i++)
    {
        for (dwFlags=0;
             dwFlags<=DPGETCAPS_GUARANTEED;
             dwFlags+=DPGETCAPS_GUARANTEED)
        {

            hr = IDirectPlayX_GetPlayerCaps( pDP[0], dpid[i],
                                             &playerCaps, dwFlags );
            checkHR( DP_OK, hr );


            check( sizeof(DPCAPS), playerCaps.dwSize );
            check( 40,    playerCaps.dwSize );
            check( 0,     playerCaps.dwMaxQueueSize );
            check( 0,     playerCaps.dwHundredBaud );
            check( 0,     playerCaps.dwLatency );
            check( 65536, playerCaps.dwMaxLocalPlayers );
            check( 20,    playerCaps.dwHeaderLength );

            if ( i == 0 )
            {
                checkFlags( DPCAPS_ISHOST |
                            DPCAPS_GUARANTEEDOPTIMIZED |
                            DPCAPS_GUARANTEEDSUPPORTED |
                            DPCAPS_ASYNCSUPPORTED |
                            DPPLAYERCAPS_LOCAL,
                            playerCaps.dwFlags, FLAGS_DPCAPS );
            }
            else
                checkFlags( DPCAPS_ISHOST |
                            DPCAPS_GUARANTEEDOPTIMIZED |
                            DPCAPS_GUARANTEEDSUPPORTED |
                            DPCAPS_ASYNCSUPPORTED,
                            playerCaps.dwFlags, FLAGS_DPCAPS );

            if ( dwFlags == DPGETCAPS_GUARANTEED )
            {
                check( 1048547, playerCaps.dwMaxBufferSize );
                check( 64,      playerCaps.dwMaxPlayers );
            }
            else
            {
                check( 65479, playerCaps.dwMaxBufferSize );
                check( 65536, playerCaps.dwMaxPlayers );
            }

        }
    }


    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

/* SetPlayerData
   GetPlayerData */

static void test_PlayerData(void)
{
    IDirectPlay4 *pDP;
    DPSESSIONDESC2 dpsd;
    DPID dpid;
    HRESULT hr;

    /* lpDataFake has to be bigger than the rest, limits lpDataGet size */
    LPCSTR lpDataFake     = "big_fake_data_chunk";
    DWORD dwDataSizeFake  = strlen(lpDataFake)+1;

    LPCSTR lpData         = "remote_data";
    DWORD dwDataSize      = strlen(lpData)+1;

    LPCSTR lpDataLocal    = "local_data";
    DWORD dwDataSizeLocal = strlen(lpDataLocal)+1;

    LPSTR lpDataGet       = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY,
                                       dwDataSizeFake );
    DWORD dwDataSizeGet   = dwDataSizeFake;


    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
    if (FAILED(hr)) return;

    /* No service provider */
    hr = IDirectPlayX_SetPlayerData( pDP, 0, (LPVOID) lpData,
                                     dwDataSize, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );

    hr = IDirectPlayX_GetPlayerData( pDP, 0, lpDataGet, &dwDataSizeGet, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );


    init_TCPIP_provider( pDP, "127.0.0.1", 0 );

    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    IDirectPlayX_Open( pDP, &dpsd, DPOPEN_CREATE );


    /* Invalid player */
    hr = IDirectPlayX_SetPlayerData( pDP, 0, (LPVOID) lpData,
                                     dwDataSize, 0 );
    todo_wine checkHR( DPERR_INVALIDPLAYER, hr );

    hr = IDirectPlayX_GetPlayerData( pDP, 0, lpDataGet, &dwDataSizeGet, 0 );
    todo_wine checkHR( DPERR_INVALIDPLAYER, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "Get/SetPlayerData not implemented\n" );
        return;
    }

    /* Create the player */
    /* By default, the data is remote */
    hr = IDirectPlayX_CreatePlayer( pDP, &dpid, NULL, NULL, (LPVOID) lpData,
                                    dwDataSize, 0 );
    checkHR( DP_OK, hr );

    /* Invalid parameters */
    hr = IDirectPlayX_SetPlayerData( pDP, dpid, NULL, dwDataSize, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_SetPlayerData( pDP, dpid, lpDataGet, -1, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, NULL, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );


    /*
     * Remote data (default)
     */


    /* Buffer redimension */
    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, NULL,
                                     &dwDataSizeGet, 0 );
    check( DPERR_BUFFERTOOSMALL, hr );
    check( dwDataSize, dwDataSizeGet );
    checkStr( lpDataFake, lpDataGet );

    dwDataSizeGet = 2;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet, 0 );
    check( DPERR_BUFFERTOOSMALL, hr );
    check( dwDataSize, dwDataSizeGet );

    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet, 0 );
    checkHR( DP_OK, hr );
    check( dwDataSize, dwDataSizeGet );
    checkStr( lpData, lpDataGet );

    /* Normal operation */
    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet, 0 );
    checkHR( DP_OK, hr );
    check( dwDataSize, dwDataSizeGet );
    checkStr( lpData, lpDataGet );

    /* Flag tests */
    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet, 0 );
    checkHR( DP_OK, hr );
    check( dwDataSize, dwDataSizeGet ); /* Remote: works as expected */
    checkStr( lpData, lpDataGet );

    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet,
                                     DPGET_REMOTE );
    checkHR( DP_OK, hr );
    check( dwDataSize, dwDataSizeGet ); /* Same behaviour as in previous test */
    checkStr( lpData, lpDataGet );

    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet,
                                     DPGET_LOCAL );
    checkHR( DP_OK, hr );
    check( 0, dwDataSizeGet ); /* Sets size to 0 (as local data doesn't exist) */
    checkStr( lpDataFake, lpDataGet );

    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet,
                                     DPGET_LOCAL | DPGET_REMOTE );
    checkHR( DP_OK, hr );
    check( 0, dwDataSizeGet ); /* Same behaviour as in previous test */
    checkStr( lpDataFake, lpDataGet );

    /* Getting local data (which doesn't exist), buffer size is ignored */
    dwDataSizeGet = 0;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet,
                                     DPGET_LOCAL );
    checkHR( DP_OK, hr );
    check( 0, dwDataSizeGet ); /* Sets size to 0 */
    checkStr( lpDataFake, lpDataGet );

    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, NULL, &dwDataSizeGet,
                                     DPGET_LOCAL );
    checkHR( DP_OK, hr );
    check( 0, dwDataSizeGet ); /* Sets size to 0 */
    checkStr( lpDataFake, lpDataGet );


    /*
     * Local data
     */


    /* Invalid flags */
    hr = IDirectPlayX_SetPlayerData( pDP, dpid, (LPVOID) lpDataLocal,
                                     dwDataSizeLocal,
                                     DPSET_LOCAL | DPSET_GUARANTEED );
    checkHR( DPERR_INVALIDPARAMS, hr );

    /* Correct parameters */
    hr = IDirectPlayX_SetPlayerData( pDP, dpid, (LPVOID) lpDataLocal,
                                     dwDataSizeLocal, DPSET_LOCAL );
    checkHR( DP_OK, hr );

    /* Flag tests (again) */
    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet, 0 );
    checkHR( DP_OK, hr );
    check( dwDataSize, dwDataSizeGet ); /* Remote: works as expected */
    checkStr( lpData, lpDataGet );

    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet,
                                     DPGET_REMOTE );
    checkHR( DP_OK, hr );
    check( dwDataSize, dwDataSizeGet ); /* Like in previous test */
    checkStr( lpData, lpDataGet );

    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet,
                                     DPGET_LOCAL );
    checkHR( DP_OK, hr );
    check( dwDataSizeLocal, dwDataSizeGet ); /* Local: works as expected */
    checkStr( lpDataLocal, lpDataGet );

    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet,
                                     DPGET_LOCAL | DPGET_REMOTE );
    checkHR( DP_OK, hr );
    check( dwDataSizeLocal, dwDataSizeGet ); /* Like in previous test */
    checkStr( lpDataLocal, lpDataGet );

    /* Small buffer works as expected again */
    dwDataSizeGet = 0;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet,
                                     DPGET_LOCAL );
    checkHR( DPERR_BUFFERTOOSMALL, hr );
    check( dwDataSizeLocal, dwDataSizeGet );
    checkStr( lpDataFake, lpDataGet );

    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, NULL,
                                     &dwDataSizeGet, DPGET_LOCAL );
    check( DPERR_BUFFERTOOSMALL, hr );
    check( dwDataSizeLocal, dwDataSizeGet );
    checkStr( lpDataFake, lpDataGet );


    /*
     * Changing remote data
     */


    /* Remote data := local data */
    hr = IDirectPlayX_SetPlayerData( pDP, dpid, (LPVOID) lpDataLocal,
                                     dwDataSizeLocal,
                                     DPSET_GUARANTEED | DPSET_REMOTE );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_SetPlayerData( pDP, dpid, (LPVOID) lpDataLocal,
                                     dwDataSizeLocal, 0 );
    checkHR( DP_OK, hr );

    dwDataSizeGet = dwDataSizeFake;
    strcpy(lpDataGet, lpDataFake);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet, 0 );
    checkHR( DP_OK, hr );
    check( dwDataSizeLocal, dwDataSizeGet );
    checkStr( lpDataLocal, lpDataGet );

    /* Remote data := fake data */
    hr = IDirectPlayX_SetPlayerData( pDP, dpid, (LPVOID) lpDataFake,
                                     dwDataSizeFake, DPSET_REMOTE );
    checkHR( DP_OK, hr );

    dwDataSizeGet = dwDataSizeFake + 1;
    strcpy(lpDataGet, lpData);
    hr = IDirectPlayX_GetPlayerData( pDP, dpid, lpDataGet, &dwDataSizeGet, 0 );
    checkHR( DP_OK, hr );
    check( dwDataSizeFake, dwDataSizeGet );
    checkStr( lpDataFake, lpDataGet );


    HeapFree( GetProcessHeap(), 0, lpDataGet );
    IDirectPlayX_Release( pDP );
}

/* GetPlayerName
   SetPlayerName */

static void test_PlayerName(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID dpid[2];
    HRESULT hr;
    UINT i;

    DPNAME playerName;
    DWORD dwDataSize = 1024;
    LPVOID lpData = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwDataSize );
    CallbackData callbackData;


    for (i=0; i<2; i++)
    {
        hr= CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                              &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    ZeroMemory( &playerName, sizeof(DPNAME) );


    /* Service provider not initialized */
    hr = IDirectPlayX_SetPlayerName( pDP[0], 0, &playerName, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[0], 0, lpData, &dwDataSize );
    checkHR( DPERR_UNINITIALIZED, hr );
    check( 1024, dwDataSize );


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );


    /* Session not initialized */
    hr = IDirectPlayX_SetPlayerName( pDP[0], 0, &playerName, 0 );
    todo_wine checkHR( DPERR_INVALIDPLAYER, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "Get/SetPlayerName not implemented\n" );
        return;
    }

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[0], 0, lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( 1024, dwDataSize );


    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;
    IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                               pDP[1], 0 );

    IDirectPlayX_CreatePlayer( pDP[0], &dpid[0], NULL, NULL, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP[1], &dpid[1], NULL, NULL, NULL, 0, 0 );


    /* Name not initialized */
    playerName.dwSize = -1;
    hr = IDirectPlayX_SetPlayerName( pDP[0], dpid[0], &playerName, 0 );
    checkHR( DP_OK, hr );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[0], 0, lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( 1024, dwDataSize );


    playerName.dwSize = sizeof(DPNAME);
    U1(playerName).lpszShortNameA = (LPSTR) "player_name";
    U2(playerName).lpszLongNameA = (LPSTR) "player_long_name";


    /* Invalid parameters */
    hr = IDirectPlayX_SetPlayerName( pDP[0], -1, &playerName, 0 );
    checkHR( DPERR_INVALIDPLAYER, hr );
    hr = IDirectPlayX_SetPlayerName( pDP[0], 0, &playerName, 0 );
    checkHR( DPERR_INVALIDPLAYER, hr );
    hr = IDirectPlayX_SetPlayerName( pDP[0], dpid[0], &playerName, -1 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[0], 0, lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( 1024, dwDataSize );

if(0)
{
    /* Crashes under Win7 */
    dwDataSize = -1;
    hr = IDirectPlayX_GetPlayerName( pDP[0], dpid[0], lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check( -1, dwDataSize );
}

    hr = IDirectPlayX_GetPlayerName( pDP[0], dpid[0], lpData, NULL );
    checkHR( DPERR_INVALIDPARAMS, hr );

    /* Trying to modify remote player */
    hr = IDirectPlayX_SetPlayerName( pDP[0], dpid[1], &playerName, 0 );
    checkHR( DPERR_ACCESSDENIED, hr );


    /* Regular operation */
    hr = IDirectPlayX_SetPlayerName( pDP[0], dpid[0], &playerName, 0 );
    checkHR( DP_OK, hr );
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[0], dpid[0], lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    check( 45, dwDataSize );
    checkStr( U1(playerName).lpszShortNameA, U1(*(LPDPNAME)lpData).lpszShortNameA );
    checkStr( U2(playerName).lpszLongNameA,  U2(*(LPDPNAME)lpData).lpszLongNameA );
    check( 0,                            ((LPDPNAME)lpData)->dwFlags );

    hr = IDirectPlayX_SetPlayerName( pDP[0], dpid[0], NULL, 0 );
    checkHR( DP_OK, hr );
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[0], dpid[0], lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    check( 16, dwDataSize );
    checkLP( NULL, U1(*(LPDPNAME)lpData).lpszShortNameA );
    checkLP( NULL, U2(*(LPDPNAME)lpData).lpszLongNameA );
    check( 0,      ((LPDPNAME)lpData)->dwFlags );


    /* Small buffer in get operation */
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[0], dpid[0], NULL, &dwDataSize );
    checkHR( DPERR_BUFFERTOOSMALL, hr );
    check( 16, dwDataSize );

    dwDataSize = 0;
    hr = IDirectPlayX_GetPlayerName( pDP[0], dpid[0], lpData, &dwDataSize );
    checkHR( DPERR_BUFFERTOOSMALL, hr );
    check( 16, dwDataSize );

    hr = IDirectPlayX_GetPlayerName( pDP[0], dpid[0], lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    check( 16, dwDataSize );
    checkLP( NULL, U1(*(LPDPNAME)lpData).lpszShortNameA );
    checkLP( NULL, U2(*(LPDPNAME)lpData).lpszLongNameA );
    check( 0, ((LPDPNAME)lpData)->dwFlags );


    /* Flags */
    hr = IDirectPlayX_SetPlayerName( pDP[0], dpid[0], &playerName,
                                     DPSET_GUARANTEED );
    checkHR( DP_OK, hr );
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[0], dpid[0], lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    check( 45, dwDataSize );
    checkStr( U1(playerName).lpszShortNameA, U1(*(LPDPNAME)lpData).lpszShortNameA );
    checkStr( U2(playerName).lpszLongNameA,  U2(*(LPDPNAME)lpData).lpszLongNameA );
    check( 0, ((LPDPNAME)lpData)->dwFlags );

    /* - Local (no propagation) */
    U1(playerName).lpszShortNameA = (LPSTR) "no_propagation";
    hr = IDirectPlayX_SetPlayerName( pDP[0], dpid[0], &playerName,
                                     DPSET_LOCAL );
    checkHR( DP_OK, hr );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[0], dpid[0],
                                     lpData, &dwDataSize ); /* Local fetch */
    checkHR( DP_OK, hr );
    check( 48, dwDataSize );
    checkStr( "no_propagation", U1(*(LPDPNAME)lpData).lpszShortNameA );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[1], dpid[0],
                                     lpData, &dwDataSize ); /* Remote fetch */
    checkHR( DP_OK, hr );
    check( 45, dwDataSize );
    checkStr( "player_name", U1(*(LPDPNAME)lpData).lpszShortNameA );

    /* -- 2 */

    U1(playerName).lpszShortNameA = (LPSTR) "no_propagation_2";
    hr = IDirectPlayX_SetPlayerName( pDP[0], dpid[0], &playerName,
                                     DPSET_LOCAL | DPSET_REMOTE );
    checkHR( DP_OK, hr );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[0], dpid[0],
                                     lpData, &dwDataSize ); /* Local fetch */
    checkHR( DP_OK, hr );
    check( 50, dwDataSize );
    checkStr( "no_propagation_2", U1(*(LPDPNAME)lpData).lpszShortNameA );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[1], dpid[0],
                                     lpData, &dwDataSize ); /* Remote fetch */
    checkHR( DP_OK, hr );
    check( 45, dwDataSize );
    checkStr( "player_name", U1(*(LPDPNAME)lpData).lpszShortNameA );

    /* - Remote (propagation, default) */
    U1(playerName).lpszShortNameA = (LPSTR) "propagation";
    hr = IDirectPlayX_SetPlayerName( pDP[0], dpid[0], &playerName,
                                     DPSET_REMOTE );
    checkHR( DP_OK, hr );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[1], dpid[0],
                                     lpData, &dwDataSize ); /* Remote fetch */
    checkHR( DP_OK, hr );
    check( 45, dwDataSize );
    checkStr( "propagation", U1(*(LPDPNAME)lpData).lpszShortNameA );

    /* -- 2 */
    U1(playerName).lpszShortNameA = (LPSTR) "propagation_2";
    hr = IDirectPlayX_SetPlayerName( pDP[0], dpid[0], &playerName,
                                     0 );
    checkHR( DP_OK, hr );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerName( pDP[1], dpid[0],
                                     lpData, &dwDataSize ); /* Remote fetch */
    checkHR( DP_OK, hr );
    check( 47, dwDataSize );
    checkStr( "propagation_2", U1(*(LPDPNAME)lpData).lpszShortNameA );


    /* Checking system messages */
    check_messages( pDP[0], dpid, 2, &callbackData );
    checkStr( "S0,S0,S0,S0,S0,S0,S0,", callbackData.szTrace1 );
    checkStr( "48,28,57,28,57,57,59,", callbackData.szTrace2 );
    check_messages( pDP[1], dpid, 2, &callbackData );
    checkStr( "S1,S1,S1,S1,S1,S1,", callbackData.szTrace1 );
    checkStr( "28,57,28,57,57,59,", callbackData.szTrace2 );


    HeapFree( GetProcessHeap(), 0, lpData );
    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

/* GetPlayerAccount */

static BOOL CALLBACK EnumSessions_cb_join_secure( LPCDPSESSIONDESC2 lpThisSD,
                                                  LPDWORD lpdwTimeOut,
                                                  DWORD dwFlags,
                                                  LPVOID lpContext )
{
    IDirectPlay4 *pDP = lpContext;
    DPSESSIONDESC2 dpsd;
    DPCREDENTIALS dpCredentials;
    HRESULT hr;

    if (dwFlags & DPESC_TIMEDOUT)
    {
        return FALSE;
    }

    checkFlags( DPSESSION_SECURESERVER, lpThisSD->dwFlags, FLAGS_DPSESSION );

    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.guidInstance = lpThisSD->guidInstance;

    ZeroMemory( &dpCredentials, sizeof(DPCREDENTIALS) );
    dpCredentials.dwSize = sizeof(DPCREDENTIALS);
    U1(dpCredentials).lpszUsernameA = (LPSTR) "user";
    U2(dpCredentials).lpszPasswordA = (LPSTR) "pass";
    hr = IDirectPlayX_SecureOpen( pDP, &dpsd, DPOPEN_JOIN,
                                  NULL, &dpCredentials );
    checkHR( DPERR_LOGONDENIED, hr ); /* TODO: Make this work */

    return TRUE;
}

static void test_GetPlayerAccount(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID dpid[2];
    HRESULT hr;
    UINT i;

    DWORD dwDataSize = 1024;
    LPVOID lpData = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwDataSize );


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;

    /* Uninitialized service provider */
    hr = IDirectPlayX_GetPlayerAccount( pDP[0], 0, 0, lpData, &dwDataSize );
    todo_wine checkHR( DPERR_UNINITIALIZED, hr );

    if ( hr == DP_OK )
    {
        todo_wine win_skip( "GetPlayerAccount not implemented\n" );
        return;
    }


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );


    /* No session */
    hr = IDirectPlayX_GetPlayerAccount( pDP[0], 0, 0, lpData, &dwDataSize );
    checkHR( DPERR_NOSESSIONS, hr );


    IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                               pDP[1], 0 );

    for (i=0; i<2; i++)
    {
        hr = IDirectPlayX_CreatePlayer( pDP[i], &dpid[i], NULL, NULL, NULL,
                                        0, 0 );
        checkHR( DP_OK, hr );
    }


    /* Session is not secure */
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAccount( pDP[0], dpid[0], 0,
                                        lpData, &dwDataSize );
    checkHR( DPERR_UNSUPPORTED, hr );
    check( 1024, dwDataSize );


    /* Open a secure session */
    for (i=0; i<2; i++)
    {
        hr = IDirectPlayX_Close( pDP[i] );
        checkHR( DP_OK, hr );
    }

    dpsd.dwFlags = DPSESSION_SECURESERVER;
    hr = IDirectPlayX_SecureOpen( pDP[0], &dpsd, DPOPEN_CREATE, NULL, NULL );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid[0],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0,
                                    EnumSessions_cb_join_secure, pDP[1], 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreatePlayer( pDP[1], &dpid[1],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    /* TODO: Player creation so that this works */

    /* Invalid player */
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAccount( pDP[0], 0, 0,
                                        lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( 1024, dwDataSize );

    /* Invalid flags */
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAccount( pDP[0], dpid[0], -1,
                                        lpData, &dwDataSize );
    checkHR( DPERR_INVALIDFLAGS, hr );
    check( 1024, dwDataSize );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAccount( pDP[0], dpid[0], 1,
                                        lpData, &dwDataSize );
    checkHR( DPERR_INVALIDFLAGS, hr );
    check( 1024, dwDataSize );

    /* Small buffer */
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAccount( pDP[0], dpid[0], 0,
                                        NULL, &dwDataSize );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( 0, dwDataSize );

    dwDataSize = 0;
    hr = IDirectPlayX_GetPlayerAccount( pDP[0], dpid[0], 0,
                                        lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( 0, dwDataSize );

    hr = IDirectPlayX_GetPlayerAccount( pDP[0], dpid[0], 0,
                                        lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( 0, dwDataSize );

    /* Normal operation */
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAccount( pDP[0], dpid[0], 0,
                                        lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( 1024, dwDataSize );


    HeapFree( GetProcessHeap(), 0, lpData );
    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

/* GetPlayerAddress */

static BOOL CALLBACK EnumAddress_cb( REFGUID guidDataType,
                                     DWORD dwDataSize,
                                     LPCVOID lpData,
                                     LPVOID lpContext )
{
    lpCallbackData callbackData = lpContext;
    static REFGUID types[] = { &DPAID_TotalSize,
                               &DPAID_ServiceProvider,
                               &DPAID_INet,
                               &DPAID_INetW };
    static DWORD sizes[] = { 4, 16, 12, 24, 4, 16, 10, 20 };


    checkGuid( types[callbackData->dwCounter1%4], guidDataType );
    check( sizes[callbackData->dwCounter1], dwDataSize );

    switch(callbackData->dwCounter1)
    {
    case 0:
        check( 136, *(LPDWORD) lpData );
        break;
    case 4:
        check( 130, *(LPDWORD) lpData );
        break;
    case 1:
    case 5:
        checkGuid( &DPSPGUID_TCPIP, lpData );
        break;
    case 6:
        checkStr( "127.0.0.1", (LPSTR) lpData );
        break;
    default: break;
    }


    callbackData->dwCounter1++;

    return TRUE;
}

static void test_GetPlayerAddress(void)
{

    IDirectPlay4 *pDP[2];
    IDirectPlayLobby3 *pDPL;
    DPSESSIONDESC2 dpsd;
    DPID dpid[2];
    CallbackData callbackData;
    HRESULT hr;
    UINT i;

    DWORD dwDataSize = 1024;
    LPVOID lpData = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwDataSize );


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    hr = CoCreateInstance( &CLSID_DirectPlayLobby, NULL, CLSCTX_ALL,
                           &IID_IDirectPlayLobby3A, (LPVOID*) &pDPL );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlayLobby / IID_IDirectPlayLobby3A failed\n" );
    if (FAILED(hr)) return;

    /* Uninitialized service provider */
    hr = IDirectPlayX_GetPlayerAddress( pDP[0], 0, lpData, &dwDataSize );
    todo_wine checkHR( DPERR_UNINITIALIZED, hr );

    if ( hr == DP_OK )
    {
        todo_wine win_skip( "GetPlayerAddress not implemented\n" );
        goto cleanup;
    }

    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );


    /* No session */
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAddress( pDP[0], 0, lpData, &dwDataSize );
    checkHR( DPERR_UNSUPPORTED, hr );
    check( 1024, dwDataSize );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAddress( pDP[0], 1, lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( 1024, dwDataSize );


    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;
    IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                               pDP[1], 0 );

    for (i=0; i<2; i++)
    {
        hr = IDirectPlayX_CreatePlayer( pDP[i], &dpid[i], NULL, NULL, NULL,
                                        0, 0 );
        checkHR( DP_OK, hr );
    }

    /* Invalid player */
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAddress( pDP[0], 0,
                                        lpData, &dwDataSize );
    checkHR( DPERR_UNSUPPORTED, hr );
    check( 1024, dwDataSize );

    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAddress( pDP[0], 1,
                                        lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( 1024, dwDataSize );

    /* Small buffer */
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAddress( pDP[0], dpid[0],
                                        NULL, &dwDataSize );
    checkHR( DPERR_BUFFERTOOSMALL, hr );
    check( 136, dwDataSize );

    dwDataSize = 0;
    hr = IDirectPlayX_GetPlayerAddress( pDP[0], dpid[0],
                                        lpData, &dwDataSize );
    checkHR( DPERR_BUFFERTOOSMALL, hr );
    check( 136, dwDataSize );

    hr = IDirectPlayX_GetPlayerAddress( pDP[0], dpid[0],
                                        lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    check( 136, dwDataSize );


    /* Regular parameters */
    callbackData.dwCounter1 = 0;

    /* - Local */
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAddress( pDP[0], dpid[0],
                                        lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    check( 136, dwDataSize );

    hr = IDirectPlayLobby_EnumAddress( pDPL, EnumAddress_cb, lpData, dwDataSize,
                                       &callbackData );
    checkHR( DP_OK, hr );

    check( 4, callbackData.dwCounter1 );

    /* - Remote */
    dwDataSize = 1024;
    hr = IDirectPlayX_GetPlayerAddress( pDP[0], dpid[1],
                                        lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    check( 130, dwDataSize );

    hr = IDirectPlayLobby_EnumAddress( pDPL, EnumAddress_cb, lpData, dwDataSize,
                                       &callbackData );
    checkHR( DP_OK, hr );

    check( 8, callbackData.dwCounter1 );


    HeapFree( GetProcessHeap(), 0, lpData );

cleanup:
    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );
    IDirectPlayLobby_Release(pDPL);
}

/* GetPlayerFlags */

static void test_GetPlayerFlags(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID dpid[4];
    HRESULT hr;
    UINT i;

    DWORD dwFlags = 0;


    for (i=0; i<2; i++)
    {
        hr= CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                              &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;

    /* Uninitialized service provider */
    hr = IDirectPlayX_GetPlayerFlags( pDP[0], 0, &dwFlags );
    todo_wine checkHR( DPERR_UNINITIALIZED, hr );

    if ( hr == DP_OK )
    {
        todo_wine win_skip( "GetPlayerFlags not implemented\n" );
        return;
    }

    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );


    /* No session */
    hr = IDirectPlayX_GetPlayerFlags( pDP[0], 0, &dwFlags );
    checkHR( DPERR_INVALIDPLAYER, hr );

    hr = IDirectPlayX_GetPlayerFlags( pDP[0], 1, &dwFlags );
    checkHR( DPERR_INVALIDPLAYER, hr );


    IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                               pDP[1], 0 );

    for (i=0; i<2; i++)
    {
        hr = IDirectPlayX_CreatePlayer( pDP[i], &dpid[i],
                                        NULL, NULL, NULL, 0, 0 );
        checkHR( DP_OK, hr );
    }
    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid[2],
                                    NULL, NULL, NULL,
                                    0, DPPLAYER_SPECTATOR );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid[3],
                                    NULL, NULL, NULL,
                                    0, DPPLAYER_SERVERPLAYER );
    checkHR( DP_OK, hr );


    /* Invalid player */
    hr = IDirectPlayX_GetPlayerFlags( pDP[0], 0, &dwFlags );
    checkHR( DPERR_INVALIDPLAYER, hr );

    hr = IDirectPlayX_GetPlayerFlags( pDP[0], 2, &dwFlags );
    checkHR( DPERR_INVALIDPLAYER, hr );

    /* Invalid parameters */
    hr = IDirectPlayX_GetPlayerFlags( pDP[0], dpid[0], NULL );
    checkHR( DPERR_INVALIDPARAMS, hr );


    /* Regular parameters */
    hr = IDirectPlayX_GetPlayerFlags( pDP[0], dpid[0], &dwFlags );
    checkHR( DP_OK, hr );
    checkFlags( dwFlags, DPPLAYER_LOCAL, FLAGS_DPPLAYER );

    hr = IDirectPlayX_GetPlayerFlags( pDP[1], dpid[1], &dwFlags );
    checkHR( DP_OK, hr );
    checkFlags( dwFlags, DPPLAYER_LOCAL, FLAGS_DPPLAYER );

    hr = IDirectPlayX_GetPlayerFlags( pDP[0], dpid[1], &dwFlags );
    checkHR( DP_OK, hr );
    checkFlags( dwFlags, 0, FLAGS_DPPLAYER );

    hr = IDirectPlayX_GetPlayerFlags( pDP[0], dpid[2], &dwFlags );
    checkHR( DP_OK, hr );
    checkFlags( dwFlags, DPPLAYER_SPECTATOR | DPPLAYER_LOCAL, FLAGS_DPPLAYER );

    hr = IDirectPlayX_GetPlayerFlags( pDP[1], dpid[3], &dwFlags );
    checkHR( DP_OK, hr );
    checkFlags( dwFlags, DPPLAYER_SERVERPLAYER, FLAGS_DPPLAYER );


    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

/* CreateGroup
   CreateGroupInGroup */

static void test_CreateGroup(void)
{

    IDirectPlay4 *pDP;
    DPSESSIONDESC2 dpsd;
    DPID idFrom, idTo, dpid, idGroup, idGroupParent;
    DPNAME groupName;
    HRESULT hr;
    UINT i;

    LPCSTR lpData = "data";
    DWORD dwDataSize = strlen(lpData)+1;
    LPDPMSG_CREATEPLAYERORGROUP lpDataGet = HeapAlloc( GetProcessHeap(),
                                                       HEAP_ZERO_MEMORY,
                                                       1024 );
    DWORD dwDataSizeGet = 1024;
    CallbackData callbackData;


    hr= CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                          &IID_IDirectPlay4A, (LPVOID*) &pDP );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
    if (FAILED(hr)) return;
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;
    ZeroMemory( &groupName, sizeof(DPNAME) );


    /* No service provider */
    hr = IDirectPlayX_CreateGroup( pDP, &idGroup, NULL, NULL, 0, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );

    hr = IDirectPlayX_CreateGroupInGroup( pDP, 0, &idGroup, NULL, NULL, 0, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );



    init_TCPIP_provider( pDP, "127.0.0.1", 0 );


    /* No session */
    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   NULL, NULL, 0, 0 );
    todo_wine checkHR( DPERR_INVALIDPARAMS, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "CreateGroup not implemented\n" );
        return;
    }

    hr = IDirectPlayX_CreateGroupInGroup( pDP, 0, &idGroup,
                                          NULL, NULL, 0, 0 );
    checkHR( DPERR_INVALIDGROUP, hr );

    hr = IDirectPlayX_CreateGroupInGroup( pDP, 2, &idGroup,
                                          NULL, NULL, 0, 0 );
    checkHR( DPERR_INVALIDGROUP, hr );


    hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_CREATE );
    checkHR( DP_OK, hr );
    IDirectPlayX_CreatePlayer( pDP, &dpid,
                               NULL, NULL, NULL, 0, 0 );



    /* With name */
    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroupInGroup( pDP, idGroup, &idGroup,
                                          NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   &groupName, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroupInGroup( pDP, idGroup, &idGroup,
                                          &groupName, NULL, 0, 0 );
    checkHR( DP_OK, hr );


    groupName.dwSize = sizeof(DPNAME);
    U1(groupName).lpszShortNameA = (LPSTR) lpData;


    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   &groupName, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroupInGroup( pDP, idGroup, &idGroup,
                                          &groupName, NULL, 0, 0 );
    checkHR( DP_OK, hr );


    /* Message checking */
    for (i=0; i<6; i++)
    {
        dwDataSizeGet = 1024;
        hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0, lpDataGet,
                                   &dwDataSizeGet );
        checkHR( DP_OK, hr );
        if ( NULL == U1(lpDataGet->dpnName).lpszShortNameA )
        {
            check( 48, dwDataSizeGet );
        }
        else
        {
            check( 48 + dwDataSize, dwDataSizeGet );
            checkStr( lpData, U1(lpDataGet->dpnName).lpszShortNameA );
        }
        check( DPID_SYSMSG, idFrom );
        checkConv( DPSYS_CREATEPLAYERORGROUP, lpDataGet->dwType, dpMsgType2str );
        check( DPPLAYERTYPE_GROUP,            lpDataGet->dwPlayerType );
        checkFlags( DPGROUP_LOCAL,            lpDataGet->dwFlags, FLAGS_DPGROUP );
    }
    check_messages( pDP, &dpid, 1, &callbackData );
    checkStr( "", callbackData.szTrace1 );


    /* With data */
    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   NULL, (LPVOID) lpData, -1, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   NULL, (LPVOID) lpData, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   NULL, NULL, dwDataSize, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   NULL, (LPVOID) lpData, dwDataSize, 0 );
    checkHR( DP_OK, hr );


    hr = IDirectPlayX_CreateGroupInGroup( pDP, idGroup, &idGroup,
                                          NULL, (LPVOID) lpData, -1, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    hr = IDirectPlayX_CreateGroupInGroup( pDP, idGroup, &idGroup,
                                          NULL, (LPVOID) lpData, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroupInGroup( pDP, idGroup, &idGroup,
                                          NULL, NULL, dwDataSize, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );

    hr = IDirectPlayX_CreateGroupInGroup( pDP, idGroup, &idGroup,
                                          NULL, (LPVOID)lpData, dwDataSize, 0 );
    checkHR( DP_OK, hr );


    hr = IDirectPlayX_CreateGroup( pDP, &idGroupParent,
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );


    /* Message checking */
    for (i=0; i<5; i++)
    {
        dwDataSizeGet = 1024;
        hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0, lpDataGet,
                                   &dwDataSizeGet );
        checkHR( DP_OK, hr );
        check( 48 + lpDataGet->dwDataSize, dwDataSizeGet );
        check( DPID_SYSMSG, idFrom );
        checkConv( DPSYS_CREATEPLAYERORGROUP, lpDataGet->dwType, dpMsgType2str );
        check( DPPLAYERTYPE_GROUP,            lpDataGet->dwPlayerType );
        checkFlags( DPGROUP_LOCAL,            lpDataGet->dwFlags, FLAGS_DPGROUP );
    }
    check_messages( pDP, &dpid, 1, &callbackData );
    checkStr( "", callbackData.szTrace1 );


    /* Flags and idGroupParent */
    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   NULL, NULL, 0, DPGROUP_HIDDEN );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   NULL, NULL, 0, DPGROUP_STAGINGAREA );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   NULL, NULL, 0,
                                   DPGROUP_HIDDEN | DPGROUP_STAGINGAREA );
    checkHR( DP_OK, hr );


    hr = IDirectPlayX_CreateGroupInGroup( pDP, idGroupParent, &idGroup,
                                          NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroupInGroup( pDP, idGroupParent, &idGroup,
                                          NULL, NULL, 0, DPGROUP_HIDDEN );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroupInGroup( pDP, idGroupParent, &idGroup,
                                          NULL, NULL, 0, DPGROUP_STAGINGAREA );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroupInGroup( pDP, idGroupParent, &idGroup,
                                          NULL, NULL, 0,
                                          DPGROUP_HIDDEN |
                                          DPGROUP_STAGINGAREA );
    checkHR( DP_OK, hr );


    /* Message checking */
    for (i=0; i<8; i++)
    {
        dwDataSizeGet = 1024;
        hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0, lpDataGet,
                                   &dwDataSizeGet );
        checkHR( DP_OK, hr );
        check( 48, dwDataSizeGet );
        check( DPID_SYSMSG, idFrom );
        checkConv( DPSYS_CREATEPLAYERORGROUP, lpDataGet->dwType, dpMsgType2str );
        check( DPPLAYERTYPE_GROUP,            lpDataGet->dwPlayerType );

        if ( lpDataGet->dpIdParent != 0 )
        {
            check( idGroupParent, lpDataGet->dpIdParent );
        }

        switch (i%4)
        {
        case 0:
            checkFlags( DPGROUP_LOCAL,
                        lpDataGet->dwFlags, FLAGS_DPGROUP );
            break;
        case 1:
            checkFlags( DPGROUP_LOCAL | DPGROUP_HIDDEN,
                        lpDataGet->dwFlags, FLAGS_DPGROUP );
            break;
        case 2:
            checkFlags( DPGROUP_STAGINGAREA | DPGROUP_LOCAL,
                        lpDataGet->dwFlags, FLAGS_DPGROUP );
            break;
        case 3:
            checkFlags( DPGROUP_STAGINGAREA | DPGROUP_LOCAL | DPGROUP_HIDDEN,
                        lpDataGet->dwFlags, FLAGS_DPGROUP );
            break;
        default: break;
        }
    }
    check_messages( pDP, &dpid, 1, &callbackData );
    checkStr( "", callbackData.szTrace1 );


    /* If a group is created in C/S mode, no messages are sent */

    /* - Peer 2 peer */
    IDirectPlayX_Close( pDP );

    dpsd.dwFlags = 0;
    hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_CREATE );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP, &dpid, NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroup( pDP, &idGroup, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    /* Messages are received */
    check_messages( pDP, &dpid, 1, &callbackData );
    checkStr( "S0,", callbackData.szTrace1 );


    /* - Client/Server */
    IDirectPlayX_Close( pDP );

    dpsd.dwFlags = DPSESSION_CLIENTSERVER;
    hr = IDirectPlayX_Open( pDP, &dpsd, DPOPEN_CREATE );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP, &dpid,
                                    NULL, NULL, NULL, 0,
                                    DPPLAYER_SERVERPLAYER );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroup( pDP, &idGroup,
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    /* No messages */
    check_messages( pDP, &dpid, 1, &callbackData );
    checkStr( "S0,", callbackData.szTrace1 ); /* Or at least there
                                                 shouldn't be messages... */


    HeapFree( GetProcessHeap(), 0, lpDataGet );
    IDirectPlayX_Release( pDP );

}

/* GroupOwner */

static void test_GroupOwner(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID dpid[2], idGroup, idOwner;
    HRESULT hr;
    UINT i;


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;
    idGroup = 0;
    idOwner = 0;

    /* Service provider not initialized */
    hr = IDirectPlayX_GetGroupOwner( pDP[0], idGroup, &idOwner );
    todo_wine checkHR( DPERR_UNINITIALIZED, hr );
    check( 0, idOwner );

    if ( hr == DP_OK )
    {
        todo_wine win_skip( "GetGroupOwner not implemented\n" );
        return;
    }


    for (i=0; i<2; i++)
        init_TCPIP_provider( pDP[i], "127.0.0.1", 0 );

    hr = IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                                    pDP[1], 0 );
    checkHR( DP_OK, hr );

    for (i=0; i<2; i++)
    {
        hr = IDirectPlayX_CreatePlayer( pDP[i], &dpid[i],
                                        NULL, NULL, NULL, 0, 0 );
        checkHR( DP_OK, hr );
    }

    /* Invalid group */
    hr = IDirectPlayX_GetGroupOwner( pDP[0], idGroup, &idOwner );
    checkHR( DPERR_INVALIDGROUP, hr );

    hr = IDirectPlayX_CreateGroup( pDP[0], &idGroup, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    /* Fails, because we need a lobby session */
    hr = IDirectPlayX_GetGroupOwner( pDP[0], idGroup, &idOwner );
    checkHR( DPERR_UNSUPPORTED, hr );


    /* TODO:
     * - Make this work
     * - Check migration of the ownership of a group
     *   when the owner leaves
     */


    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

/* EnumPlayers */

static BOOL CALLBACK EnumPlayers_cb( DPID dpId,
                                     DWORD dwPlayerType,
                                     LPCDPNAME lpName,
                                     DWORD dwFlags,
                                     LPVOID lpContext )
{
    lpCallbackData callbackData = lpContext;
    char playerIndex = dpid2char( callbackData->dpid,
                                  callbackData->dpidSize,
                                  dpId );


    /* Trace to study player ids */
    callbackData->szTrace1[ callbackData->dwCounter1 ] = playerIndex;
    callbackData->dwCounter1++;
    callbackData->szTrace1[ callbackData->dwCounter1 ] = '\0';

    /* Trace to study flags received */
    strcat( callbackData->szTrace2,
            ( dwFlags2str(dwFlags, FLAGS_DPENUMPLAYERS) +
              strlen("DPENUMPLAYERS_") ) );
    strcat( callbackData->szTrace2, ":" );


    if ( playerIndex < '5' )
    {
        check( DPPLAYERTYPE_PLAYER, dwPlayerType );
    }
    else
    {
        check( DPPLAYERTYPE_GROUP, dwPlayerType );
    }

    return TRUE;

}

static BOOL CALLBACK EnumSessions_cb_EnumPlayers( LPCDPSESSIONDESC2 lpThisSD,
                                                  LPDWORD lpdwTimeOut,
                                                  DWORD dwFlags,
                                                  LPVOID lpContext )
{
    lpCallbackData callbackData = lpContext;
    HRESULT hr;

    if (dwFlags & DPESC_TIMEDOUT)
    {
        return FALSE;
    }

    /* guid = NULL */
    callbackData->dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( callbackData->pDP, NULL, EnumPlayers_cb,
                                   &callbackData, 0 );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData->dwCounter1 );

    /* guid = appGuid */
    callbackData->dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( callbackData->pDP, (LPGUID) &appGuid,
                                   EnumPlayers_cb, &callbackData, 0 );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData->dwCounter1 );

    callbackData->dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( callbackData->pDP, (LPGUID) &appGuid,
                                   EnumPlayers_cb, &callbackData,
                                   DPENUMPLAYERS_SESSION );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData->dwCounter1 );

    /* guid = guidInstance */
    callbackData->dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( callbackData->pDP,
                                   (LPGUID) &lpThisSD->guidInstance,
                                   EnumPlayers_cb, &callbackData, 0 );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData->dwCounter1 );

    callbackData->dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( callbackData->pDP,
                                   (LPGUID) &lpThisSD->guidInstance,
                                   EnumPlayers_cb, &callbackData,
                                   DPENUMPLAYERS_SESSION );
    checkHR( DPERR_GENERIC, hr ); /* Why? */
    check( 0, callbackData->dwCounter1 );

    return TRUE;

}

static void test_EnumPlayers(void)
{
    IDirectPlay4 *pDP[3];
    DPSESSIONDESC2 dpsd[3];
    DPID dpid[5+2]; /* 5 players, 2 groups */
    CallbackData callbackData;
    HRESULT hr;
    UINT i;


    for (i=0; i<3; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;

        ZeroMemory( &dpsd[i], sizeof(DPSESSIONDESC2) );
        dpsd[i].dwSize = sizeof(DPSESSIONDESC2);
    }

    dpsd[0].guidApplication = appGuid;
    dpsd[1].guidApplication = appGuid2;
    dpsd[2].guidApplication = GUID_NULL;

    callbackData.dpid = dpid;
    callbackData.dpidSize = 5+2;


    /* Uninitialized service provider */
    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[0], (LPGUID) &appGuid, NULL,
                                   &callbackData, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );
    check( 0, callbackData.dwCounter1 );


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[2], "127.0.0.1", 0 );


    /* No session */
    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[0], NULL, EnumPlayers_cb,
                                   &callbackData, 0 );
    todo_wine checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData.dwCounter1 );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "EnumPlayers not implemented\n" );
        return;
    }

    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[0], (LPGUID) &appGuid, EnumPlayers_cb,
                                   &callbackData, 0 );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[0], (LPGUID) &appGuid, EnumPlayers_cb,
                                   &callbackData, DPENUMPLAYERS_SESSION );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData.dwCounter1 );


    hr = IDirectPlayX_Open( pDP[0], &dpsd[0], DPOPEN_CREATE );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_Open( pDP[1], &dpsd[1], DPOPEN_CREATE );
    checkHR( DP_OK, hr );


    /* No players */
    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[0], NULL, EnumPlayers_cb,
                                   &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 );


    /* Create players */
    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid[0],
                                    NULL, NULL, NULL, 0,
                                    DPPLAYER_SERVERPLAYER );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[1], &dpid[1],
                                    NULL, NULL, NULL, 0,
                                    0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid[2],
                                    NULL, NULL, NULL, 0,
                                    0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroup( pDP[0], &dpid[5],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );


    /* Invalid parameters */
    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[0], (LPGUID) &appGuid, NULL,
                                   &callbackData, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[0], NULL, EnumPlayers_cb,
                                   &callbackData, DPENUMPLAYERS_SESSION );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check( 0, callbackData.dwCounter1 );


    /* Regular operation */
    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[0], NULL, EnumPlayers_cb,
                                   &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 );
    checkStr( "20", callbackData.szTrace1 );
    checkStr( "ALL:SERVERPLAYER:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[1], NULL, EnumPlayers_cb,
                                   &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 );
    checkStr( "1", callbackData.szTrace1 );
    checkStr( "ALL:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[0], (LPGUID) &appGuid, EnumPlayers_cb,
                                   &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 ); /* Guid is ignored */
    checkStr( "20", callbackData.szTrace1 );
    checkStr( "ALL:SERVERPLAYER:", callbackData.szTrace2 );


    /* Enumerating from a remote session */
    /* - Session not open */
    callbackData.pDP = pDP[2];
    hr = IDirectPlayX_EnumSessions( pDP[2], &dpsd[2], 0,
                                    EnumSessions_cb_EnumPlayers,
                                    &callbackData, 0 );
    checkHR( DP_OK, hr );


    /* - Open session */
    callbackData.pDP = pDP[2];
    hr = IDirectPlayX_EnumSessions( pDP[2], &dpsd[0], 0, EnumSessions_cb_join,
                                    pDP[2], 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[2], &dpid[3],
                                    NULL, NULL, NULL, 0,
                                    DPPLAYER_SPECTATOR );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[2], &dpid[4],
                                    NULL, NULL, NULL, 0,
                                    0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroup( pDP[2], &dpid[6],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[2], NULL, EnumPlayers_cb,
                                   &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 4, callbackData.dwCounter1 );
    checkStr( "4302", callbackData.szTrace1 );
    checkStr( "ALL:SPECTATOR:SERVERPLAYER:ALL:", callbackData.szTrace2 );


    /* Flag tests */

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[2], NULL, EnumPlayers_cb,
                                   &callbackData, DPENUMPLAYERS_ALL );
    checkHR( DP_OK, hr );
    check( 4, callbackData.dwCounter1 );
    checkStr( "4302", callbackData.szTrace1 );
    checkStr( "ALL:SPECTATOR:SERVERPLAYER:ALL:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[2], NULL, EnumPlayers_cb,
                                   &callbackData, DPENUMPLAYERS_GROUP );
    checkHR( DP_OK, hr );
    check( 6, callbackData.dwCounter1 );
    checkStr( "430256", callbackData.szTrace1 );
    checkStr( "GROUP:"
              "GROUP,DPENUMPLAYERS_SPECTATOR:"
              "GROUP,DPENUMPLAYERS_SERVERPLAYER:"
              "GROUP:ALL:ALL:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[2], NULL, EnumPlayers_cb,
                                   &callbackData, DPENUMPLAYERS_LOCAL );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 );
    checkStr( "43", callbackData.szTrace1 );
    checkStr( "LOCAL:"
              "LOCAL,DPENUMPLAYERS_SPECTATOR:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[2], NULL, EnumPlayers_cb,
                                   &callbackData, DPENUMPLAYERS_SERVERPLAYER );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 );
    checkStr( "0", callbackData.szTrace1 );
    checkStr( "SERVERPLAYER:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumPlayers( pDP[2], NULL, EnumPlayers_cb,
                                   &callbackData, DPENUMPLAYERS_SPECTATOR );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 );
    checkStr( "3", callbackData.szTrace1 );
    checkStr( "SPECTATOR:", callbackData.szTrace2 );


    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );
    IDirectPlayX_Release( pDP[2] );

}

/* EnumGroups */

static BOOL CALLBACK EnumGroups_cb( DPID dpId,
                                    DWORD dwPlayerType,
                                    LPCDPNAME lpName,
                                    DWORD dwFlags,
                                    LPVOID lpContext )
{
    lpCallbackData callbackData = lpContext;
    char playerIndex = dpid2char( callbackData->dpid,
                                  callbackData->dpidSize,
                                  dpId );


    /* Trace to study player ids */
    callbackData->szTrace1[ callbackData->dwCounter1 ] = playerIndex;
    callbackData->dwCounter1++;
    callbackData->szTrace1[ callbackData->dwCounter1 ] = '\0';

    /* Trace to study flags received */
    strcat( callbackData->szTrace2,
            ( dwFlags2str(dwFlags, FLAGS_DPENUMGROUPS) +
              strlen("DPENUMGROUPS_") ) );
    strcat( callbackData->szTrace2, ":" );


    check( DPPLAYERTYPE_GROUP, dwPlayerType );

    return TRUE;
}

static BOOL CALLBACK EnumSessions_cb_EnumGroups( LPCDPSESSIONDESC2 lpThisSD,
                                                 LPDWORD lpdwTimeOut,
                                                 DWORD dwFlags,
                                                 LPVOID lpContext )
{
    lpCallbackData callbackData = lpContext;
    HRESULT hr;

    if (dwFlags & DPESC_TIMEDOUT)
    {
        return FALSE;
    }

    /* guid = NULL */
    callbackData->dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( callbackData->pDP, NULL,
                                  EnumGroups_cb, &callbackData, 0 );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData->dwCounter1 );

    /* guid = appGuid */
    callbackData->dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( callbackData->pDP, (LPGUID) &appGuid,
                                  EnumGroups_cb, &callbackData, 0 );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData->dwCounter1 );

    callbackData->dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( callbackData->pDP, (LPGUID) &appGuid,
                                  EnumGroups_cb, &callbackData,
                                  DPENUMGROUPS_SESSION );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData->dwCounter1 );

    /* guid = guidInstance */
    callbackData->dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( callbackData->pDP,
                                  (LPGUID) &lpThisSD->guidInstance,
                                  EnumGroups_cb, &callbackData, 0 );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData->dwCounter1 );

    callbackData->dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( callbackData->pDP,
                                  (LPGUID) &lpThisSD->guidInstance,
                                  EnumGroups_cb, &callbackData,
                                  DPENUMGROUPS_SESSION );
    checkHR( DPERR_GENERIC, hr ); /* Why? */
    check( 0, callbackData->dwCounter1 );

    return TRUE;

}

static void test_EnumGroups(void)
{
    IDirectPlay4 *pDP[3];
    DPSESSIONDESC2 dpsd[3];
    DPID dpid[5];
    CallbackData callbackData;
    HRESULT hr;
    UINT i;


    for (i=0; i<3; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;

        ZeroMemory( &dpsd[i], sizeof(DPSESSIONDESC2) );
        dpsd[i].dwSize = sizeof(DPSESSIONDESC2);
    }

    dpsd[0].guidApplication = appGuid;
    dpsd[1].guidApplication = appGuid2;
    dpsd[2].guidApplication = GUID_NULL;

    callbackData.dpid = dpid;
    callbackData.dpidSize = 5;


    /* Uninitialized service provider */
    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( pDP[0], NULL, EnumGroups_cb,
                                  &callbackData, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );
    check( 0, callbackData.dwCounter1 );


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[2], "127.0.0.1", 0 );


    /* No session */
    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( pDP[0], NULL, EnumGroups_cb,
                                  &callbackData, 0 );
    todo_wine checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData.dwCounter1 );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "EnumGroups not implemented\n" );
        return;
    }

    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( pDP[0], (LPGUID) &appGuid, EnumGroups_cb,
                                  &callbackData, 0 );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( pDP[0], (LPGUID) &appGuid, EnumGroups_cb,
                                  &callbackData, DPENUMGROUPS_SESSION );
    checkHR( DPERR_NOSESSIONS, hr );
    check( 0, callbackData.dwCounter1 );


    hr = IDirectPlayX_Open( pDP[0], &dpsd[0], DPOPEN_CREATE );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_Open( pDP[1], &dpsd[1], DPOPEN_CREATE );
    checkHR( DP_OK, hr );


    /* No groups */
    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( pDP[0], NULL, EnumGroups_cb,
                                  &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 );


    /* Create groups */
    hr = IDirectPlayX_CreateGroup( pDP[0], &dpid[0],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroupInGroup( pDP[0], dpid[0], &dpid[3],
                                          NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr ); /* Not a superior level group,
                             won't appear in the enumerations */
    hr = IDirectPlayX_CreateGroup( pDP[1], &dpid[1],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroup( pDP[0], &dpid[2],
                                   NULL, NULL, 0, DPGROUP_HIDDEN );
    checkHR( DP_OK, hr );


    /* Invalid parameters */
    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( pDP[0], (LPGUID) &appGuid, NULL,
                                  &callbackData, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroups( pDP[0], NULL, EnumGroups_cb,
                                  &callbackData, DPENUMGROUPS_SESSION );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check( 0, callbackData.dwCounter1 );


    /* Regular operation */
    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroups( pDP[0], NULL, EnumGroups_cb,
                                  &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 );
    checkStr( "02", callbackData.szTrace1 );
    checkStr( "ALL:HIDDEN:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroups( pDP[1], NULL, EnumGroups_cb,
                                  &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 );
    checkStr( "1", callbackData.szTrace1 );
    checkStr( "ALL:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroups( pDP[0], (LPGUID) &appGuid, EnumGroups_cb,
                                  &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 ); /* Guid is ignored */
    checkStr( "02", callbackData.szTrace1 );
    checkStr( "ALL:HIDDEN:", callbackData.szTrace2 );


    /* Enumerating from a remote session */
    /* - Session not open */
    callbackData.pDP = pDP[2];
    hr = IDirectPlayX_EnumSessions( pDP[2], &dpsd[2], 0,
                                    EnumSessions_cb_EnumGroups,
                                    &callbackData, 0 );
    checkHR( DP_OK, hr );

    /* - Open session */
    callbackData.pDP = pDP[2];
    hr = IDirectPlayX_EnumSessions( pDP[2], &dpsd[0], 0, EnumSessions_cb_join,
                                    pDP[2], 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroup( pDP[2], &dpid[3],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroup( pDP[2], &dpid[4],
                                   NULL, NULL, 0, DPGROUP_STAGINGAREA );
    checkHR( DP_OK, hr );


    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroups( pDP[2], NULL, EnumGroups_cb,
                                  &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 4, callbackData.dwCounter1 );
    checkStr( "0234", callbackData.szTrace1 );
    checkStr( "ALL:HIDDEN:ALL:STAGINGAREA:", callbackData.szTrace2 );

    /* Flag tests */
    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroups( pDP[2], NULL, EnumGroups_cb,
                                  &callbackData, DPENUMGROUPS_ALL );
    checkHR( DP_OK, hr );
    check( 4, callbackData.dwCounter1 );
    checkStr( "0234", callbackData.szTrace1 );
    checkStr( "ALL:HIDDEN:ALL:STAGINGAREA:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroups( pDP[2], NULL, EnumGroups_cb,
                                  &callbackData, DPENUMGROUPS_HIDDEN );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 );
    checkStr( "2", callbackData.szTrace1 );
    checkStr( "HIDDEN:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroups( pDP[2], NULL, EnumGroups_cb,
                                  &callbackData, DPENUMGROUPS_LOCAL );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 );
    checkStr( "34", callbackData.szTrace1 );
    checkStr( "LOCAL:"
              "LOCAL,DPENUMGROUPS_STAGINGAREA:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroups( pDP[2], NULL, EnumGroups_cb,
                                  &callbackData, DPENUMGROUPS_REMOTE );
    checkHR( DP_OK, hr );
    check( 2, callbackData.dwCounter1 );
    checkStr( "02", callbackData.szTrace1 );
    checkStr( "REMOTE:"
              "REMOTE,DPENUMGROUPS_HIDDEN:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroups( pDP[2], NULL, EnumGroups_cb,
                                  &callbackData, DPENUMGROUPS_STAGINGAREA );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 );
    checkStr( "4", callbackData.szTrace1 );
    checkStr( "STAGINGAREA:", callbackData.szTrace2 );


    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );
    IDirectPlayX_Release( pDP[2] );

}

static void test_EnumGroupsInGroup(void)
{
    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd[2];
    DPID dpid[6];
    CallbackData callbackData;
    HRESULT hr;
    UINT i;


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;

        ZeroMemory( &dpsd[i], sizeof(DPSESSIONDESC2) );
        dpsd[i].dwSize = sizeof(DPSESSIONDESC2);
    }

    dpsd[0].guidApplication = appGuid;
    dpsd[1].guidApplication = GUID_NULL;

    callbackData.dpid = dpid;
    callbackData.dpidSize = 6;


    /* Uninitialized service provider */
    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], 0, NULL, EnumGroups_cb,
                                         &callbackData, 0 );
    checkHR( DPERR_UNINITIALIZED, hr );
    check( 0, callbackData.dwCounter1 );


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );

    hr = IDirectPlayX_Open( pDP[0], &dpsd[0], DPOPEN_CREATE );
    todo_wine checkHR( DP_OK, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "EnumGroupsInGroup not implemented\n" );
        return;
    }

    /* Create groups */
    /*
     * 0
     *   / 2
     * 1 | 3
     *   | 4
     *   \ 5 (shortcut)
     */
    hr = IDirectPlayX_CreateGroup( pDP[0], &dpid[0],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroup( pDP[0], &dpid[1],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroupInGroup( pDP[0], dpid[1], &dpid[2],
                                          NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroupInGroup( pDP[0], dpid[1], &dpid[3],
                                          NULL, NULL, 0,
                                          DPGROUP_HIDDEN );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroupInGroup( pDP[0], dpid[1], &dpid[4],
                                          NULL, NULL, 0,
                                          DPGROUP_STAGINGAREA );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroup( pDP[0], &dpid[5],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_AddGroupToGroup( pDP[0], dpid[1], dpid[5] );
    checkHR( DP_OK, hr );


    /* Invalid parameters */
    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], 0, NULL, EnumGroups_cb,
                                         &callbackData, 0 );
    checkHR( DPERR_INVALIDGROUP, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], 10, NULL, EnumGroups_cb,
                                         &callbackData, 0 );
    checkHR( DPERR_INVALIDGROUP, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], dpid[1], (LPGUID) &appGuid,
                                         NULL, &callbackData, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], dpid[1], NULL, EnumGroups_cb,
                                         &callbackData, DPENUMGROUPS_SESSION );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check( 0, callbackData.dwCounter1 );


    /* Regular operation */
    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], dpid[0], NULL, EnumGroups_cb,
                                         &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], dpid[1], NULL, EnumGroups_cb,
                                         &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 4, callbackData.dwCounter1 );
    checkStr( "5432", callbackData.szTrace1 );
    checkStr( "SHORTCUT:STAGINGAREA:HIDDEN:ALL:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], dpid[1], (LPGUID) &appGuid,
                                         EnumGroups_cb, &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 4, callbackData.dwCounter1 ); /* Guid is ignored */
    checkStr( "5432", callbackData.szTrace1 );
    checkStr( "SHORTCUT:STAGINGAREA:HIDDEN:ALL:", callbackData.szTrace2 );


    /* Enumerating from a remote session */
    /* - Session not open */
    callbackData.pDP = pDP[1];
    hr = IDirectPlayX_EnumSessions( pDP[1], &dpsd[1], 0,
                                    EnumSessions_cb_EnumGroups,
                                    &callbackData, 0 );
    checkHR( DP_OK, hr );

    /* - Open session */
    hr = IDirectPlayX_EnumSessions( pDP[1], &dpsd[0], 0, EnumSessions_cb_join,
                                    pDP[1], 0 );
    checkHR( DP_OK, hr );


    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[1], dpid[1], NULL, EnumGroups_cb,
                                         &callbackData, 0 );
    checkHR( DP_OK, hr );
    check( 4, callbackData.dwCounter1 );
    checkStr( "5432", callbackData.szTrace1 );
    checkStr( "SHORTCUT:STAGINGAREA:HIDDEN:ALL:", callbackData.szTrace2 );

    /* Flag tests */
    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], dpid[1], NULL, EnumGroups_cb,
                                         &callbackData, DPENUMGROUPS_ALL );
    checkHR( DP_OK, hr );
    check( 4, callbackData.dwCounter1 );
    checkStr( "5432", callbackData.szTrace1 );
    checkStr( "SHORTCUT:STAGINGAREA:HIDDEN:ALL:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], dpid[1], NULL, EnumGroups_cb,
                                         &callbackData, DPENUMGROUPS_HIDDEN );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 );
    checkStr( "3", callbackData.szTrace1 );
    checkStr( "HIDDEN:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], dpid[1], NULL, EnumGroups_cb,
                                         &callbackData, DPENUMGROUPS_LOCAL );
    checkHR( DP_OK, hr );
    check( 4, callbackData.dwCounter1 );
    checkStr( "5432", callbackData.szTrace1 );
    checkStr( "LOCAL,DPENUMGROUPS_SHORTCUT:"
              "LOCAL,DPENUMGROUPS_STAGINGAREA:"
              "LOCAL,DPENUMGROUPS_HIDDEN:LOCAL:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], dpid[1], NULL, EnumGroups_cb,
                                         &callbackData, DPENUMGROUPS_REMOTE );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[1], dpid[1], NULL, EnumGroups_cb,
                                         &callbackData, DPENUMGROUPS_LOCAL );
    checkHR( DP_OK, hr );
    check( 0, callbackData.dwCounter1 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[1], dpid[1], NULL, EnumGroups_cb,
                                         &callbackData, DPENUMGROUPS_REMOTE );
    checkHR( DP_OK, hr );
    check( 4, callbackData.dwCounter1 );
    checkStr( "5432", callbackData.szTrace1 );
    checkStr( "REMOTE,DPENUMGROUPS_SHORTCUT:"
              "REMOTE,DPENUMGROUPS_STAGINGAREA:"
              "REMOTE,DPENUMGROUPS_HIDDEN:REMOTE:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], dpid[1], NULL, EnumGroups_cb,
                                         &callbackData, DPENUMGROUPS_SHORTCUT );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 );
    checkStr( "5", callbackData.szTrace1 );
    checkStr( "SHORTCUT:", callbackData.szTrace2 );

    callbackData.dwCounter1 = 0;
    callbackData.szTrace2[0] = 0;
    hr = IDirectPlayX_EnumGroupsInGroup( pDP[0], dpid[1], NULL, EnumGroups_cb,
                                         &callbackData,
                                         DPENUMGROUPS_STAGINGAREA );
    checkHR( DP_OK, hr );
    check( 1, callbackData.dwCounter1 );
    checkStr( "4", callbackData.szTrace1 );
    checkStr( "STAGINGAREA:", callbackData.szTrace2 );


    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

static void test_groups_p2p(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID idPlayer[6], idGroup[3];
    HRESULT hr;
    UINT i;

    DWORD dwDataSize = 1024;
    LPVOID lpData = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 );
    CallbackData callbackData;


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );

    hr = IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    todo_wine checkHR( DP_OK, hr );
    hr = IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                                    pDP[1], 0 );
    todo_wine checkHR( DP_OK, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "dplay not implemented enough for this test yet\n" );
        return;
    }


    /* Create players */
    hr = IDirectPlayX_CreatePlayer( pDP[0], &idPlayer[0],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[0], &idPlayer[1],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[0], &idPlayer[2],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[1], &idPlayer[3],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[1], &idPlayer[4],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[1], &idPlayer[5],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroup( pDP[0], &idGroup[0],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroup( pDP[1], &idGroup[2],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroupInGroup( pDP[1], idGroup[2], &idGroup[1],
                                          NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );


    /* Purge queues */
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "S0," "S1,S0,"
              "S2,S1,S0," "S2,S1,S0,"
              "S2,S1,S0," "S2,S1,S0,"
              "S2,S1,S0," "S2,S1,S0,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "S3," "S4,S3,"
              "S5,S4,S3," "S5,S4,S3,"
              "S5,S4,S3,", callbackData.szTrace1 );


    /*
     * Player 0   |                  |
     * Player 1   | Group 0          | pDP 0
     * Player 2   |                  |
     * Player 3  | Group 1 )          |
     * Player 4  |         | Group 2  | pDP 1
     * Player 5            |          |
     */

    /* Build groups */
    hr = IDirectPlayX_AddPlayerToGroup( pDP[0], idGroup[0], idPlayer[0] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[0], idGroup[0], idPlayer[1] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[0], idGroup[0], idPlayer[2] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[1], idGroup[1], idPlayer[3] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[1], idGroup[1], idPlayer[4] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[1], idGroup[2], idPlayer[4] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[1], idGroup[2], idPlayer[5] );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_AddGroupToGroup( pDP[1], idGroup[2], idGroup[1] );
    checkHR( DP_OK, hr );

    /* Purge queues */
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "S2,S1,S0," "S2,S1,S0," "S2,S1,S0,"
              "S2,S1,S0," "S2,S1,S0," "S2,S1,S0,"
              "S2,S1,S0,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "S5,S4,S3," "S5,S4,S3," "S5,S4,S3,"
              "S5,S4,S3," "S5,S4,S3," "S5,S4,S3,"
              "S5,S4,S3,", callbackData.szTrace1 );


    /* Sending broadcast messages, and checking who receives them */

    dwDataSize = 4;
    /* 0 -> * */
    hr = IDirectPlayX_Send( pDP[0], idPlayer[0], DPID_ALLPLAYERS, 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "02,01,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "05,04,03,", callbackData.szTrace1 );

    /* 0 -> g0 */
    hr = IDirectPlayX_Send( pDP[0], idPlayer[0], idGroup[0], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "02,01,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    /* 0 -> g1 */
    hr = IDirectPlayX_Send( pDP[0], idPlayer[0], idGroup[1], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "04,03,", callbackData.szTrace1 );
    /* 0 -> g2 */
    hr = IDirectPlayX_Send( pDP[0], idPlayer[0], idGroup[2], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "05,04,", callbackData.szTrace1 );

    /* 3 -> * */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[3], DPID_ALLPLAYERS, 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "32,31,30,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "35,34,", callbackData.szTrace1 );
    /* 3 -> g0 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[3], idGroup[0], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "32,31,30,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    /* 3 -> g1 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[3], idGroup[1], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "34,", callbackData.szTrace1 );
    /* 3 -> g2 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[3], idGroup[2], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "35,34,", callbackData.szTrace1 );

    /* 5 -> * */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[5], DPID_ALLPLAYERS, 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "52,51,50,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "54,53,", callbackData.szTrace1 );
    /* 5 -> g0 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[5], idGroup[0], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "52,51,50,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    /* 5 -> g1 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[5], idGroup[1], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "54,53,", callbackData.szTrace1 );
    /* 5 -> g2 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[5], idGroup[2], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "54,", callbackData.szTrace1 );


    HeapFree( GetProcessHeap(), 0, lpData );
    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

static void test_groups_cs(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID idPlayer[6], idGroup[3];
    CallbackData callbackData;
    HRESULT hr;
    UINT i;

    DWORD dwDataSize = 1024;
    LPVOID lpData = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 );


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );

    dpsd.dwFlags = DPSESSION_CLIENTSERVER;
    hr = IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    todo_wine checkHR( DP_OK, hr );
    dpsd.dwFlags = 0;
    hr = IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                                    pDP[1], 0 );
    todo_wine checkHR( DP_OK, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "dplay not implemented enough for this test yet\n" );
        return;
    }


    /* Create players */
    hr = IDirectPlayX_CreatePlayer( pDP[0], &idPlayer[0],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DPERR_ACCESSDENIED, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[0], &idPlayer[0],
                                    NULL, NULL, NULL, 0,
                                    DPPLAYER_SERVERPLAYER );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[0], &idPlayer[1],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DPERR_ACCESSDENIED, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[1], &idPlayer[1],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[1], &idPlayer[2],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[1], &idPlayer[3],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[1], &idPlayer[4],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreatePlayer( pDP[1], &idPlayer[5],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreateGroup( pDP[0], &idGroup[0],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroup( pDP[1], &idGroup[2],
                                   NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_CreateGroupInGroup( pDP[1], idGroup[2], &idGroup[1],
                                          NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );


    /* Purge queues */
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "S0,S0,S0,S0,S0,S0,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "S1," "S2,S1," "S3,S2,S1," "S4,S3,S2,S1,"
              "S5,S4,S3,S2,S1," "S5,S4,S3,S2,S1,", callbackData.szTrace1 );

    /*
     * Player 0   |                  | pDP 0
     * Player 1   | Group 0           |
     * Player 2   |                   |
     * Player 3  | Group 1 )          |
     * Player 4  |         | Group 2  | pDP 1
     * Player 5            |          |
     */

    /* Build groups */
    hr = IDirectPlayX_AddPlayerToGroup( pDP[0], idGroup[0], idPlayer[0] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[0], idGroup[0], idPlayer[1] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[0], idGroup[0], idPlayer[2] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[1], idGroup[1], idPlayer[3] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[1], idGroup[1], idPlayer[4] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[1], idGroup[2], idPlayer[4] );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_AddPlayerToGroup( pDP[1], idGroup[2], idPlayer[5] );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_AddGroupToGroup( pDP[1], idGroup[2], idGroup[1] );
    checkHR( DP_OK, hr );

    /* Purge queues */
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "S0,S0,S0,S0,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "S5," "S4,S3,S2,S1," "S5,S4,S3,S2,S1,"
              "S5,S4,S3,S2,S1," "S5,S4,S3,S2,S1,", callbackData.szTrace1 );


    /* Sending broadcast messages, and checking who receives them */
    dwDataSize = 4;
    /* 0 -> * */
    hr = IDirectPlayX_Send( pDP[0], idPlayer[0], DPID_ALLPLAYERS, 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "05,04,03,02,01,", callbackData.szTrace1 );

    /* 0 -> g0 */
    hr = IDirectPlayX_Send( pDP[0], idPlayer[0], idGroup[0], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "02,01,", callbackData.szTrace1 );
    /* 0 -> g1 */
    hr = IDirectPlayX_Send( pDP[0], idPlayer[0], idGroup[1], 0,
                            lpData, dwDataSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    /* 0 -> g2 */
    hr = IDirectPlayX_Send( pDP[0], idPlayer[0], idGroup[2], 0,
                            lpData, dwDataSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );

    /* 3 -> * */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[3], DPID_ALLPLAYERS, 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "30,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "35,34,32,31,", callbackData.szTrace1 );
    /* 3 -> g0 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[3], idGroup[0], 0,
                            lpData, dwDataSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    /* 3 -> g1 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[3], idGroup[1], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "34,", callbackData.szTrace1 );
    /* 3 -> g2 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[3], idGroup[2], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "35,34,", callbackData.szTrace1 );

    /* 5 -> * */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[5], DPID_ALLPLAYERS, 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "50,", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "54,53,52,51,", callbackData.szTrace1 );
    /* 5 -> g0 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[5], idGroup[0], 0,
                            lpData, dwDataSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    /* 5 -> g1 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[5], idGroup[1], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "54,53,", callbackData.szTrace1 );
    /* 5 -> g2 */
    hr = IDirectPlayX_Send( pDP[1], idPlayer[5], idGroup[2], 0,
                            lpData, dwDataSize );
    checkHR( DP_OK, hr );
    check_messages( pDP[0], idPlayer, 6, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], idPlayer, 6, &callbackData );
    checkStr( "54,", callbackData.szTrace1 );


    HeapFree( GetProcessHeap(), 0, lpData );
    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

/* Send */

static void test_Send(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID dpid[4], idFrom, idTo;
    CallbackData callbackData;
    HRESULT hr;
    LPCSTR message = "message";
    DWORD messageSize = strlen(message) + 1;
    DWORD dwDataSize = 1024;
    LPDPMSG_GENERIC lpData = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwDataSize );
    LPDPMSG_SECUREMESSAGE lpDataSecure;
    UINT i;


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );


    /* Uninitialized service provider */
    hr = IDirectPlayX_Send( pDP[0], 0, 0, 0,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_UNINITIALIZED, hr );


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );

    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;
    IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                               pDP[1], 0 );
    IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );


    /* Incorrect players */
    hr = IDirectPlayX_Send( pDP[0], 0, 1, 2,
                            (LPVOID) message, messageSize );
    todo_wine checkHR( DPERR_INVALIDPLAYER, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "Send not implemented\n" );
        return;
    }


    IDirectPlayX_CreatePlayer( pDP[0], &dpid[0], NULL, NULL, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP[0], &dpid[1], NULL, NULL, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP[0], &dpid[2], NULL, NULL, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP[1], &dpid[3], NULL, NULL, NULL, 0, 0 );

    /* Purge player creation messages */
    check_messages( pDP[0], dpid, 4, &callbackData );
    checkStr( "S0," "S1,S0," "S2,S1,S0,", callbackData.szTrace1 );
    check_messages( pDP[1], dpid, 4, &callbackData );
    checkStr( "", callbackData.szTrace1 );


    /* Message to self: no error, but no message is sent */
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[0], 0,
                            (LPVOID) message, messageSize );
    checkHR( DP_OK, hr );

    /* Send a message from a remote player */
    hr = IDirectPlayX_Send( pDP[1], dpid[0], dpid[1], 0,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_ACCESSDENIED, hr );
    hr = IDirectPlayX_Send( pDP[1], dpid[0], dpid[3], 0,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_ACCESSDENIED, hr );

    /* Null message */
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1], 0,
                            NULL, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1], 0,
                            (LPVOID) message, 0 );
    checkHR( DPERR_INVALIDPARAMS, hr );


    /* Checking no message was sent */
    check_messages( pDP[0], dpid, 4, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], dpid, 4, &callbackData );
    checkStr( "", callbackData.szTrace1 );


    /* Regular parameters */
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            0,
                            (LPVOID) message, messageSize );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_Receive( pDP[0], &dpid[0], &dpid[1],
                               DPRECEIVE_FROMPLAYER | DPRECEIVE_TOPLAYER,
                               lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    checkStr( message, (LPSTR) lpData );
    check( strlen(message)+1, dwDataSize );

    check_messages( pDP[0], dpid, 4, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], dpid, 4, &callbackData );
    checkStr( "", callbackData.szTrace1 );


    /* Message to a remote player */
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[3], 0,
                            (LPVOID) message, messageSize );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_Receive( pDP[0], &dpid[0], &dpid[3],
                               DPRECEIVE_FROMPLAYER | DPRECEIVE_TOPLAYER,
                               lpData, &dwDataSize );
    checkHR( DPERR_NOMESSAGES, hr );
    hr = IDirectPlayX_Receive( pDP[1], &dpid[0], &dpid[3],
                               DPRECEIVE_FROMPLAYER | DPRECEIVE_TOPLAYER,
                               lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    checkStr( message, (LPSTR) lpData );
    check( strlen(message)+1, dwDataSize );

    check_messages( pDP[0], dpid, 4, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], dpid, 4, &callbackData );
    checkStr( "", callbackData.szTrace1 );


    /* Broadcast */

    hr = IDirectPlayX_Send( pDP[0], dpid[0], DPID_ALLPLAYERS, 0,
                            (LPVOID) message, messageSize );
    checkHR( DP_OK, hr );

    for (i=1; i<3; i++)
    {
        hr = IDirectPlayX_Receive( pDP[0], &dpid[0], &dpid[i],
                                   DPRECEIVE_FROMPLAYER | DPRECEIVE_TOPLAYER,
                                   lpData, &dwDataSize );
        checkHR( DP_OK, hr );
        checkStr( message, (LPSTR) lpData );
    }
    hr = IDirectPlayX_Receive( pDP[1], &dpid[0], &dpid[3],
                               DPRECEIVE_FROMPLAYER | DPRECEIVE_TOPLAYER,
                               lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    checkStr( message, (LPSTR) lpData );

    check_messages( pDP[0], dpid, 4, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], dpid, 4, &callbackData );
    checkStr( "", callbackData.szTrace1 );


    hr = IDirectPlayX_Send( pDP[0], DPID_ALLPLAYERS, dpid[1],
                            0,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPLAYER, hr );
    hr = IDirectPlayX_Send( pDP[0], DPID_ALLPLAYERS, DPID_ALLPLAYERS,
                            0,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPLAYER, hr );


    /* Flags */
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_GUARANTEED,
                            (LPVOID) message, messageSize );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_Receive( pDP[0], &dpid[0], &dpid[1],
                               DPRECEIVE_FROMPLAYER | DPRECEIVE_TOPLAYER,
                               lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    checkStr( message, (LPSTR)lpData );

    /* - Inorrect flags */
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_ENCRYPTED,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_SIGNED,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_ENCRYPTED | DPSEND_SIGNED,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );

    /* - Correct flags, but session is not secure */
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_ENCRYPTED | DPSEND_GUARANTEED,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_SIGNED | DPSEND_GUARANTEED,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            ( DPSEND_ENCRYPTED |
                              DPSEND_SIGNED |
                              DPSEND_GUARANTEED ),
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );

    /* - Correct flags, secure session incorrectly opened (without flags) */
    hr = IDirectPlayX_Close( pDP[0] );
    checkHR( DP_OK, hr );

    dpsd.dwFlags = 0;
    hr = IDirectPlayX_SecureOpen( pDP[0], &dpsd, DPOPEN_CREATE, NULL, NULL );
    checkHR( DP_OK, hr );
    for (i=0; i<2; i++)
        IDirectPlayX_CreatePlayer( pDP[0], &dpid[i], NULL, NULL, NULL, 0, 0 );

    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_ENCRYPTED | DPSEND_GUARANTEED,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_SIGNED | DPSEND_GUARANTEED,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            ( DPSEND_ENCRYPTED |
                              DPSEND_SIGNED |
                              DPSEND_GUARANTEED ),
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );

    /* - Correct flags, secure session */
    hr = IDirectPlayX_Close( pDP[0] );
    checkHR( DP_OK, hr );

    dpsd.dwFlags = DPSESSION_SECURESERVER;
    hr = IDirectPlayX_SecureOpen( pDP[0], &dpsd, DPOPEN_CREATE, NULL, NULL );
    checkHR( DP_OK, hr );
    IDirectPlayX_CreatePlayer( pDP[0], &dpid[0], NULL, NULL, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP[0], &dpid[1], NULL, NULL, NULL, 0, 0 );

    /* Purge */
    check_messages( pDP[0], dpid, 6, &callbackData );
    checkStr( "S0,", callbackData.szTrace1 );


    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_ENCRYPTED | DPSEND_GUARANTEED,
                            (LPVOID) message, messageSize );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_SIGNED | DPSEND_GUARANTEED,
                            (LPVOID) message, messageSize );
    checkHR( DP_OK, hr );
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            ( DPSEND_ENCRYPTED |
                              DPSEND_SIGNED |
                              DPSEND_GUARANTEED ),
                            (LPVOID) message, messageSize );
    checkHR( DP_OK, hr );


    for (i=0; i<3; i++)
    {
        dwDataSize = 1024;
        hr = IDirectPlayX_Receive( pDP[0], &idFrom, &idTo, 0, lpData,
                                   &dwDataSize );

        lpDataSecure = (LPDPMSG_SECUREMESSAGE) lpData;

        checkHR( DP_OK, hr );
        checkConv( DPSYS_SECUREMESSAGE,   lpData->dwType, dpMsgType2str );
        check( DPID_SYSMSG,               idFrom );
        check( dpid[1],                   idTo );
        check( dpid[0],                   lpDataSecure->dpIdFrom );
        checkStr( message,        (LPSTR) lpDataSecure->lpData );
        check( strlen(message)+1,         lpDataSecure->dwDataSize );

        switch(i)
        {
        case 0:
            checkFlags( DPSEND_ENCRYPTED,
                        lpDataSecure->dwFlags,
                        FLAGS_DPSEND );
            break;
        case 1:
            checkFlags( DPSEND_SIGNED,
                        lpDataSecure->dwFlags,
                        FLAGS_DPSEND );
            break;
        case 2:
            checkFlags( DPSEND_SIGNED | DPSEND_ENCRYPTED,
                        lpDataSecure->dwFlags,
                        FLAGS_DPSEND );
            break;
        default: break;
        }
    }
    check_messages( pDP[0], dpid, 4, &callbackData );
    checkStr( "", callbackData.szTrace1 );


    /* - Even in a secure session, incorrect flags still not working */
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_ENCRYPTED,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_SIGNED,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_Send( pDP[0], dpid[0], dpid[1],
                            DPSEND_ENCRYPTED | DPSEND_SIGNED,
                            (LPVOID) message, messageSize );
    checkHR( DPERR_INVALIDPARAMS, hr );


    HeapFree( GetProcessHeap(), 0, lpData );
    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

/* Receive */

static void test_Receive(void)
{

    IDirectPlay4 *pDP;
    DPSESSIONDESC2 dpsd;
    DPID dpid[4], idFrom, idTo;
    HRESULT hr;
    LPCSTR message = "message";
    DWORD messageSize = strlen(message) + 1;
    DWORD dwDataSize = 1024;
    LPDPMSG_GENERIC lpData = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY,
                                        dwDataSize );
    LPDPMSG_CREATEPLAYERORGROUP lpDataCreate;
    LPDPMSG_DESTROYPLAYERORGROUP lpDataDestroy;

    DWORD dwCount;
    UINT i;


    hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                           &IID_IDirectPlay4A, (LPVOID*) &pDP );
    ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
    if (FAILED(hr)) return;

    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;

    init_TCPIP_provider( pDP, "127.0.0.1", 0 );

    IDirectPlayX_Open( pDP, &dpsd, DPOPEN_CREATE );


    /* Invalid parameters */
    hr = IDirectPlayX_Receive( pDP, NULL, &idTo, 0,
                               lpData, &dwDataSize );
    todo_wine checkHR( DPERR_INVALIDPARAMS, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "Receive not implemented\n" );
        return;
    }

    hr = IDirectPlayX_Receive( pDP, &idFrom, NULL, 0,
                               lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPARAMS, hr );
    hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0,
                               lpData, NULL );
    checkHR( DPERR_INVALIDPARAMS, hr );
    dwDataSize = -1;
    hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0,
                               lpData, &dwDataSize );
    checkHR( DPERR_INVALIDPARAMS, hr );

    /* No messages yet */
    hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0,
                               NULL, &dwDataSize );
    checkHR( DPERR_NOMESSAGES, hr );
    dwDataSize = 0;
    hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0,
                               lpData, &dwDataSize );
    checkHR( DPERR_NOMESSAGES, hr );


    IDirectPlayX_CreatePlayer( pDP, &dpid[0], NULL, 0, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP, &dpid[1], NULL, 0, NULL, 0,
                               DPPLAYER_SPECTATOR );
    IDirectPlayX_CreatePlayer( pDP, &dpid[2], NULL, 0, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP, &dpid[3], NULL, 0, NULL, 0, 0 );


    /* 0, 1, 2, 3 */
    /* 3, 2, 1, 0 */
    for (i=0; i<4; i++)
    {
        IDirectPlayX_GetMessageCount( pDP, dpid[i], &dwCount );
        check( 3-i, dwCount );
    }


    IDirectPlayX_DestroyPlayer( pDP, dpid[3] );
    IDirectPlayX_DestroyPlayer( pDP, dpid[1] );


    /* 0, 1, 2, 3 */
    /* 5, 5, 3, 3 */
    IDirectPlayX_GetMessageCount( pDP, dpid[0], &dwCount );
    check( 5, dwCount );
    IDirectPlayX_GetMessageCount( pDP, dpid[1], &dwCount );
    check( 5, dwCount );
    IDirectPlayX_GetMessageCount( pDP, dpid[2], &dwCount );
    check( 3, dwCount );
    IDirectPlayX_GetMessageCount( pDP, dpid[3], &dwCount );
    check( 3, dwCount );


    /* Buffer too small */
    hr = IDirectPlayX_Receive( pDP, &idFrom, &idFrom, 0,
                               NULL, &dwDataSize );
    checkHR( DPERR_BUFFERTOOSMALL, hr );
    check( 48, dwDataSize );
    dwDataSize = 0;
    hr = IDirectPlayX_Receive( pDP, &idTo, &idFrom, 0,
                               lpData, &dwDataSize );
    checkHR( DPERR_BUFFERTOOSMALL, hr );
    check( 48, dwDataSize );


    /* Checking the order or reception */
    for (i=0; i<11; i++)
    {
        dwDataSize = 1024;
        hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0,
                                   lpData, &dwDataSize );

        checkHR( DP_OK, hr );
        check( DPID_SYSMSG, idFrom );

        if (i<6)  /* Player creation */
        {
            checkConv( DPSYS_CREATEPLAYERORGROUP, lpData->dwType, dpMsgType2str );
            check( 48, dwDataSize );
            lpDataCreate = (LPDPMSG_CREATEPLAYERORGROUP) lpData;
            check( DPPLAYERTYPE_PLAYER,   lpDataCreate->dwPlayerType );
            checkLP( NULL,                lpDataCreate->lpData );
            check( 0,                     lpDataCreate->dwDataSize );
            checkLP( NULL,                U1(lpDataCreate->dpnName).lpszShortNameA );
            check( 0,                     lpDataCreate->dpIdParent );
        }
        else  /* Player destruction */
        {
            checkConv( DPSYS_DESTROYPLAYERORGROUP, lpData->dwType,
                       dpMsgType2str );
            check( 52, dwDataSize );
            lpDataDestroy = (LPDPMSG_DESTROYPLAYERORGROUP) lpData;
            check( DPPLAYERTYPE_PLAYER,   lpDataDestroy->dwPlayerType );
            checkLP( NULL,                lpDataDestroy->lpLocalData );
            check( 0,                     lpDataDestroy->dwLocalDataSize );
            checkLP( NULL,                lpDataDestroy->lpRemoteData );
            check( 0,                     lpDataDestroy->dwRemoteDataSize );
            checkLP( NULL,                U1(lpDataDestroy->dpnName).lpszShortNameA );
            check( 0,                     lpDataDestroy->dpIdParent );
        }

        switch(i)
        {
            /* 1 -> 0 */
        case 0:
            lpDataCreate = (LPDPMSG_CREATEPLAYERORGROUP) lpData;
            check( dpid[0], idTo );
            check( dpid[1],              lpDataCreate->dpId );
            check( 1,                    lpDataCreate->dwCurrentPlayers );
            checkFlags( DPPLAYER_LOCAL|DPPLAYER_SPECTATOR, lpDataCreate->dwFlags,
                        FLAGS_DPPLAYER|FLAGS_DPGROUP );
            break;

            /* 2 -> 1,0 */
        case 1:
            check( dpid[1], idTo );
            lpDataCreate = (LPDPMSG_CREATEPLAYERORGROUP) lpData;
            check( dpid[2],              lpDataCreate->dpId );
            check( 2,                    lpDataCreate->dwCurrentPlayers );
            checkFlags( DPPLAYER_LOCAL,  lpDataCreate->dwFlags,
                        FLAGS_DPPLAYER | FLAGS_DPGROUP );
            break;
        case 2:
            check( dpid[0], idTo );
            lpDataCreate = (LPDPMSG_CREATEPLAYERORGROUP) lpData;
            check( dpid[2],              lpDataCreate->dpId );
            check( 2,                    lpDataCreate->dwCurrentPlayers );
            checkFlags( DPPLAYER_LOCAL,  lpDataCreate->dwFlags,
                        FLAGS_DPPLAYER | FLAGS_DPGROUP );
            break;

            /* 3 -> 2,1,0 */
        case 3:
            check( dpid[2], idTo );
            lpDataCreate = (LPDPMSG_CREATEPLAYERORGROUP) lpData;
            check( dpid[3],              lpDataCreate->dpId );
            check( 3,                    lpDataCreate->dwCurrentPlayers );
            checkFlags( DPPLAYER_LOCAL,  lpDataCreate->dwFlags,
                        FLAGS_DPPLAYER | FLAGS_DPGROUP );
            break;
        case 4:
            check( dpid[1], idTo );
            lpDataCreate = (LPDPMSG_CREATEPLAYERORGROUP) lpData;
            check( dpid[3],              lpDataCreate->dpId );
            check( 3,                    lpDataCreate->dwCurrentPlayers );
            checkFlags( DPPLAYER_LOCAL,  lpDataCreate->dwFlags,
                        FLAGS_DPPLAYER | FLAGS_DPGROUP );
            break;
        case 5:
            check( dpid[0], idTo );
            lpDataCreate = (LPDPMSG_CREATEPLAYERORGROUP) lpData;
            check( dpid[3],              lpDataCreate->dpId );
            check( 3,                    lpDataCreate->dwCurrentPlayers );
            checkFlags( DPPLAYER_LOCAL,  lpDataCreate->dwFlags,
                        FLAGS_DPPLAYER | FLAGS_DPGROUP );
            break;

            /* 3 -> 2,1,0 */
        case 6:
            check( dpid[2], idTo );
            lpDataDestroy = (LPDPMSG_DESTROYPLAYERORGROUP) lpData;
            check( dpid[3],              lpDataDestroy->dpId );
            checkFlags( DPPLAYER_LOCAL,  lpDataDestroy->dwFlags,
                        FLAGS_DPPLAYER | FLAGS_DPGROUP );
            break;
        case 7:
            check( dpid[1], idTo );
            lpDataDestroy = (LPDPMSG_DESTROYPLAYERORGROUP) lpData;
            check( dpid[3],              lpDataDestroy->dpId );
            checkFlags( DPPLAYER_LOCAL,  lpDataDestroy->dwFlags,
                        FLAGS_DPPLAYER | FLAGS_DPGROUP );
            break;
        case 8:
            check( dpid[0], idTo );
            lpDataDestroy = (LPDPMSG_DESTROYPLAYERORGROUP) lpData;
            check( dpid[3],              lpDataDestroy->dpId );
            checkFlags( DPPLAYER_LOCAL,  lpDataDestroy->dwFlags,
                        FLAGS_DPPLAYER | FLAGS_DPGROUP );
            break;

            /* 1 -> 2,0 */
        case 9:
            check( dpid[2], idTo );
            lpDataDestroy = (LPDPMSG_DESTROYPLAYERORGROUP) lpData;
            check( dpid[1],                 lpDataDestroy->dpId );
            checkFlags( DPPLAYER_LOCAL |
                        DPPLAYER_SPECTATOR, lpDataDestroy->dwFlags,
                        FLAGS_DPPLAYER | FLAGS_DPGROUP );
            break;
        case 10:
            check( dpid[0], idTo );
            lpDataDestroy = (LPDPMSG_DESTROYPLAYERORGROUP) lpData;
            check( dpid[1],                 lpDataDestroy->dpId );
            checkFlags( DPPLAYER_LOCAL |
                        DPPLAYER_SPECTATOR, lpDataDestroy->dwFlags,
                        FLAGS_DPPLAYER | FLAGS_DPGROUP );
            break;

        default:
            trace( "%s\n", dpMsgType2str(lpData->dwType) );
            break;
        }
    }

    hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0, lpData, &dwDataSize );
    checkHR( DPERR_NOMESSAGES, hr );


    /* New data message */
    hr = IDirectPlayX_Send( pDP, dpid[0], dpid[2], 0,
                            (LPVOID) message, messageSize );
    checkHR( DP_OK, hr );


    /* Ensuring DPRECEIVE_PEEK doesn't remove the messages from the queue */
    for (i=0; i<10; i++)
    {
        hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, DPRECEIVE_PEEK,
                                   lpData, &dwDataSize );
        checkHR( DP_OK, hr );
        checkStr( message, (LPSTR) lpData );
    }

    /* Removing the message from the queue */
    hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0, lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    check( idFrom, dpid[0] );
    check( idTo, dpid[2] );
    checkStr( message, (LPSTR) lpData );

    hr = IDirectPlayX_Receive( pDP, &idFrom, &idTo, 0, lpData, &dwDataSize );
    checkHR( DPERR_NOMESSAGES, hr );


    HeapFree( GetProcessHeap(), 0, lpData );
    IDirectPlayX_Release( pDP );

}

/* GetMessageCount */

static void test_GetMessageCount(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID dpid[4];
    HRESULT hr;
    UINT i;
    DWORD dwCount;

    DWORD dwDataSize = 1024;
    LPVOID lpData = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwDataSize );
    CallbackData callbackData;


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], 0,  &dwCount );
    todo_wine checkHR( DPERR_UNINITIALIZED, hr );
    check( -1, dwCount );

    if ( hr == DP_OK )
    {
        todo_wine win_skip( "GetMessageCount not implemented\n" );
        return;
    }


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );


    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], 0, &dwCount );
    checkHR( DP_OK, hr );
    check( 0, dwCount );


    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;
    IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                               pDP[1], 0 );

    IDirectPlayX_CreatePlayer( pDP[0], &dpid[0], NULL, NULL, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP[0], &dpid[1], NULL, NULL, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP[1], &dpid[3], NULL, NULL, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP[0], &dpid[2], NULL, NULL, NULL, 0, 0 );


    /* Incorrect parameters */
    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], dpid[0], NULL );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check( -1, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], 0, NULL );
    checkHR( DPERR_INVALIDPARAMS, hr );
    check( -1, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], -1, &dwCount );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( -1, dwCount );


    /* Correct parameters */
    /* Player creation messages */
    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], 0, &dwCount );
    checkHR( DP_OK, hr );
    check( 5, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[1], 0, &dwCount );
    checkHR( DP_OK, hr );
    check( 1, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], dpid[0], &dwCount );
    checkHR( DP_OK, hr );
    check( 3, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], dpid[1], &dwCount );
    checkHR( DP_OK, hr );
    check( 2, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], dpid[3], &dwCount );
    checkHR( DP_OK, hr );
    /* Remote player: doesn't throw error but result is 0 and not 1 */
    check( 0, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[1], dpid[3], &dwCount );
    checkHR( DP_OK, hr );
    check( 1, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], dpid[1], &dwCount );
    checkHR( DP_OK, hr );
    check( 2, dwCount );


    /* Purge queues */
    check_messages( pDP[0], dpid, 6, &callbackData );
    checkStr( "S0,S1,S0,S1,S0,", callbackData.szTrace1 );
    check_messages( pDP[1], dpid, 6, &callbackData );
    checkStr( "S3,", callbackData.szTrace1 );


    /* Ensure queues is purged */
    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], 0, &dwCount );
    checkHR( DP_OK, hr );
    check( 0, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[1], 0, &dwCount );
    checkHR( DP_OK, hr );
    check( 0, dwCount );


    /* Send data messages */
    for (i=0; i<5; i++)
        IDirectPlayX_Send( pDP[0], dpid[0], dpid[1], 0, lpData, dwDataSize );
    for (i=0; i<6; i++)
        IDirectPlayX_Send( pDP[0], dpid[1], dpid[2], 0, lpData, dwDataSize );
    for (i=0; i<7; i++)
        IDirectPlayX_Send( pDP[0], dpid[2], dpid[3], 0, lpData, dwDataSize );


    /* Check all messages are in the queues */
    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], 0, &dwCount );
    checkHR( DP_OK, hr );
    check( 11, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[1], 0, &dwCount );
    checkHR( DP_OK, hr );
    check( 7, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], dpid[0], &dwCount );
    checkHR( DP_OK, hr );
    check( 0, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], dpid[1], &dwCount );
    checkHR( DP_OK, hr );
    check( 5, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], dpid[2], &dwCount );
    checkHR( DP_OK, hr );
    check( 6, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[1], dpid[3], &dwCount );
    checkHR( DP_OK, hr );
    check( 7, dwCount );


    /* Purge queues again */
    check_messages( pDP[0], dpid, 6, &callbackData );
    checkStr( "01,01,01,01,01,"
              "12,12,12,12,12,12,", callbackData.szTrace1 );
    check_messages( pDP[1], dpid, 6, &callbackData );
    checkStr( "23,23,23,23,23,23,23,", callbackData.szTrace1 );


    /* Check queues are purged */
    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], 0, &dwCount );
    checkHR( DP_OK, hr );
    check( 0, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[1], 0, &dwCount );
    checkHR( DP_OK, hr );
    check( 0, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], dpid[0], &dwCount );
    checkHR( DP_OK, hr );
    check( 0, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], dpid[1], &dwCount );
    checkHR( DP_OK, hr );
    check( 0, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[0], dpid[2], &dwCount );
    checkHR( DP_OK, hr );
    check( 0, dwCount );

    dwCount = -1;
    hr = IDirectPlayX_GetMessageCount( pDP[1], dpid[3], &dwCount );
    checkHR( DP_OK, hr );
    check( 0, dwCount );


    HeapFree( GetProcessHeap(), 0, lpData );
    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

/* GetMessageQueue */

static void test_GetMessageQueue(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID dpid[4];
    CallbackData callbackData;
    HRESULT hr;
    UINT i;
    DWORD dwNumMsgs, dwNumBytes;

    DWORD dwDataSize = 1024;
    LPVOID lpData = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwDataSize );


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );


    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], 0, 0, 0,
                                       &dwNumMsgs, &dwNumBytes );
    todo_wine checkHR( DPERR_UNINITIALIZED, hr );
    check( -1, dwNumMsgs );
    check( -1, dwNumBytes );

    if ( hr == DP_OK )
    {
        todo_wine win_skip( "GetMessageQueue not implemented\n" );
        return;
    }


    init_TCPIP_provider( pDP[0], "127.0.0.1", 0 );
    init_TCPIP_provider( pDP[1], "127.0.0.1", 0 );


    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], 0, 0, 0,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 0, dwNumMsgs );
    check( 0, dwNumBytes );


    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;
    IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                               pDP[1], 0 );

    IDirectPlayX_CreatePlayer( pDP[0], &dpid[0], NULL, NULL, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP[0], &dpid[1], NULL, NULL, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP[1], &dpid[3], NULL, NULL, NULL, 0, 0 );
    IDirectPlayX_CreatePlayer( pDP[0], &dpid[2], NULL, NULL, NULL, 0, 0 );



    /* Incorrect parameters */
    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], -1, dpid[1],
                                       0,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( -1, dwNumMsgs );
    check( -1, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], -1,
                                       0,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DPERR_INVALIDPLAYER, hr );
    check( -1, dwNumMsgs );
    check( -1, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], dpid[0],
                                       -1,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DPERR_INVALIDFLAGS, hr );
    check( -1, dwNumMsgs );
    check( -1, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], dpid[1],
                                       ( DPMESSAGEQUEUE_SEND |
                                         DPMESSAGEQUEUE_RECEIVE ),
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DPERR_INVALIDFLAGS, hr );
    check( -1, dwNumMsgs );
    check( -1, dwNumBytes );

    /* - Remote players */
if(0)
{
    /* Crash under Win7 */
    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], 0, dpid[3],
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DPERR_INVALIDPLAYER, hr ); /* Player 3 is remote */
    check( -1, dwNumMsgs );
    check( -1, dwNumBytes );
}

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[3], 0,
                                       DPMESSAGEQUEUE_SEND,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DPERR_INVALIDPLAYER, hr ); /* Player 3 is remote */
    check( -1, dwNumMsgs );
    check( -1, dwNumBytes );

    /* - Remote players, this time in the right place */
    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], 0, dpid[3],
                                       DPMESSAGEQUEUE_SEND,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 0, dwNumMsgs );
    check( 0, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[3], 0,
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 0, dwNumMsgs );
    check( 0, dwNumBytes );


    /* Correct parameters */
    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], 0, dpid[1],
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 2, dwNumMsgs );
    check( 96, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], 0,
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 0, dwNumMsgs );
    check( 0, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], 0, 0,
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 5, dwNumMsgs );
    check( 240, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], dpid[1],
                                       DPMESSAGEQUEUE_RECEIVE,
                                       NULL, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( -1, dwNumMsgs );
    check( 0, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], dpid[1],
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, NULL );
    checkHR( DP_OK, hr );
    check( 0, dwNumMsgs );
    check( -1, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], dpid[1],
                                       DPMESSAGEQUEUE_RECEIVE,
                                       NULL, NULL );
    checkHR( DP_OK, hr );
    check( -1, dwNumMsgs );
    check( -1, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], dpid[1],
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 0, dwNumMsgs );
    check( 0, dwNumBytes );


    /* Purge messages */
    check_messages( pDP[0], dpid, 6, &callbackData );
    checkStr( "S0,S1,S0,S1,S0,", callbackData.szTrace1 );
    check_messages( pDP[1], dpid, 6, &callbackData );
    checkStr( "S3,", callbackData.szTrace1 );

    /* Check queues are empty */
    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], 0, 0,
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 0, dwNumMsgs );
    check( 0, dwNumBytes );


    /* Sending 4 data messages from 0 to 1 */
    /*         3               from 0 to 3 */
    /*         2               from 1 to 3 */
    for (i=0; i<4; i++)
        IDirectPlayX_Send( pDP[0], dpid[0], dpid[1], 0, lpData, dwDataSize );
    for (i=0; i<3; i++)
        IDirectPlayX_Send( pDP[0], dpid[0], dpid[3], 0, lpData, dwDataSize );
    for (i=0; i<2; i++)
        IDirectPlayX_Send( pDP[0], dpid[1], dpid[3], 0, lpData, dwDataSize );


    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], dpid[1],
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 4, dwNumMsgs );
    check( 4*dwDataSize, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[1], dpid[0], dpid[3],
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 3, dwNumMsgs );
    check( 3*dwDataSize, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[1], dpid[1], dpid[3],
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 2, dwNumMsgs );
    check( 2*dwDataSize, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], 0,
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 4, dwNumMsgs );
    check( 4*dwDataSize, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[1], dpid[0], 0,
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 3, dwNumMsgs );
    check( 3*dwDataSize, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[1], 0, dpid[3],
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 5, dwNumMsgs );
    check( 5*dwDataSize, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], 0, 0,
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 4, dwNumMsgs );
    check( 4*dwDataSize, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[1], 0, 0,
                                       DPMESSAGEQUEUE_RECEIVE,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 5, dwNumMsgs );
    check( 5*dwDataSize, dwNumBytes );


    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], dpid[1],
                                       DPMESSAGEQUEUE_SEND,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 0, dwNumMsgs );
    check( 0, dwNumBytes );

    dwNumMsgs = dwNumBytes = -1;
    hr = IDirectPlayX_GetMessageQueue( pDP[0], dpid[0], dpid[1],
                                       0,
                                       &dwNumMsgs, &dwNumBytes );
    checkHR( DP_OK, hr );
    check( 0, dwNumMsgs );
    check( 0, dwNumBytes );


    HeapFree( GetProcessHeap(), 0, lpData );
    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

/* Remote data replication */

static void test_remote_data_replication(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID dpid[2], idFrom, idTo;
    CallbackData callbackData;
    HRESULT hr;
    UINT i, j;
    DWORD dwFlags, dwDataSize = 1024;
    DWORD dwCount;

    LPDPMSG_SETPLAYERORGROUPDATA lpData = HeapAlloc( GetProcessHeap(),
                                                     HEAP_ZERO_MEMORY,
                                                     dwDataSize );

    LPCSTR lpDataLocal[] = { "local_0", "local_1" };
    LPCSTR lpDataRemote[] = { "remote_0", "remote_1" };
    LPCSTR lpDataFake = "ugly_fake_data";
    LPSTR lpDataGet = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, 32 );
    DWORD dwDataSizeLocal = strlen(lpDataLocal[0])+1,
        dwDataSizeRemote = strlen(lpDataRemote[0])+1,
        dwDataSizeFake = strlen(lpDataFake)+1,
        dwDataSizeGet;


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
        init_TCPIP_provider( pDP[i], "127.0.0.1", 0 );
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;

    /* Host */
    hr = IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    todo_wine checkHR( DP_OK, hr );

    if ( hr == DPERR_UNINITIALIZED )
    {
        todo_wine win_skip( "dplay not implemented enough for this test yet\n" );
        return;
    }

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid[0],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    /* Peer */
    hr = IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                                    pDP[1], 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreatePlayer( pDP[1], &dpid[1],
                                    NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    /* Check players */
    for (i=0; i<2; i++)
    {
        /* Local (0,0) (1,1) */
        IDirectPlayX_GetPlayerFlags( pDP[i], dpid[i], &dwFlags );
        checkFlags( DPPLAYER_LOCAL, dwFlags, FLAGS_DPPLAYER );
        /* Remote (0,1) (1,0) */
        IDirectPlayX_GetPlayerFlags( pDP[i], dpid[!i], &dwFlags );
        checkFlags( 0, dwFlags, FLAGS_DPPLAYER );
    }

    /* Set data for a local player */
    for (i=0; i<2; i++)
    {
        hr = IDirectPlayX_SetPlayerData( pDP[i], dpid[i],
                                         (LPVOID) lpDataLocal[i],
                                         dwDataSizeLocal,
                                         DPSET_LOCAL );
        checkHR( DP_OK, hr );
        hr = IDirectPlayX_SetPlayerData( pDP[i], dpid[i],
                                         (LPVOID) lpDataRemote[i],
                                         dwDataSizeRemote,
                                         DPSET_REMOTE );
        checkHR( DP_OK, hr );
    }

    /* Retrieve data locally (0->0, 1->1) */
    for (i=0; i<2; i++)
    {
        dwDataSizeGet = dwDataSizeFake;
        strcpy( lpDataGet, lpDataFake );
        hr = IDirectPlayX_GetPlayerData( pDP[i], dpid[i],
                                         lpDataGet, &dwDataSizeGet,
                                         DPGET_LOCAL );
        checkHR( DP_OK, hr );
        check( dwDataSizeLocal, dwDataSizeGet );
        checkStr( lpDataLocal[i], lpDataGet );

        dwDataSizeGet = dwDataSizeFake;
        strcpy( lpDataGet, lpDataFake );
        hr = IDirectPlayX_GetPlayerData( pDP[i], dpid[i],
                                         lpDataGet, &dwDataSizeGet,
                                         DPGET_REMOTE );
        checkHR( DP_OK, hr );
        check( dwDataSizeRemote, dwDataSizeGet );
        checkStr( lpDataRemote[i], lpDataGet );
    }


    /* Set data for a remote player */
    /* This should fail with DPERR_ACCESSDENIED,
       but for some reason it doesn't */
    for (i=0; i<2; i++)
    {
        IDirectPlayX_SetPlayerData( pDP[i], dpid[!i],
                                    (LPVOID) lpDataLocal[!i],
                                    dwDataSizeLocal,
                                    DPSET_LOCAL );
        checkHR( DP_OK, hr );
        IDirectPlayX_SetPlayerData( pDP[i], dpid[!i],
                                    (LPVOID) lpDataRemote[!i],
                                    dwDataSizeRemote,
                                    DPSET_REMOTE );
        checkHR( DP_OK, hr );
    }

    /* Retrieve crossed data (0->1, 1->0) */
    for (i=0; i<2; i++)
    {
        dwDataSizeGet = dwDataSizeFake;
        strcpy( lpDataGet, lpDataFake );
        hr = IDirectPlayX_GetPlayerData( pDP[i], dpid[!i],
                                         lpDataGet, &dwDataSizeGet,
                                         DPGET_LOCAL );
        checkHR( DP_OK, hr );
        check( dwDataSizeLocal, dwDataSizeGet );
        checkStr( lpDataLocal[!i], lpDataGet );

        dwDataSizeGet = dwDataSizeFake;
        strcpy( lpDataGet, lpDataFake );
        hr = IDirectPlayX_GetPlayerData( pDP[i], dpid[!i],
                                         lpDataGet, &dwDataSizeGet,
                                         DPGET_REMOTE );
        checkHR( DP_OK, hr );
        check( dwDataSizeRemote, dwDataSizeGet );
        checkStr( lpDataRemote[!i], lpDataGet );
    }


    /* Purge "new player" messages from queue */
    hr = IDirectPlayX_Receive( pDP[0], &idFrom, &idTo, 0, lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    checkConv( DPSYS_CREATEPLAYERORGROUP, lpData->dwType, dpMsgType2str );

    /* Check number of messages in queue */
    for (i=0; i<2; i++)
    {
        IDirectPlayX_GetMessageCount( pDP[i], dpid[i], &dwCount );
        check( 2, dwCount );
        IDirectPlayX_GetMessageCount( pDP[i], dpid[!i], &dwCount );
        check( 0, dwCount );
    }

    /* Checking system messages */
    for (i=0; i<2; i++)
    {
        for (j=0; j<2; j++)
        {
            hr = IDirectPlayX_Receive( pDP[i], &idFrom, &idTo, 0, lpData,
                                       &dwDataSize );
            checkHR( DP_OK, hr );
            check( 29, dwDataSize );
            check( DPID_SYSMSG, idFrom );
            check( dpid[i], idTo );
            checkConv( DPSYS_SETPLAYERORGROUPDATA, lpData->dwType,
                       dpMsgType2str );
            check( DPPLAYERTYPE_PLAYER,            lpData->dwPlayerType );
            check( dpid[j],                        lpData->dpId );
            checkStr( lpDataRemote[j],     (LPSTR) lpData->lpData );
            check( dwDataSizeRemote,               lpData->dwDataSize );
            dwDataSize = 1024;
        }
        hr = IDirectPlayX_Receive( pDP[i], &idFrom, &idTo, 0,
                                   lpData, &dwDataSize );
        checkHR( DPERR_NOMESSAGES, hr );
    }


    /* Changing remote data */
    hr = IDirectPlayX_SetPlayerData( pDP[0], dpid[0],
                                     (LPVOID) lpDataRemote[0], dwDataSizeRemote,
                                     DPSET_REMOTE );
    checkHR( DP_OK, hr );

    /* Checking system messages (j=0) */
    for (i=0; i<2; i++)
    {
        hr = IDirectPlayX_Receive( pDP[i], &idFrom, &idTo, 0,
                                   lpData, &dwDataSize );
        checkHR( DP_OK, hr );
        check( 29, dwDataSize );
        check( DPID_SYSMSG, idFrom );
        check( dpid[i], idTo );
        checkConv( DPSYS_SETPLAYERORGROUPDATA, lpData->dwType, dpMsgType2str );
        check( DPPLAYERTYPE_PLAYER,            lpData->dwPlayerType );
        check( dpid[0],                        lpData->dpId );
        checkStr( lpDataRemote[0],     (LPSTR) lpData->lpData );
        check( dwDataSizeRemote,               lpData->dwDataSize );
        dwDataSize = 1024;
    }

    /* Queue is empty */
    check_messages( pDP[0], dpid, 2, &callbackData );
    checkStr( "", callbackData.szTrace1 );
    check_messages( pDP[1], dpid, 2, &callbackData );
    checkStr( "", callbackData.szTrace1 );


    HeapFree( GetProcessHeap(), 0, lpDataGet );
    HeapFree( GetProcessHeap(), 0, lpData );
    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

/* Host migration */

static void test_host_migration(void)
{

    IDirectPlay4 *pDP[2];
    DPSESSIONDESC2 dpsd;
    DPID dpid[2], idFrom, idTo;
    HRESULT hr;
    UINT i;
    DWORD dwCount;

    DWORD dwDataSize = 1024;
    LPDPMSG_DESTROYPLAYERORGROUP lpData = HeapAlloc( GetProcessHeap(),
                                                     HEAP_ZERO_MEMORY,
                                                     dwDataSize );


    for (i=0; i<2; i++)
    {
        hr = CoCreateInstance( &CLSID_DirectPlay, NULL, CLSCTX_ALL,
                               &IID_IDirectPlay4A, (LPVOID*) &pDP[i] );
        ok( SUCCEEDED(hr), "CCI of CLSID_DirectPlay / IID_IDirectPlay4A failed\n" );
        if (FAILED(hr)) return;
        init_TCPIP_provider( pDP[i], "127.0.0.1", 0 );
    }
    ZeroMemory( &dpsd, sizeof(DPSESSIONDESC2) );
    dpsd.dwSize = sizeof(DPSESSIONDESC2);
    dpsd.guidApplication = appGuid;
    dpsd.dwMaxPlayers = 10;
    dpsd.dwFlags = DPSESSION_MIGRATEHOST;

    /* Host */
    hr = IDirectPlayX_Open( pDP[0], &dpsd, DPOPEN_CREATE );
    todo_wine checkHR( DP_OK, hr );

    if ( hr != DP_OK )
    {
        todo_wine win_skip( "dplay not implemented enough for this test yet\n" );
        return;
    }

    hr = IDirectPlayX_CreatePlayer( pDP[0], &dpid[0], NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );

    /* Peer */
    hr = IDirectPlayX_EnumSessions( pDP[1], &dpsd, 0, EnumSessions_cb_join,
                                    pDP[1], 0 );
    checkHR( DP_OK, hr );

    hr = IDirectPlayX_CreatePlayer( pDP[1], &dpid[1], NULL, NULL, NULL, 0, 0 );
    checkHR( DP_OK, hr );


    /* Host: One message in queue */
    IDirectPlayX_GetMessageCount( pDP[0], dpid[0], &dwCount );
    check( 1, dwCount );
    dwDataSize = 1024;
    hr = IDirectPlayX_Receive( pDP[0], &idFrom, &idTo, DPRECEIVE_PEEK,
                               lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    checkConv( DPSYS_CREATEPLAYERORGROUP, lpData->dwType, dpMsgType2str );

    /* Peer: No messages */
    IDirectPlayX_GetMessageCount( pDP[1], dpid[1], &dwCount );
    check( 0, dwCount );
    hr = IDirectPlayX_Receive( pDP[1], &idFrom, &idTo, DPRECEIVE_PEEK,
                               lpData, &dwDataSize );
    checkHR( DPERR_NOMESSAGES, hr );


    /* Closing host */
    IDirectPlayX_Close( pDP[0] );


    /* Host: Queue is cleaned */
    IDirectPlayX_GetMessageCount( pDP[0], dpid[0], &dwCount );
    check( 0, dwCount );
    hr = IDirectPlayX_Receive( pDP[0], &idFrom, &idTo, DPRECEIVE_PEEK,
                               lpData, &dwDataSize );
    checkHR( DPERR_NOMESSAGES, hr );

    /* Peer: gets message of player destruction */
    IDirectPlayX_GetMessageCount( pDP[1], dpid[1], &dwCount );
    check( 2, dwCount );
    dwDataSize = 1024;
    hr = IDirectPlayX_Receive( pDP[1], &idFrom, &idTo, DPRECEIVE_PEEK,
                               lpData, &dwDataSize );
    checkHR( DP_OK, hr );
    checkConv( DPSYS_DESTROYPLAYERORGROUP, lpData->dwType, dpMsgType2str );


    /* Message analysis */
    for (i=0; i<2; i++)
    {
        hr = IDirectPlayX_Receive( pDP[1], &idFrom, &idTo, 0,
                                   lpData, &dwDataSize );
        checkHR( DP_OK, hr );
        check( DPID_SYSMSG, idFrom );
        check( dpid[1], idTo ); /* Peer player id */
        switch(i)
        {
        case 0:
            checkConv( DPSYS_DESTROYPLAYERORGROUP, lpData->dwType,
                       dpMsgType2str );
            check( DPPLAYERTYPE_PLAYER, lpData->dwPlayerType );
            check( dpid[0],             lpData->dpId ); /* Host player id */
            checkLP( NULL,              lpData->lpLocalData );
            check( 0,                   lpData->dwLocalDataSize );
            checkLP( NULL,              lpData->lpRemoteData );
            check( 0,                   lpData->dwRemoteDataSize );
            checkLP( NULL,              U1(lpData->dpnName).lpszShortNameA );
            check( 0,                   lpData->dpIdParent );
            checkFlags( 0,              lpData->dwFlags,
                        FLAGS_DPPLAYER | FLAGS_DPGROUP );
            break;
        case 1:
            checkConv( DPSYS_HOST, lpData->dwType, dpMsgType2str );
            break;
        default:
            break;
        }
        dwDataSize = 1024;
    }
    hr = IDirectPlayX_Receive( pDP[1], &idFrom, &idTo, 0, lpData, &dwDataSize );
    checkHR( DPERR_NOMESSAGES, hr );


    HeapFree( GetProcessHeap(), 0, lpData );
    IDirectPlayX_Release( pDP[0] );
    IDirectPlayX_Release( pDP[1] );

}

static void test_COM(void)
{
    IDirectPlay *dp;
    IDirectPlay2A *dp2A;
    IDirectPlay2 *dp2;
    IDirectPlay3A *dp3A;
    IDirectPlay3 *dp3;
    IDirectPlay4A *dp4A;
    IDirectPlay4 *dp4 = (IDirectPlay4*)0xdeadbeef;
    IUnknown *unk;
    ULONG refcount;
    HRESULT hr;

    /* COM aggregation */
    hr = CoCreateInstance(&CLSID_DirectPlay, (IUnknown*)0xdeadbeef, CLSCTX_INPROC_SERVER,
            &IID_IUnknown, (void**)&dp4);
    ok(hr == CLASS_E_NOAGGREGATION || broken(hr == E_INVALIDARG),
            "DirectPlay create failed: %08x, expected CLASS_E_NOAGGREGATION\n", hr);
    ok(!dp4 || dp4 == (IDirectPlay4*)0xdeadbeef, "dp4 = %p\n", dp4);

    /* Invalid RIID */
    hr = CoCreateInstance(&CLSID_DirectPlay, NULL, CLSCTX_INPROC_SERVER, &IID_IDirectPlayLobby,
            (void**)&dp4);
    ok(hr == E_NOINTERFACE, "DirectPlay create failed: %08x, expected E_NOINTERFACE\n", hr);

    /* Different refcount for all DirectPlay Interfaces */
    hr = CoCreateInstance(&CLSID_DirectPlay, NULL, CLSCTX_INPROC_SERVER, &IID_IDirectPlay4,
            (void**)&dp4);
    ok(hr == S_OK, "DirectPlay create failed: %08x, expected S_OK\n", hr);
    refcount = IDirectPlayX_AddRef(dp4);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);

    hr = IDirectPlayX_QueryInterface(dp4, &IID_IDirectPlay2A, (void**)&dp2A);
    ok(hr == S_OK, "QueryInterface for IID_IDirectPlay2A failed: %08x\n", hr);
    refcount = IDirectPlay2_AddRef(dp2A);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);
    IDirectPlay2_Release(dp2A);

    hr = IDirectPlayX_QueryInterface(dp4, &IID_IDirectPlay2, (void**)&dp2);
    ok(hr == S_OK, "QueryInterface for IID_IDirectPlay2 failed: %08x\n", hr);
    refcount = IDirectPlay2_AddRef(dp2);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);
    IDirectPlay2_Release(dp2);

    hr = IDirectPlayX_QueryInterface(dp4, &IID_IDirectPlay3A, (void**)&dp3A);
    ok(hr == S_OK, "QueryInterface for IID_IDirectPlay3A failed: %08x\n", hr);
    refcount = IDirectPlay3_AddRef(dp3A);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);
    IDirectPlay3_Release(dp3A);

    hr = IDirectPlayX_QueryInterface(dp4, &IID_IDirectPlay3, (void**)&dp3);
    ok(hr == S_OK, "QueryInterface for IID_IDirectPlay3 failed: %08x\n", hr);
    refcount = IDirectPlay3_AddRef(dp3);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);
    IDirectPlay3_Release(dp3);

    hr = IDirectPlayX_QueryInterface(dp4, &IID_IDirectPlay4A, (void**)&dp4A);
    ok(hr == S_OK, "QueryInterface for IID_IDirectPlay4A failed: %08x\n", hr);
    refcount = IDirectPlayX_AddRef(dp4A);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);
    IDirectPlayX_Release(dp4A);

    /* IDirectPlay and IUnknown share a refcount */
    hr = IDirectPlayX_QueryInterface(dp4, &IID_IDirectPlay, (void**)&dp);
    ok(hr == S_OK, "QueryInterface for IID_IDirectPlay failed: %08x\n", hr);
    refcount = IDirectPlayX_AddRef(dp);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);
    IDirectPlay_Release(dp);

    hr = IDirectPlayX_QueryInterface(dp4, &IID_IUnknown, (void**)&unk);
    ok(hr == S_OK, "QueryInterface for IID_IUnknown failed: %08x\n", hr);
    refcount = IUnknown_AddRef(unk);
    ok(refcount == 3, "refcount == %u, expected 3\n", refcount);
    refcount = IUnknown_Release(unk);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);

    IUnknown_Release(unk);
    IDirectPlay_Release(dp);
    IDirectPlayX_Release(dp4A);
    IDirectPlay3_Release(dp3);
    IDirectPlay3_Release(dp3A);
    IDirectPlay2_Release(dp2);
    IDirectPlay2_Release(dp2A);
    IDirectPlayX_Release(dp4);
    refcount = IDirectPlayX_Release(dp4);
    ok(refcount == 0, "refcount == %u, expected 0\n", refcount);
}

static void test_COM_dplobby(void)
{
    IDirectPlayLobby *dpl = (IDirectPlayLobby*)0xdeadbeef;
    IDirectPlayLobbyA *dplA;
    IDirectPlayLobby2A *dpl2A;
    IDirectPlayLobby2 *dpl2;
    IDirectPlayLobby3A *dpl3A;
    IDirectPlayLobby3 *dpl3;
    IUnknown *unk;
    ULONG refcount;
    HRESULT hr;

    /* COM aggregation */
    hr = CoCreateInstance(&CLSID_DirectPlayLobby, (IUnknown*)0xdeadbeef, CLSCTX_INPROC_SERVER,
            &IID_IUnknown, (void**)&dpl);
    ok(hr == CLASS_E_NOAGGREGATION || broken(hr == E_INVALIDARG),
            "DirectPlayLobby create failed: %08x, expected CLASS_E_NOAGGREGATION\n", hr);
    ok(!dpl || dpl == (IDirectPlayLobby*)0xdeadbeef, "dpl = %p\n", dpl);

    /* Invalid RIID */
    hr = CoCreateInstance(&CLSID_DirectPlayLobby, NULL, CLSCTX_INPROC_SERVER, &IID_IDirectPlay,
            (void**)&dpl);
    ok(hr == E_NOINTERFACE, "DirectPlayLobby create failed: %08x, expected E_NOINTERFACE\n", hr);

    /* Different refcount for all DirectPlayLobby Interfaces */
    hr = CoCreateInstance(&CLSID_DirectPlayLobby, NULL, CLSCTX_INPROC_SERVER, &IID_IDirectPlayLobby,
            (void**)&dpl);
    ok(hr == S_OK, "DirectPlayLobby create failed: %08x, expected S_OK\n", hr);
    refcount = IDirectPlayLobby_AddRef(dpl);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);

    hr = IDirectPlayLobby_QueryInterface(dpl, &IID_IDirectPlayLobbyA, (void**)&dplA);
    ok(hr == S_OK, "QueryInterface for IID_IDirectPlayLobbyA failed: %08x\n", hr);
    refcount = IDirectPlayLobby_AddRef(dplA);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);
    IDirectPlayLobby_Release(dplA);

    hr = IDirectPlayLobby_QueryInterface(dpl, &IID_IDirectPlayLobby2, (void**)&dpl2);
    ok(hr == S_OK, "QueryInterface for IID_IDirectPlayLobby2 failed: %08x\n", hr);
    refcount = IDirectPlayLobby_AddRef(dpl2);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);
    IDirectPlayLobby_Release(dpl2);

    hr = IDirectPlayLobby_QueryInterface(dpl, &IID_IDirectPlayLobby2A, (void**)&dpl2A);
    ok(hr == S_OK, "QueryInterface for IID_IDirectPlayLobby2A failed: %08x\n", hr);
    refcount = IDirectPlayLobby_AddRef(dpl2A);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);
    IDirectPlayLobby_Release(dpl2A);

    hr = IDirectPlayLobby_QueryInterface(dpl, &IID_IDirectPlayLobby3, (void**)&dpl3);
    ok(hr == S_OK, "QueryInterface for IID_IDirectPlayLobby3 failed: %08x\n", hr);
    refcount = IDirectPlayLobby_AddRef(dpl3);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);
    IDirectPlayLobby_Release(dpl3);

    hr = IDirectPlayLobby_QueryInterface(dpl, &IID_IDirectPlayLobby3A, (void**)&dpl3A);
    ok(hr == S_OK, "QueryInterface for IID_IDirectPlayLobby3A failed: %08x\n", hr);
    refcount = IDirectPlayLobby_AddRef(dpl3A);
    ok(refcount == 2, "refcount == %u, expected 2\n", refcount);
    IDirectPlayLobby_Release(dpl3A);

    /* IDirectPlayLobby and IUnknown share a refcount */
    hr = IDirectPlayX_QueryInterface(dpl, &IID_IUnknown, (void**)&unk);
    ok(hr == S_OK, "QueryInterface for IID_IUnknown failed: %08x\n", hr);
    refcount = IUnknown_AddRef(unk);
    ok(refcount == 4, "refcount == %u, expected 4\n", refcount);
    IDirectPlayLobby_Release(unk);

    IUnknown_Release(unk);
    IDirectPlayLobby_Release(dpl3);
    IDirectPlayLobby_Release(dpl3A);
    IDirectPlayLobby_Release(dpl2);
    IDirectPlayLobby_Release(dpl2A);
    IDirectPlayLobby_Release(dplA);
    IDirectPlayLobby_Release(dpl);
    refcount = IDirectPlayLobby_Release(dpl);
    ok(refcount == 0, "refcount == %u, expected 0\n", refcount);
}

enum firewall_op
{
    APP_ADD,
    APP_REMOVE
};

static BOOL is_process_elevated(void)
{
    HANDLE token;
    if (OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &token ))
    {
        TOKEN_ELEVATION_TYPE type;
        DWORD size;
        BOOL ret;

        ret = GetTokenInformation( token, TokenElevationType, &type, sizeof(type), &size );
        CloseHandle( token );
        return (ret && type == TokenElevationTypeFull);
    }
    return FALSE;
}

static BOOL is_firewall_enabled(void)
{
    HRESULT hr, init;
    INetFwMgr *mgr = NULL;
    INetFwPolicy *policy = NULL;
    INetFwProfile *profile = NULL;
    VARIANT_BOOL enabled = VARIANT_FALSE;

    init = CoInitializeEx( 0, COINIT_APARTMENTTHREADED );

    hr = CoCreateInstance( &CLSID_NetFwMgr, NULL, CLSCTX_INPROC_SERVER, &IID_INetFwMgr,
                           (void **)&mgr );
    ok( hr == S_OK, "got %08x\n", hr );
    if (hr != S_OK) goto done;

    hr = INetFwMgr_get_LocalPolicy( mgr, &policy );
    ok( hr == S_OK, "got %08x\n", hr );
    if (hr != S_OK) goto done;

    hr = INetFwPolicy_get_CurrentProfile( policy, &profile );
    if (hr != S_OK) goto done;

    hr = INetFwProfile_get_FirewallEnabled( profile, &enabled );
    ok( hr == S_OK, "got %08x\n", hr );

done:
    if (policy) INetFwPolicy_Release( policy );
    if (profile) INetFwProfile_Release( profile );
    if (mgr) INetFwMgr_Release( mgr );
    if (SUCCEEDED( init )) CoUninitialize();
    return (enabled == VARIANT_TRUE);
}

static HRESULT set_firewall( enum firewall_op op )
{
    static const WCHAR dplaysvrW[] =
        {'d','p','l','a','y','s','v','r','.','e','x','e',0};
    static const WCHAR separator[] = {'\\',0};
    static const WCHAR clientW[] =
        {'d','p','l','a','y','_','c','l','i','e','n','t',0};
    static const WCHAR serverW[] =
        {'d','p','l','a','y','_','s','e','r','v','e','r',0};
    HRESULT hr, init;
    INetFwMgr *mgr = NULL;
    INetFwPolicy *policy = NULL;
    INetFwProfile *profile = NULL;
    INetFwAuthorizedApplication *app = NULL;
    INetFwAuthorizedApplications *apps = NULL;
    BSTR name, image = SysAllocStringLen( NULL, MAX_PATH );
    WCHAR path[MAX_PATH];

    if (!GetModuleFileNameW( NULL, image, MAX_PATH ))
    {
        SysFreeString( image );
        return E_FAIL;
    }

    if(!GetSystemDirectoryW(path, MAX_PATH))
    {
        SysFreeString( image );
        return E_FAIL;
    }
    lstrcatW(path, separator);
    lstrcatW(path, dplaysvrW);

    init = CoInitializeEx( 0, COINIT_APARTMENTTHREADED );

    hr = CoCreateInstance( &CLSID_NetFwMgr, NULL, CLSCTX_INPROC_SERVER, &IID_INetFwMgr,
                           (void **)&mgr );
    ok( hr == S_OK, "got %08x\n", hr );
    if (hr != S_OK) goto done;

    hr = INetFwMgr_get_LocalPolicy( mgr, &policy );
    ok( hr == S_OK, "got %08x\n", hr );
    if (hr != S_OK) goto done;

    hr = INetFwPolicy_get_CurrentProfile( policy, &profile );
    if (hr != S_OK) goto done;

    hr = INetFwProfile_get_AuthorizedApplications( profile, &apps );
    ok( hr == S_OK, "got %08x\n", hr );
    if (hr != S_OK) goto done;

    hr = CoCreateInstance( &CLSID_NetFwAuthorizedApplication, NULL, CLSCTX_INPROC_SERVER,
                           &IID_INetFwAuthorizedApplication, (void **)&app );
    ok( hr == S_OK, "got %08x\n", hr );
    if (hr != S_OK) goto done;

    hr = INetFwAuthorizedApplication_put_ProcessImageFileName( app, image );
    if (hr != S_OK) goto done;

    name = SysAllocString( clientW );
    hr = INetFwAuthorizedApplication_put_Name( app, name );
    SysFreeString( name );
    ok( hr == S_OK, "got %08x\n", hr );
    if (hr != S_OK) goto done;

    if (op == APP_ADD)
        hr = INetFwAuthorizedApplications_Add( apps, app );
    else if (op == APP_REMOVE)
        hr = INetFwAuthorizedApplications_Remove( apps, image );
    else
        hr = E_INVALIDARG;
    if (hr != S_OK) goto done;

    INetFwAuthorizedApplication_Release( app );
    hr = CoCreateInstance( &CLSID_NetFwAuthorizedApplication, NULL, CLSCTX_INPROC_SERVER,
                           &IID_INetFwAuthorizedApplication, (void **)&app );
    ok( hr == S_OK, "got %08x\n", hr );
    if (hr != S_OK) goto done;

    SysFreeString( image );
    image = SysAllocString( path );
    hr = INetFwAuthorizedApplication_put_ProcessImageFileName( app, image );
    if (hr != S_OK) goto done;

    name = SysAllocString( serverW );
    hr = INetFwAuthorizedApplication_put_Name( app, name );
    SysFreeString( name );
    ok( hr == S_OK, "got %08x\n", hr );
    if (hr != S_OK) goto done;

    if (op == APP_ADD)
        hr = INetFwAuthorizedApplications_Add( apps, app );
    else if (op == APP_REMOVE)
        hr = INetFwAuthorizedApplications_Remove( apps, image );
    else
        hr = E_INVALIDARG;

done:
    if (app) INetFwAuthorizedApplication_Release( app );
    if (apps) INetFwAuthorizedApplications_Release( apps );
    if (policy) INetFwPolicy_Release( policy );
    if (profile) INetFwProfile_Release( profile );
    if (mgr) INetFwMgr_Release( mgr );
    if (SUCCEEDED( init )) CoUninitialize();
    SysFreeString( image );
    return hr;
}

/* taken from programs/winetest/main.c */
static BOOL is_stub_dll(const char *filename)
{
    DWORD size, ver;
    BOOL isstub = FALSE;
    char *p, *data;

    size = GetFileVersionInfoSizeA(filename, &ver);
    if (!size) return FALSE;

    data = HeapAlloc(GetProcessHeap(), 0, size);
    if (!data) return FALSE;

    if (GetFileVersionInfoA(filename, ver, size, data))
    {
        char buf[256];

        sprintf(buf, "\\StringFileInfo\\%04x%04x\\OriginalFilename", MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), 1200);
        if (VerQueryValueA(data, buf, (void**)&p, &size))
            isstub = !lstrcmpiA("wcodstub.dll", p);
    }
    HeapFree(GetProcessHeap(), 0, data);

    return isstub;
}

START_TEST(dplayx)
{
    BOOL firewall_enabled;
    HRESULT hr;
    char path[MAX_PATH];
    HMODULE module;

    if(!GetSystemDirectoryA(path, MAX_PATH))
    {
        skip("Failed to get systems directory\n");
        return;
    }
    strcat(path, "\\dplayx.dll");

    if (!winetest_interactive && is_stub_dll(path))
    {
        win_skip("dpnet is a stub dll, skipping tests\n");
        return;
    }

    if ((firewall_enabled = is_firewall_enabled()) && !is_process_elevated())
    {
        skip("no privileges, skipping tests to avoid firewall dialog\n");
        return;
    }

    if (firewall_enabled)
    {
        hr = set_firewall(APP_ADD);
        if (hr != S_OK)
        {
            skip("can't authorize app in firewall %08x\n", hr);
            return;
        }
    }

    CoInitialize( NULL );

    module = LoadLibraryA("dplayx.dll");

    pDirectPlayEnumerateA = (void *)GetProcAddress(module, "DirectPlayEnumerateA");
    pDirectPlayEnumerateW = (void *)GetProcAddress(module, "DirectPlayEnumerateW");
    pDirectPlayCreate = (void *)GetProcAddress(module, "DirectPlayCreate");

    test_COM();
    test_COM_dplobby();
    test_EnumerateProviders();
    test_DirectPlayCreate();
    test_EnumConnections();
    test_InitializeConnection();
    test_GetCaps();
    test_EnumAddressTypes();

    if (!winetest_interactive)
    {
        skip("Run in interactive mode to run all dplayx tests.\n");
        goto done;
    }

    trace("Running in interactive mode, tests will take a while\n");

    /* test_Open() takes almost a minute, */
    test_Open();
    /* test_EnumSession takes three minutes */
    test_EnumSessions();
    test_SessionDesc();

    /* test_CreatePlayer() takes over a minute */
    test_CreatePlayer();
    test_GetPlayerCaps();
    test_PlayerData();
    test_PlayerName();

    /* test_GetPlayerAccount() takes over 30s */
    test_GetPlayerAccount();
    test_GetPlayerAddress();
    test_GetPlayerFlags();

    test_CreateGroup();
    test_GroupOwner();

    test_EnumPlayers();
    test_EnumGroups();
    test_EnumGroupsInGroup();

    test_groups_p2p();
    test_groups_cs();

    test_Send();
    test_Receive();
    test_GetMessageCount();
    test_GetMessageQueue();

    test_remote_data_replication();
    test_host_migration();

done:
    FreeLibrary(module);
    CoUninitialize();
    if (firewall_enabled) set_firewall(APP_REMOVE);
}
