/* dpnet.dll
 *
 * Copyright 2002, 2003 TransGaming Technologies
 *
 * David Hammerton (david@transgaming.com)
 */

/* all the non-service provider networking code. the three main dp8 classes call this.
 * to interface to the service providers.
 * Just a pile of steaming crap written by David.
 * THIS IS JUST TEMPORARY AND SHOULD EVENTAULLY BE CLEANED OUT/COM-ified once all the protocol has
 * been figured out and we have a solid idea of everything MS does.
 */


#include "windef.h"
#include "winbase.h"
#include "winuser.h"
#include "winreg.h"
#include "objbase.h"
#include "wine/debug.h"
#include "winerror.h"
#include "winnt.h"
#include "winreg.h"
#include "winnls.h"
#include "wine/unicode.h"

#include "dplay8.h"
#include "dplay8_private.h"
#include "protocol.h"

#define WINE_UNICODE_REWRITE

WINE_DEFAULT_DEBUG_CHANNEL(dplay);

extern IDirectPlay8ServiceProvider *globalSP; /* hack. see general.c to as why */

HRESULT DirectPlay8Communicate_Packet_SendAck(DirectPlay8GlobalData *dp8gdData,
                                              IDirectPlay8Address *pAddrHost, BYTE packetID,
                                              BYTE reply_type, void *data, DWORD dataSize);
static void DPNET_FreeMsgToAddrNode(DirectPlay8GlobalData *dp8gdData,
                                    dpOutMessageToAddr *msgNode);
HRESULT DPNET_SendMessageToAddress(DirectPlay8GlobalData *dp8gdData,
                                   IDirectPlay8Address *pAddrHost, LPVOID pPacket,
                                   DWORD dwSize, DWORD dwFlags, DWORD dwRetryLimit,
                                   DWORD dwRetryInterval, DWORD dwTimeout, BYTE packetID,
                                   void *pvPacketIntData);


static dpOutMessageToAddr *DPNET_findOutMessageToAddrByPID(DirectPlay8GlobalData *dp8gdData,
                                                           DWORD packetID)
{
    dpOutMessageToAddr *curMessage = dp8gdData->msgToAddrTail;

    TRACE("(%p, %li)\n", dp8gdData, packetID);
    TRACE("curMessage is: %p\n", curMessage);
    while (curMessage)
    {
        if (curMessage->packetID == packetID)
            return curMessage;
        curMessage = curMessage->prev;
    }
    return NULL;
}

static void DPNET_ProcessMessage_EnumHosts(DirectPlay8GlobalData *dp8gdData, void *message, int bytes, PVOID spAddrData)
{
    switch(((BYTE *)message)[1])
    {
    case DP8P_ENUMHOSTS_SEND:
        {
            FIXME("support replying to enum hosts\n");
            break;
        }
    case DP8P_ENUMHOSTS_RESPONSE:
        {
            DPNMSG_ENUM_HOSTS_RESPONSE dpnMsgEnumHostsResponse;
            HRESULT hr;
            dpOutMessageToAddr *outMessage; /* message that we sent */
            dp8pmsg_EnumHostsResponse *msgEnumHostsResponse =
                (dp8pmsg_EnumHostsResponse *) message;

            TRACE("enum host response: max_players: %li, current_players: %li\n\t"
                  "guidUnknown: %s, guidApp: %s. game name: %s\n",
                  msgEnumHostsResponse->max_players, msgEnumHostsResponse->current_players,
                  debugstr_guid(&msgEnumHostsResponse->guidUnknown),
                  debugstr_guid(&msgEnumHostsResponse->guidApp),
                  debugstr_w(msgEnumHostsResponse->game_name));

            outMessage = DPNET_findOutMessageToAddrByPID(dp8gdData, msgEnumHostsResponse->packet_count);
            if (!outMessage)
            {
                WARN("Enum hosts response to a message we didn't send out\n");
                break;
            }

            dpnMsgEnumHostsResponse.dwSize = sizeof(dpnMsgEnumHostsResponse);
            TRACE("creating address (sender)\n");
            hr = CoCreateInstance(&CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER,
                                  &IID_IDirectPlay8Address,
                                  (LPVOID *) &dpnMsgEnumHostsResponse.pAddressSender);
            if (!SUCCEEDED(hr))
            {
                WARN("couldn't create address object. enum host message not sent\n");
                break;
            }
            dp8gdData->sp->lpVtbl->BuildAddressFromSPData(dp8gdData->sp, dpnMsgEnumHostsResponse.pAddressSender,
                                                          spAddrData);
            TRACE("creating address (device)\n");
            hr = CoCreateInstance(&CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER,
                                  &IID_IDirectPlay8Address,
                                  (LPVOID *) &dpnMsgEnumHostsResponse.pAddressDevice);
            if (!SUCCEEDED(hr))
            {
                WARN("couldn't create address object. enum host message not sent\n");
                dpnMsgEnumHostsResponse.pAddressSender->lpVtbl->Release(dpnMsgEnumHostsResponse.pAddressSender);
                break;
            }
            dpnMsgEnumHostsResponse.pApplicationDescription = HeapAlloc(GetProcessHeap(),
                                                                        HEAP_ZERO_MEMORY,
                                                                        sizeof(DPN_APPLICATION_DESC));
            dpnMsgEnumHostsResponse.pApplicationDescription->dwSize = sizeof(DPN_APPLICATION_DESC);
            dpnMsgEnumHostsResponse.pApplicationDescription->dwFlags = 0; /* FIXME */
            memcpy(&dpnMsgEnumHostsResponse.pApplicationDescription->guidInstance, &msgEnumHostsResponse->guidUnknown, sizeof(GUID)); /* FIXME */
            memcpy(&dpnMsgEnumHostsResponse.pApplicationDescription->guidApplication, &msgEnumHostsResponse->guidApp, sizeof(GUID));
            dpnMsgEnumHostsResponse.pApplicationDescription->dwMaxPlayers = msgEnumHostsResponse->max_players;
            dpnMsgEnumHostsResponse.pApplicationDescription->dwCurrentPlayers = msgEnumHostsResponse->current_players;
            dpnMsgEnumHostsResponse.pApplicationDescription->pwszSessionName = DPNET_strndupW(msgEnumHostsResponse->game_name,
                                                                                              strlenW(msgEnumHostsResponse->game_name));
            dpnMsgEnumHostsResponse.pApplicationDescription->pwszPassword = NULL;
            dpnMsgEnumHostsResponse.pApplicationDescription->pvReservedData = NULL;
            dpnMsgEnumHostsResponse.pApplicationDescription->dwReservedDataSize = 0;
            dpnMsgEnumHostsResponse.pApplicationDescription->pvApplicationReservedData = NULL; /* FIXME */
            dpnMsgEnumHostsResponse.pApplicationDescription->dwApplicationReservedDataSize = 0;

            dpnMsgEnumHostsResponse.pvResponseData = NULL;
            dpnMsgEnumHostsResponse.dwResponseDataSize = 0;
            dpnMsgEnumHostsResponse.pvUserContext = dp8gdData->pvUserContext;
            dpnMsgEnumHostsResponse.dwRoundTripLatencyMS = GetTickCount() - outMessage->dwTimeSent;

            TRACE("sending DPN_MSGID_ENUMHOSTS_RESPONSE message\n");
            dp8gdData->pfMessageHandler(dp8gdData->pvUserContext, DPN_MSGID_ENUM_HOSTS_RESPONSE,
                                        &dpnMsgEnumHostsResponse);
            TRACE("message handler returned\n");

            dpnMsgEnumHostsResponse.pAddressSender->lpVtbl->Release(dpnMsgEnumHostsResponse.pAddressSender);
            dpnMsgEnumHostsResponse.pAddressDevice->lpVtbl->Release(dpnMsgEnumHostsResponse.pAddressDevice);
            HeapFree(GetProcessHeap(), 0, dpnMsgEnumHostsResponse.pApplicationDescription->pwszSessionName);
            HeapFree(GetProcessHeap(), 0, dpnMsgEnumHostsResponse.pApplicationDescription);

/*
            DPNMSG_ASYNC_OP_COMPLETE dpnMsgAsyncOpComplete;

            dpnMsgAsyncOpComplete.dwSize = sizeof(dpnMsgAsyncOpComplete);
            dpnMsgAsyncOpComplete.hAsyncOp = gAsyncHandle;
            dpnMsgAsyncOpComplete.pvUserContext = dp8gdData->pvUserContext;
            dpnMsgAsyncOpComplete.hResultCode = S_OK;
            dp8gdData->pfMessageHandler(dp8gdData->pvUserContext, DPN_MSGID_ASYNC_OP_COMPLETE,
                                        &dpnMsgAsyncOpComplete);
            TRACE("sent async message\n");*/
            break;
        }
    default:
        FIXME("unknown enum hosts type (%i). Maybe this isn't actually an enumhosts?\n",
              ((BYTE *)message)[1]);
    }
}

static void DPNET_ProcessMessage_Connect(DirectPlay8GlobalData *dp8gdData, void *message, int bytes, PVOID spAddrData)
{
    switch(((BYTE *)message)[1])
    {
    case 0x01: /* can we connect? */
        {
            FIXME("don't support replying to connect requests\n");
            break;
        }
    case 0x02: /* "sure, you can try" response from host (i think) */
        {
            dpOutMessageToAddr *outMessage; /* message that we sent */
            dp8pmsg_Connect *msgConnect = (dp8pmsg_Connect *)message;
            dp8pmsg_MessageData *msgData;
            IDirectPlay8Address *pAddrHost;
            HRESULT hr;

            TRACE("got a reply from our connect request, we hope\n");

            /* FIXME WILD HACK.. don't use a message ID here. im using 0 here cause we reset
             the counter when sending out a message, still this is very ugly */
            outMessage = DPNET_findOutMessageToAddrByPID(dp8gdData, 0);
            if (!outMessage)
            {
                WARN("connect probe response to a message we didn't send out\n");
                break;
            }
            DPNET_FreeMsgToAddrNode(dp8gdData, outMessage);

            hr = CoCreateInstance(&CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER,
                                  &IID_IDirectPlay8Address,
                                  (LPVOID *) &pAddrHost);
            if (FAILED(hr))
            {
                ERR("couldn't create address object\n");
                break;
            }
            dp8gdData->sp->lpVtbl->BuildAddressFromSPData(dp8gdData->sp, pAddrHost, spAddrData);

            DirectPlay8Communicate_Packet_SendAck(dp8gdData, pAddrHost, msgConnect->packet_count,
                                                  0x02, &msgConnect->unknown_09, sizeof(DWORD));


            DirectPlay8Communicate_SendPeerInfo(dp8gdData, pAddrHost);

     /* FIXME this shouldn't be here. don't really get it */
            msgData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(dp8pmsg_MessageData));

            msgData->protocol_magic = DP8P_MESSAGEDATA_GUARANTEED;
            msgData->unknown_02 = 0x00;
            msgData->packet_count = 0; /* for some reason we arn't supposed to fill this in here? */
            msgData->unknown_04 = 0x00;
            DPNET_SendMessageToAddress(dp8gdData, pAddrHost, msgData,
                                       sizeof(dp8pmsg_MessageData),
                                       0, 0, 500, 0,
                                       0, NULL);
            HeapFree(GetProcessHeap(), 0, msgData);

            dp8gdData->dwPacketCount++;
     /* end that comment */

            pAddrHost->lpVtbl->Release(pAddrHost);

        }

    }
}

/* very much a guess: */

/* ACK
 static void DPNET_SendPeerIndiv_Connect(DirectPlay8GlobalData *dp8gdData, IDirectPlay8Address *pAddrDest)
 */

static void DPNET_SendPeerIndiv_Info(DirectPlay8GlobalData *dp8gdData, IDirectPlay8Address *pAddrDest)
{
    dp8pmsg_SetPeerInfo_Unknown msgSetPeerInfoUnknown;

    msgSetPeerInfoUnknown.protocol_magic = DP8P_SETPEERINFO;
    msgSetPeerInfoUnknown.unknown_02 = 0x00;
    msgSetPeerInfoUnknown.packet_count = dp8gdData->dwPacketCount;
    msgSetPeerInfoUnknown.unknown_04 = 0x02;
    msgSetPeerInfoUnknown.unknown_05 = 0xc3;
    msgSetPeerInfoUnknown.unknown_06 = 0x00;
    msgSetPeerInfoUnknown.unknown_07 = 0x00;
    msgSetPeerInfoUnknown.unknown_08 = 0x00;

    DPNET_SendMessageToAddress(dp8gdData, pAddrDest, &msgSetPeerInfoUnknown, sizeof(msgSetPeerInfoUnknown), 0,
                               0 /* fixme: retries */, 0 /* fixme: retry timeout */, 0,
                               dp8gdData->dwPacketCount, NULL);

    dp8gdData->dwPacketCount++;
}

static void DPNET_ProcessMessage_SetPeerInfo(DirectPlay8GlobalData *dp8gdData, void *message, int bytes, PVOID spAddrData)
{
    if (bytes < 5)
    {
        FIXME("very small peer message (%i). unhandled\n", bytes);
        return;
    }
    switch(((BYTE *)message)[4])
    {
    case 0xc1:
        {
            FIXME("don't suppor setting of individual peer infos right now\n");
            break;
        }
    case 0xc2:
        {
            dpOutMessageToAddr *outMessage; /* message that we sent */
            dp8pmsg_SetPeerInfo_Game *msgPeerInfo = (dp8pmsg_SetPeerInfo_Game *)message;
            dp8pmsg_part_PeerInfo_Player *msgPeerPlayerInfo = (dp8pmsg_part_PeerInfo_Player *)&msgPeerInfo->data;
            int i;
            void *string_pos = (void *)&msgPeerInfo->data;
            HRESULT hr;
            IDirectPlay8Address *pAddrHost;
            PEER_player *curPlayer;

            TRACE("got a peer info thing (from host, I think)\n");

            hr = CoCreateInstance(&CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER,
                                  &IID_IDirectPlay8Address,
                                  (LPVOID *) &pAddrHost);
            if (FAILED(hr))
            {
                ERR("couldn't create address object\n");
                break;
            }
            dp8gdData->sp->lpVtbl->BuildAddressFromSPData(dp8gdData->sp, pAddrHost, spAddrData);

            DirectPlay8Communicate_Packet_SendAck(dp8gdData, pAddrHost, msgPeerInfo->packet_count,
                                                  0x06, NULL, 0);

            while ((outMessage = DPNET_findOutMessageToAddrByPID(dp8gdData, msgPeerInfo->packet_count)))
            /* FIXME: don't know if we should be using the packet count here...
             * TEMPORARY UGLY HACK. we should be using the ACK here, actually */
            {
                DPNET_FreeMsgToAddrNode(dp8gdData, outMessage);
            }
            /* FIXME: these two things REALLY shouldn't be here, but don't really know where the should be...*/

            DPNET_SendPeerIndiv_Info(dp8gdData, pAddrHost);

            pAddrHost->lpVtbl->Release(pAddrHost);


            TRACE("number of players: %li. game name len: %li.\n\tguidUnknown: %s,\n\tguidApp: %s.\n\tour player number: %li, player count (again?) %li\n",
                  msgPeerInfo->player_count, msgPeerInfo->game_name_len, debugstr_guid(&msgPeerInfo->guidUnknown),
                  debugstr_guid(&msgPeerInfo->guidApp), msgPeerInfo->our_player_number, msgPeerInfo->player_count_2);

            string_pos += (msgPeerInfo->player_count * sizeof(dp8pmsg_part_PeerInfo_Player));
            TRACE("string pos is: %p. msgPeerPlayerInfo is: %p. size is: %i\n", string_pos, msgPeerPlayerInfo, sizeof(dp8pmsg_part_PeerInfo_Player));

            for (i = msgPeerInfo->player_count - 1; i >= 0; i--)
            {
                WCHAR *player_name;
                char *player_uri;
                PEER_player *newPlayer;

                if (msgPeerPlayerInfo[i].player_data_len)
                {
                    FIXME("player has data. don't know if this is before or after URL. Its before name.\n");
                }

                player_uri = (char *)string_pos;
                string_pos += msgPeerPlayerInfo[i].uri_len;

                player_name = (WCHAR *)string_pos;
                string_pos += msgPeerPlayerInfo[i].player_name_len;

                TRACE("(%i) player dpnid: %08lx\n\t"
                      "player_number: %li. player_name_len: %li, uri_len: %li\n\t"
                      "player_name: %s\n\t"
                      "player_uri: %s\n", i,
                      msgPeerPlayerInfo[i].player_id, msgPeerPlayerInfo[i].player_number,
                      msgPeerPlayerInfo[i].player_name_len, msgPeerPlayerInfo[i].uri_len,
                      debugstr_w(player_name), player_uri);
                /* FIXME: data is probably in here somewhere */

                if (i == msgPeerInfo->player_count - 1) /* last = local? */
                {
                    newPlayer = &dp8gdData->localPlayer;
                }
                else
                {
                    newPlayer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                                              sizeof(PEER_player) + (strlenW(player_name) * sizeof(WCHAR)));
                }
                newPlayer->set = TRUE;
                newPlayer->dwPlayerID = msgPeerPlayerInfo[i].player_id;
                newPlayer->pdpnPlayerInfo.dwSize = sizeof(DPN_PLAYER_INFO) + (strlenW(player_name) * sizeof(WCHAR));
                newPlayer->pdpnPlayerInfo.dwInfoFlags = DPNINFO_NAME;
                newPlayer->pdpnPlayerInfo.pwszName = (WCHAR *)(newPlayer + sizeof(PEER_player));
                memcpy(newPlayer->pdpnPlayerInfo.pwszName, player_name, strlenW(player_name) * sizeof(WCHAR));
                newPlayer->pdpnPlayerInfo.pvData = NULL;
                newPlayer->pdpnPlayerInfo.dwDataSize = 0;
                newPlayer->pdpnPlayerInfo.dwPlayerFlags = 0;

                /* HACK: */
                if (i == msgPeerInfo->player_count - 1) /* last = local? */
                {
                    newPlayer->pdpnPlayerInfo.dwPlayerFlags |= DPNPLAYER_LOCAL;
                }
                else
                {
                    if (i == 0)
                        newPlayer->pdpnPlayerInfo.dwPlayerFlags |= DPNPLAYER_HOST;

                    if (dp8gdData->remotePlayers)
                    {
                        dp8gdData->remotePlayers->next = newPlayer;
                        newPlayer->prev = dp8gdData->remotePlayers;
                    }
                    dp8gdData->remotePlayers = newPlayer;
                }

                hr = CoCreateInstance(&CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER,
                                      &IID_IDirectPlay8Address,
                                      (LPVOID *) &newPlayer->pAddress);
                if (FAILED(hr))
                {
                    ERR("couldn't create address object\n");
                }
                else
                {
                    /* the host doesn't have a URI */
                    if (newPlayer->pdpnPlayerInfo.dwPlayerFlags & DPNPLAYER_HOST)
                        IDirectPlay8ServiceProvider_BuildAddressFromSPData(dp8gdData->sp,
                                                                           newPlayer->pAddress,
                                                                           spAddrData);
                    else
                        IDirectPlay8Address_BuildFromURLA(newPlayer->pAddress, player_uri);
                }
            }
            TRACE("\t Game name is: %s\n", debugstr_w((WCHAR *)string_pos));

            if (msgPeerInfo->reply_data_len)
            {
                string_pos += (strlenW((WCHAR *)string_pos) + 1) * sizeof(WCHAR);
                dp8gdData->reply_data = HeapAlloc(GetProcessHeap(), 0, msgPeerInfo->reply_data_len);
                memcpy(dp8gdData->reply_data, string_pos, msgPeerInfo->reply_data_len);
                dp8gdData->reply_data_len = msgPeerInfo->reply_data_len;
            }

#if 1 /* civ3 */
            {
                /* FIXME: for some reason windows doesn't send a 0xc6 message from civ3 hosts.
                 * This message (below) i figured was connect succesful.
                 * However, we don't get it, or any other similar message. Perhaps sometimes it is
                 * included in this message?.. So heres a hack for civ3:
                 */
                DPNMSG_CONNECT_COMPLETE dpnMsgConnectComplete;

                dpnMsgConnectComplete.dwSize = sizeof(dpnMsgConnectComplete);
                dpnMsgConnectComplete.hAsyncOp = 0;
                dpnMsgConnectComplete.pvUserContext = 0;
                dpnMsgConnectComplete.hResultCode = S_OK;

                if (dp8gdData->reply_data_len) /* I don't understand why if its succesful the reply data is in the peer
                                                   info structure.. if it fails its in the connect reply message? */
                {
                    dpnMsgConnectComplete.dwApplicationReplyDataSize = dp8gdData->reply_data_len;
                    dpnMsgConnectComplete.pvApplicationReplyData =
                        HeapAlloc(GetProcessHeap(), 0, dp8gdData->reply_data_len);
                    memcpy(dpnMsgConnectComplete.pvApplicationReplyData,
                           dp8gdData->reply_data,
                           dp8gdData->reply_data_len);
                    HeapFree(GetProcessHeap(), 0, dp8gdData->reply_data);
                    dp8gdData->reply_data_len = 0;
                    dp8gdData->reply_data = NULL;
                }
                else
                {
                    dpnMsgConnectComplete.dwApplicationReplyDataSize = 0;
                    dpnMsgConnectComplete.pvApplicationReplyData = NULL;
                }
                TRACE("sending DPN_MSGID_CONNECT_COMPLETE message\n");
                dp8gdData->pfMessageHandler(dp8gdData->pvUserContext, DPN_MSGID_CONNECT_COMPLETE,
                                            &dpnMsgConnectComplete);

                if (dpnMsgConnectComplete.pvApplicationReplyData)
                    HeapFree(GetProcessHeap(), 0, dpnMsgConnectComplete.pvApplicationReplyData);
                TRACE("message handler returned\n");
            }
#endif

            /* now send the create player messages */
            curPlayer = dp8gdData->remotePlayers;
            while (curPlayer)
            {
                DPNMSG_CREATE_PLAYER dpnMsgCreatePlayer;

                dpnMsgCreatePlayer.dwSize = sizeof(dpnMsgCreatePlayer);
                dpnMsgCreatePlayer.dpnidPlayer = curPlayer->dwPlayerID;
                dpnMsgCreatePlayer.pvPlayerContext = NULL;
                TRACE("sending CREATE_PLAYER message to app\n");
                dp8gdData->pfMessageHandler(dp8gdData->pvUserContext, DPN_MSGID_CREATE_PLAYER,
                                            &dpnMsgCreatePlayer);
                TRACE("message handler returned\n");
                curPlayer->pvPlayerContext = dpnMsgCreatePlayer.pvPlayerContext;

                if (curPlayer->pdpnPlayerInfo.dwPlayerFlags & DPNPLAYER_LOCAL)
                {
                    ERR("shouldn't happen\n");
                    /*
                    HeapFree(GetProcessHeap(), 0, curPlayer);
                    */
                }
                curPlayer = curPlayer->next;
            }
            /* then local player, last */
            {
                DPNMSG_CREATE_PLAYER dpnMsgCreatePlayer;

                dpnMsgCreatePlayer.dwSize = sizeof(dpnMsgCreatePlayer);
                dpnMsgCreatePlayer.dpnidPlayer = dp8gdData->localPlayer.dwPlayerID;
                dpnMsgCreatePlayer.pvPlayerContext = NULL;
                TRACE("sending CREATE_PLAYER message to app\n");
                dp8gdData->pfMessageHandler(dp8gdData->pvUserContext, DPN_MSGID_CREATE_PLAYER,
                                            &dpnMsgCreatePlayer);
                TRACE("message handler returned\n");
                dp8gdData->localPlayer.pvPlayerContext = dpnMsgCreatePlayer.pvPlayerContext;
            }

            break;
        }
    case 0xc5:
    case 0xc6:
        {
            DPNMSG_CONNECT_COMPLETE dpnMsgConnectComplete; /* fixme. dont know if this should be here. and if it should, we should only send it once. need more testing on win32 */
            dp8pmsg_SetPeerInfo_Connected *msgSetPeerInfoConnected = (dp8pmsg_SetPeerInfo_Connected *)message;
            HRESULT hr;
            IDirectPlay8Address *pAddrHost;

            hr = CoCreateInstance(&CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER,
                                  &IID_IDirectPlay8Address,
                                  (LPVOID *) &pAddrHost);
            if (FAILED(hr))
            {
                ERR("couldn't create address object\n");
                break;
            }
            dp8gdData->sp->lpVtbl->BuildAddressFromSPData(dp8gdData->sp, pAddrHost, spAddrData);

            DirectPlay8Communicate_Packet_SendAck(dp8gdData, pAddrHost, msgSetPeerInfoConnected->packet_count + 1,
                                                  0x06, NULL, 0);

            pAddrHost->lpVtbl->Release(pAddrHost);

            dpnMsgConnectComplete.dwSize = sizeof(dpnMsgConnectComplete);
            dpnMsgConnectComplete.hAsyncOp = 0;
            dpnMsgConnectComplete.pvUserContext = 0;
            if (msgSetPeerInfoConnected->unknown_05 == 0xc5)
                dpnMsgConnectComplete.hResultCode = DPNERR_HOSTREJECTEDCONNECTION;
            if (msgSetPeerInfoConnected->unknown_05 == 0xc6)
                dpnMsgConnectComplete.hResultCode = S_OK;

            if (msgSetPeerInfoConnected->data_size && dpnMsgConnectComplete.hResultCode == DPNERR_HOSTREJECTEDCONNECTION)
            {
                dpnMsgConnectComplete.dwApplicationReplyDataSize = msgSetPeerInfoConnected->data_size;
                dpnMsgConnectComplete.pvApplicationReplyData =
                    HeapAlloc(GetProcessHeap(), 0, msgSetPeerInfoConnected->data_size);
                memcpy(dpnMsgConnectComplete.pvApplicationReplyData,
                       msgSetPeerInfoConnected->data,
                       msgSetPeerInfoConnected->data_size);
            }
            else if (dp8gdData->reply_data_len) /* I don't understand why if its succesful the reply data is in the peer
                                                   info structure.. if it fails its in the connect reply message? */
            {
                dpnMsgConnectComplete.dwApplicationReplyDataSize = dp8gdData->reply_data_len;
                dpnMsgConnectComplete.pvApplicationReplyData =
                    HeapAlloc(GetProcessHeap(), 0, dp8gdData->reply_data_len);
                memcpy(dpnMsgConnectComplete.pvApplicationReplyData,
                       dp8gdData->reply_data,
                       dp8gdData->reply_data_len);
                HeapFree(GetProcessHeap(), 0, dp8gdData->reply_data);
                dp8gdData->reply_data_len = 0;
                dp8gdData->reply_data = NULL;
            }
            else
            {
                dpnMsgConnectComplete.dwApplicationReplyDataSize = 0;
                dpnMsgConnectComplete.pvApplicationReplyData = NULL;
            }
            TRACE("sending DPN_MSGID_CONNECT_COMPLETE message\n");
            dp8gdData->pfMessageHandler(dp8gdData->pvUserContext, DPN_MSGID_CONNECT_COMPLETE,
                                        &dpnMsgConnectComplete);

            if (dpnMsgConnectComplete.pvApplicationReplyData)
                HeapFree(GetProcessHeap(), 0, dpnMsgConnectComplete.pvApplicationReplyData);
            TRACE("message handler returned\n");
            break;
        }

    default:
        FIXME("unknown byte 5, %x\n", ((BYTE *)message)[4]);
    }
}

static void DPNET_ProcessMessage_Ack(DirectPlay8GlobalData *dp8gdData, void *message, int bytes, PVOID spAddrData)
{
    dp8pmsg_Ack *msgAck = (dp8pmsg_Ack *)message;

    TRACE("(%p, %p, %i, %p)\n", dp8gdData, message, bytes, spAddrData);
    if (bytes != 12 && bytes != 16)
    {
        FIXME("unknown ack size: %i. expecting 12 or 16.\n", bytes);
        return;
    }
    switch (msgAck->reply_type)
    {
    case 0x02:
        {
            FIXME("ack from a connect?\n");
            break;
        }
    case 0x06:
        {
            dpOutMessageToAddr *outMessage; /* message that we sent */
            while ((outMessage = DPNET_findOutMessageToAddrByPID(dp8gdData, msgAck->orig_packet_count - 1)))
            {
                DPNET_FreeMsgToAddrNode(dp8gdData, outMessage);
            }
            FIXME("do we need to tell the app the message got there OK?\n");

            break;
        }
    default:
        FIXME("unknown ack (%x)\n", msgAck->reply_type);
    }
}

static void DPNET_ProcessMessage_MessageData(DirectPlay8GlobalData *dp8gdData, void *message, int bytes, PVOID spAddrData)
{
    dp8pmsg_MessageData *msgData = message;
    DPNMSG_RECEIVE *dpnMsgReceive;
    int datalen;
    DPNID dpnidSender = 0; /* FIXME */
    void *pvPlayerContext = NULL;
    HRESULT hr;
    IDirectPlay8Address *pAddrSender;

    TRACE("processing message data\n");
    /* FIXME UGLY HACK. for now just take the first remote player (or local player if there are none) */

    if (dp8gdData->remotePlayers)
    {
        dpnidSender = dp8gdData->remotePlayers->dwPlayerID;
        pvPlayerContext = dp8gdData->remotePlayers->pvPlayerContext;
    }
    else if (dp8gdData->localPlayer.set)
    {
        dpnidSender = dp8gdData->localPlayer.dwPlayerID;
        pvPlayerContext = dp8gdData->localPlayer.pvPlayerContext;
    }
    else
    {
        ERR("no players..\n");
        return;
    }

    datalen = bytes - sizeof(dp8pmsg_MessageData);
    if (!datalen)
    { /* FIXME */
        WARN("there was no data, not doing anything\n");
        return;
    }
    dpnMsgReceive = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DPNMSG_RECEIVE) + datalen);
    dpnMsgReceive->dwSize = sizeof(DPNMSG_RECEIVE);
    dpnMsgReceive->dpnidSender = dpnidSender;
    dpnMsgReceive->pvPlayerContext = pvPlayerContext;
    dpnMsgReceive->pReceiveData = (void *)dpnMsgReceive + sizeof(DPNMSG_RECEIVE);
    memcpy(dpnMsgReceive->pReceiveData, msgData->data, datalen);
    dpnMsgReceive->dwReceiveDataSize = datalen;
    dpnMsgReceive->hBufferHandle = 0;

    hr = CoCreateInstance(&CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER,
                          &IID_IDirectPlay8Address,
                          (LPVOID *) &pAddrSender);
    if (FAILED(hr))
    {
        ERR("couldn't create address object\n");
        return;
    }
    dp8gdData->sp->lpVtbl->BuildAddressFromSPData(dp8gdData->sp, pAddrSender, spAddrData);

    /*if (msgData->protocol_magic == DP8P_MESSAGEDATA_GUARANTEED) */ /* FIXME: send ack anyhow? */
        DirectPlay8Communicate_Packet_SendAck(dp8gdData, pAddrSender, msgData->packet_count + 1,
                                              06, NULL, 0);

    pAddrSender->lpVtbl->Release(pAddrSender);

    hr = dp8gdData->pfMessageHandler(dp8gdData->pvUserContext, DPN_MSGID_RECEIVE,
                                     dpnMsgReceive);
    FIXME("Sent\n");
    if (hr == DPNSUCCESS_PENDING)
    {
        FIXME("we don't support letting the app hold onto the buffer. expect crash\n");
    }
    //HeapFree(GetProcessHeap(), 0, dpnMsgReceive);
}

static void DPNET_ProcessMessage(DirectPlay8GlobalData *dp8gdData, void *message, int bytes, PVOID spAddrData)
{
    TRACE("(%p, %p, %i, %p)\n", dp8gdData, message, bytes, spAddrData);
    /* FIXME: we have to use real addresses here. basically I need to figure out/implement the dp8Address
     * classes, and figure out the player ID stuff.
     */

    if (TRACE_ON(dplay))
    {
        debug_hexdump(message, (unsigned long)bytes);
    }

    if (bytes < 2)
    {
        WARN("message to small\n");
        return;
    }

    switch(((BYTE*)message)[0])
    {
    case DP8P_ENUMHOSTS:
        DPNET_ProcessMessage_EnumHosts(dp8gdData, message, bytes, spAddrData);
        break;
    case DP8P_CONNECT:
        DPNET_ProcessMessage_Connect(dp8gdData, message, bytes, spAddrData);
        break;
    case DP8P_ACK:
        DPNET_ProcessMessage_Ack(dp8gdData, message, bytes, spAddrData);
        break;
    case DP8P_SETPEERINFO:
        DPNET_ProcessMessage_SetPeerInfo(dp8gdData, message, bytes, spAddrData);
        break;
    case DP8P_MESSAGEDATA:
    case DP8P_MESSAGEDATA_UNKNOWN: /* sometimes get this when its not guaranteed */
    case DP8P_MESSAGEDATA_GUARANTEED:
        DPNET_ProcessMessage_MessageData(dp8gdData, message, bytes, spAddrData);
        break;
    default:
        FIXME("unhandled message type: %i\n", ((BYTE*)message)[0]);
        break;
    }
    return;
}


HRESULT DPNET_SwitchSP(DirectPlay8GlobalData *dp8gdData, const GUID *const guidNewSP)
{
    if (!IsEqualGUID(guidNewSP, &dp8gdData->guidSP))
    {
        HRESULT hr;
        if (dp8gdData->sp)
        {
            FIXME("we are destroying a service provider. this could be bad stuff.\n");
            if (globalSP != dp8gdData->sp)
                ERR("OH NO! looks like there could be multiple instances of this object around. we don't support that yet. (fix the hack)\n");
            dp8gdData->sp->lpVtbl->Release(dp8gdData->sp);
            dp8gdData->sp = NULL;
            globalSP = NULL;
        }
        else if (globalSP)
                ERR("OH NO! looks like there could be multiple instances of this object around. we don't support that yet. (fix the hack)\n");
        memcpy(&dp8gdData->guidSP, guidNewSP, sizeof(GUID));
        hr = CoCreateInstance(&dp8gdData->guidSP, NULL, CLSCTX_INPROC_SERVER,
                              &IID_IDirectPlay8ServiceProvider,
                              (LPVOID *) &dp8gdData->sp);
        if (SUCCEEDED(hr))
        {
            dp8gdData->sp->lpVtbl->Initialize(dp8gdData->sp, dp8gdData, DPNET_ProcessMessage, 0);
            globalSP = dp8gdData->sp;
        }
        else
        {
            WARN("failed to switch to new guid %s\n", debugstr_guid(guidNewSP));
            memset(&dp8gdData->guidSP, 0, sizeof(GUID));
            return hr;

        }
    }
    return S_OK;
}

static void DPNET_FreeMsgToAddrNode(DirectPlay8GlobalData *dp8gdData,
                                    dpOutMessageToAddr *msgNode)
{
    IDirectPlay8ThreadPool_Internal *pThreadPool;
    TRACE("(%p, %p)\n", dp8gdData, msgNode);

    dp8gdData->sp->lpVtbl->GetThreadPool(dp8gdData->sp, &pThreadPool);

    if (msgNode->pvPacketIntData) HeapFree(GetProcessHeap(), 0, msgNode->pvPacketIntData);
    if (msgNode->pAddrHost) msgNode->pAddrHost->lpVtbl->Release(msgNode->pAddrHost);
    if (msgNode->pTPRetryInfo)
    {
        IDirectPlay8ThreadPool_Internal_DeleteTimerItem(pThreadPool,
                                                        msgNode->pTPRetryInfo);
    }
    if (msgNode->pTPTimeoutInfo)
    {
        IDirectPlay8ThreadPool_Internal_DeleteTimerItem(pThreadPool,
                                                        msgNode->pTPTimeoutInfo);
    }
    IDirectPlay8ThreadPool_Internal_Release(pThreadPool);
    if (dp8gdData->msgToAddrTail == msgNode)
        dp8gdData->msgToAddrTail = msgNode->prev;
    if (msgNode->next != NULL)
        msgNode->next->prev = msgNode->prev;
    if (msgNode->prev != NULL)
        msgNode->prev->next = msgNode->next;
    HeapFree(GetProcessHeap(), 0, msgNode);
    return;
}

static void DPNET_SendMsgToAddrRetry(void *pvDp8gdData, void *pvMsgNode)
{
    DirectPlay8GlobalData *dp8gdData = (DirectPlay8GlobalData *) pvDp8gdData;
    dpOutMessageToAddr *msgNode = (dpOutMessageToAddr *) pvMsgNode;
    IDirectPlay8ThreadPool_Internal *pThreadPool;

    TRACE("(%p, %p)\n", pvDp8gdData, msgNode);

    if (msgNode->dwRetryLimit && msgNode->dwRetries >= msgNode->dwRetryLimit)
    {
        DPNET_FreeMsgToAddrNode(dp8gdData, msgNode);
        FIXME("need to send message retry end to app\n");
        return;
    }

    msgNode->dwTimeSent = GetTickCount();
    dp8gdData->sp->lpVtbl->SendToAddress(dp8gdData->sp, msgNode->pAddrHost,
                                         msgNode->msg, msgNode->dwSize,
                                         msgNode->dwFlags);
    dp8gdData->sp->lpVtbl->GetThreadPool(dp8gdData->sp, &pThreadPool);
    IDirectPlay8ThreadPool_Internal_ResetTimerItem(pThreadPool,
                                                   msgNode->pTPRetryInfo);
    if (msgNode->pTPTimeoutInfo)
        /* FIXME: not sure i'm supposed to do this.. timeout with retries? */
        IDirectPlay8ThreadPool_Internal_ResetTimerItem(pThreadPool,
                                                       msgNode->pTPTimeoutInfo);
    IDirectPlay8ThreadPool_Internal_Release(pThreadPool);

    return;
}

static void DPNET_SendMsgToAddrTimeout(void *pvDp8gdData, void *pvMsgNode)
{
    DirectPlay8GlobalData *dp8gdData = (DirectPlay8GlobalData *) pvDp8gdData;
    dpOutMessageToAddr *msgNode = (dpOutMessageToAddr *) pvMsgNode;

    TRACE("(%p, %p)\n", pvDp8gdData, pvMsgNode);
    FIXME("need to send message timeout to app\n");
    DPNET_FreeMsgToAddrNode(dp8gdData, msgNode);
    return;
}


HRESULT DPNET_SendMessageToAddress(DirectPlay8GlobalData *dp8gdData,
                                   IDirectPlay8Address *pAddrHost, LPVOID pPacket,
                                   DWORD dwSize, DWORD dwFlags, DWORD dwRetryLimit,
                                   DWORD dwRetryInterval, DWORD dwTimeout, BYTE packetID,
                                   void *pvPacketIntData)
{
    dpOutMessageToAddr *msgNode;
    IDirectPlay8ThreadPool_Internal *pThreadPool;
    DPTP_timerInfo timerInfo;

    TRACE("(%p, %p, %p, %li, 0x%08lx, %li, %li, %li, %i)\n", dp8gdData, pAddrHost, pPacket,
         dwSize, dwFlags, dwRetryLimit, dwRetryInterval, dwTimeout, packetID);

    msgNode = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(dpOutMessageToAddr) + dwSize);
    if (dp8gdData->msgToAddrTail != NULL)
        dp8gdData->msgToAddrTail->next = msgNode;
    msgNode->prev = dp8gdData->msgToAddrTail;
    dp8gdData->msgToAddrTail = msgNode;
    if (pAddrHost) pAddrHost->lpVtbl->AddRef(pAddrHost);
    msgNode->pAddrHost = pAddrHost;
    msgNode->dwFlags = dwFlags;
    msgNode->packetID = packetID;
    msgNode->pvPacketIntData = pvPacketIntData;
    msgNode->dwSize = dwSize;
    memcpy(msgNode->msg, pPacket, dwSize);

    dp8gdData->sp->lpVtbl->GetThreadPool(dp8gdData->sp, &pThreadPool);

    msgNode->dwRetryLimit = dwRetryLimit;
    msgNode->dwRetryInterval = dwRetryInterval;
    msgNode->dwTimeout = dwTimeout;

    if (msgNode->dwRetryInterval && msgNode->dwRetryLimit != 0)
    {
        timerInfo.func = DPNET_SendMsgToAddrRetry;
        timerInfo.arg1 = (void *) dp8gdData;
        timerInfo.arg2 = (void *) msgNode;
        timerInfo.dwTimerWait = msgNode->dwRetryInterval;
        IDirectPlay8ThreadPool_Internal_QueueTimerItem(pThreadPool, &timerInfo);
        msgNode->pTPRetryInfo = timerInfo.dpData;
    }

    if (msgNode->dwTimeout && msgNode->dwTimeout != INFINITE)
    {
        timerInfo.func = DPNET_SendMsgToAddrTimeout;
        timerInfo.arg1 = (void *) dp8gdData;
        timerInfo.arg2 = (void *) msgNode;
        timerInfo.dwTimerWait = msgNode->dwTimeout;
        IDirectPlay8ThreadPool_Internal_QueueTimerItem(pThreadPool, &timerInfo);
        msgNode->pTPTimeoutInfo = timerInfo.dpData;
    }

    IDirectPlay8ThreadPool_Internal_Release(pThreadPool);
    msgNode->dwTimeSent = GetTickCount();

    dp8gdData->sp->lpVtbl->SendToAddress(dp8gdData->sp, pAddrHost, pPacket, dwSize, dwFlags);
    return S_OK;
}

HRESULT DirectPlay8Communicate_Packet_SendAck(DirectPlay8GlobalData *dp8gdData,
                                              IDirectPlay8Address *pAddrHost, BYTE packetID,
                                              BYTE reply_type, void *data, DWORD dataSize)
{
    dp8pmsg_Ack *msgAck;

    TRACE("(%p, %p, %i, %i, %p, %li)\n", dp8gdData, pAddrHost, packetID, reply_type, data, dataSize);

    msgAck = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(dp8pmsg_Ack) + dataSize + sizeof(DWORD));

    msgAck->protocol_magic = DP8P_ACK;
    msgAck->reply_type = reply_type;
    msgAck->unknown_03 = 0x01;
    msgAck->unknown_04 = 0x00;
    msgAck->unknown_05 = 0x02;
    msgAck->orig_packet_count = packetID;
    msgAck->unknown_07 = 0x01;
    msgAck->unknown_08 = 0x00;
    if (data && dataSize) memcpy(msgAck->data, data, dataSize);
    *((DWORD *)(msgAck->data + dataSize)) = GetTickCount();

    dp8gdData->sp->lpVtbl->SendToAddress(dp8gdData->sp, pAddrHost, msgAck,
                                         sizeof(dp8pmsg_Ack) + dataSize + sizeof(DWORD), 0);

    return S_OK;
}

HRESULT DirectPlay8Communicate_EnumHosts(DirectPlay8GlobalData *dp8gdData,
                                         PDPN_APPLICATION_DESC const pApplicationDesc,
                                         IDirectPlay8Address *const pAddrHost, IDirectPlay8Address *const pDeviceInfo,
                                         PVOID const pUserEnumData, const DWORD dwUserEnumDataSize,
                                         const DWORD dwEnumCount, const DWORD dwRetryInterval,
                                         const DWORD dwTimeOut, PVOID const pvUserContext,
                                         DPNHANDLE *const pAsyncHandle, const DWORD dwFlags)
{
    /*IDirectPlay8AddressImpl *addrImplHost = (IDirectPlay8AddressImpl *)pAddrHost;*/
    IDirectPlay8AddressImpl *addrImplDevice = (IDirectPlay8AddressImpl *)pDeviceInfo;
    DWORD dwRRetryLimit, dwRRetryInterval, dwRTimeout;
    DPN_SP_CAPS dpnSPCaps;
    dp8pmsg_EnumHostsSend msgEnumHostsSend;

    TRACE("...\n");
    if (!pAddrHost)
    {
        FIXME("don't support NULL address yet\n");
        return S_OK;
    }
    if (pvUserContext) FIXME("we don't support pvUserContext currently\n");

    DPNET_SwitchSP(dp8gdData, &addrImplDevice->guidSP);
    if (pUserEnumData)
        FIXME("we dont support dwUserEnumData yet\n");

    msgEnumHostsSend.protocol_magic = DP8P_ENUMHOSTS;
    msgEnumHostsSend.enum_type = DP8P_ENUMHOSTS_SEND;
    msgEnumHostsSend.packet_count = dp8gdData->dwPacketCount;
    msgEnumHostsSend.unknown_03 = 0x82;
    msgEnumHostsSend.unknown_04 = 0x01;
    memcpy(&msgEnumHostsSend.guidApp, &pApplicationDesc->guidApplication, sizeof(GUID));

    dp8gdData->sp->lpVtbl->GetSPCaps(dp8gdData->sp, &dpnSPCaps, 0);
    if (dwEnumCount)
        dwRRetryLimit = dwEnumCount;
    else
        dwRRetryLimit = dpnSPCaps.dwDefaultEnumCount;

    if (dwRetryInterval)
        dwRRetryInterval = dwRetryInterval;
    else
        dwRRetryInterval = dpnSPCaps.dwDefaultEnumRetryInterval;

    if (dwTimeOut)
        dwRTimeout = dwTimeOut;
    else
        dwRTimeout = dpnSPCaps.dwDefaultEnumTimeout;

    DPNET_SendMessageToAddress(dp8gdData, pAddrHost, &msgEnumHostsSend, sizeof(msgEnumHostsSend),
                               dwFlags, dwRRetryLimit, dwRRetryInterval, dwRTimeout,
                               dp8gdData->dwPacketCount, NULL);
    dp8gdData->dwPacketCount++;

    return S_OK;
}

HRESULT DirectPlay8Communicate_Peer_Connect(DirectPlay8GlobalData *dp8gdData,
                                            const DPN_APPLICATION_DESC *const pdnAppDesc,
                                            IDirectPlay8Address *pHostAddr,
                                            IDirectPlay8Address *pDeviceInfo,
                                            const DPN_SECURITY_DESC * const pdnSecurity,
                                            const DPN_SECURITY_CREDENTIALS *const pdnCredentials,
                                            const void *const pvUserConnectData,
                                            DWORD dwUserConnectDataSize,
                                            void *pvPlayerContext, void *pvAsyncContext,
                                            DPNHANDLE *phAsyncHandle, DWORD dwFlags)
{
    /*IDirectPlay8AddressImpl *addrImplHost = (IDirectPlay8AddressImpl *)pHostAddr;*/
    /*IDirectPlay8AddressImpl *addrImplDevice = (IDirectPlay8AddressImpl *)pDeviceInfo;*/
    dp8pmsg_Connect msgConnect;
    DPN_CAPS dpnCaps;
    dpOutMessageToAddr *curMessage = dp8gdData->msgToAddrTail; /* hack, see bellow */

    TRACE("...\n");

    if (dwUserConnectDataSize)
    {
        dp8gdData->connect_data_len = dwUserConnectDataSize;
        dp8gdData->connect_data = HeapAlloc(GetProcessHeap(), 0, sizeof(dwUserConnectDataSize));
        memcpy(dp8gdData->connect_data, pvUserConnectData, dwUserConnectDataSize);
    }

    /* FIXME: do we need to switch service providers here? */

    /* remove enumhosts requests */
    /* hack: this shouldn't be here, i know (it should happen after connection
     * has been completed.) but this makes it easier for me to watch the wire
     * without annoying enum packets.
     */
    while (curMessage)
    {
        dpOutMessageToAddr *nextMessage = curMessage->next;
        if (curMessage->dwSize == 0) continue;
        if (curMessage->msg[0] == DP8P_ENUMHOSTS)
        {
            DPNET_FreeMsgToAddrNode(dp8gdData, curMessage);
        }
        curMessage = nextMessage;
    }
    /* end hack */


    dp8gdData->dwPacketCount = 0;
    if (pdnSecurity || pdnCredentials)
        FIXME("we dont support security stuff right now\n");

    msgConnect.protocol_magic = DP8P_CONNECT;
    msgConnect.connect_type = 0x01;
    msgConnect.packet_count = dp8gdData->dwPacketCount;
    msgConnect.unknown_04 = 0x00;
    msgConnect.unknown_05 = 0x02;
    msgConnect.unknown_06 = 0x00;
    msgConnect.unknown_07 = 0x01;
    msgConnect.unknown_08 = 0x00;
    msgConnect.unknown_09 = 0x13a2134f;
    msgConnect.time_sent = GetTickCount();


    dpnCaps.dwSize = sizeof(dpnCaps);
    DirectPlay8Communicate_GetCaps(dp8gdData, &dpnCaps, 0);

    DPNET_SendMessageToAddress(dp8gdData, pHostAddr, &msgConnect,
                               sizeof(dp8pmsg_Connect), dwFlags,
                               dpnCaps.dwConnectRetries, dpnCaps.dwConnectTimeout, 0,
                               dp8gdData->dwPacketCount, NULL);

    dp8gdData->dwPacketCount++;
    return S_OK;
}

/* sends local peer info stored in dp8gdData to remote players.. (FIXME: multiple players) */
HRESULT DirectPlay8Communicate_SendPeerInfo(DirectPlay8GlobalData *dp8gdData, IDirectPlay8Address *pAddr)
{
    dp8pmsg_SetPeerInfo_Local *msgSetPeerInfo;
    int name_len_b;

    TRACE("(%p, %p)\n", dp8gdData, pAddr);

    name_len_b = strlenW(dp8gdData->localPlayer.pdpnPlayerInfo.pwszName) * sizeof(WCHAR);
    msgSetPeerInfo = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY,
                               sizeof(dp8pmsg_SetPeerInfo_Local) + name_len_b + sizeof(WCHAR) + dp8gdData->connect_data_len);

    msgSetPeerInfo->protocol_magic = DP8P_SETPEERINFO;
    msgSetPeerInfo->unknown_02 = 0x00;
    msgSetPeerInfo->packet_count = dp8gdData->dwPacketCount;
    msgSetPeerInfo->unknown_04 = 0x00;
    msgSetPeerInfo->unknown_05 = 0xc1;
    msgSetPeerInfo->unknown_09 = 0x04;
    msgSetPeerInfo->unknown_13 = 0x02;
    msgSetPeerInfo->unknown_17 = 0x50 + dp8gdData->connect_data_len; /* fixme, ??? */
    msgSetPeerInfo->player_name_len = name_len_b + sizeof(WCHAR);
    msgSetPeerInfo->unknown_21 = 0x00;
    msgSetPeerInfo->unknown_25 = 0x00;
    msgSetPeerInfo->unknown_29 = 0x00;
    msgSetPeerInfo->unknown_33 = 0x00;
    msgSetPeerInfo->unknown_37 = dp8gdData->connect_data_len ? 0x50 : 0x00; /* fixme, ??? */
    msgSetPeerInfo->connect_data_len = dp8gdData->connect_data_len;
    msgSetPeerInfo->unknown_45 = 0x00;
    msgSetPeerInfo->unknown_49 = 0x00;
    memcpy(&msgSetPeerInfo->guidApp, &dp8gdData->guidApplication, sizeof(GUID));
    memcpy(&msgSetPeerInfo->guidUnknown, &dp8gdData->guidInstance, sizeof(GUID));
    memcpy(&msgSetPeerInfo->data, dp8gdData->connect_data, dp8gdData->connect_data_len);
    memcpy(((char*)&msgSetPeerInfo->data) + dp8gdData->connect_data_len,
           dp8gdData->localPlayer.pdpnPlayerInfo.pwszName, name_len_b + sizeof(WCHAR));

    if (dp8gdData->localPlayer.pdpnPlayerInfo.dwDataSize)
    {
        FIXME("need to send peer data\n");
    }

    if (!pAddr)
    {
        FIXME("how are we supposed to deal with no pAddr?.. We should send it to all peers, if we have a successful connection. Otherwise queue it?.. Not sure\n");
        return DPNERR_NOCONNECTION;
    }
    DPNET_SendMessageToAddress(dp8gdData, pAddr, msgSetPeerInfo,
                               sizeof(dp8pmsg_SetPeerInfo_Local) + name_len_b + sizeof(WCHAR) + dp8gdData->connect_data_len,
                               0, 0/*5*/, 500 /* ms */, 0, dp8gdData->dwPacketCount, NULL);

    if (dp8gdData->connect_data)
    {
        dp8gdData->connect_data_len = 0;
        HeapFree(GetProcessHeap(), 0, dp8gdData->connect_data);
        dp8gdData->connect_data = NULL;
    }

    HeapFree(GetProcessHeap(), 0, msgSetPeerInfo);

    dp8gdData->dwPacketCount++;

    return S_OK;
}

/* FIXME */
HRESULT DirectPlay8Communicate_Peer_SendTo(DirectPlay8GlobalData *dp8gdData, const DPNID dpnid,
                                           const DPN_BUFFER_DESC *const pBufferDesc,
                                           const DWORD cBufferDesc, const DWORD dwTimeOut,
                                           void const *pvAsyncContext,
                                           DPNHANDLE *const phAsyncHandle, const DWORD dwFlags)
{
    dp8pmsg_MessageData *msgData;
    HRESULT hr = DPNSUCCESS_PENDING;

    if (cBufferDesc != 1)
    {
        FIXME("we only support one buffer desc\n");
    }
    /* FIXME: use frames */
    TRACE("sending packet of size: %li\n", pBufferDesc[0].dwBufferSize);

    msgData = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(dp8pmsg_MessageData) + pBufferDesc[0].dwBufferSize);

    msgData->protocol_magic = (dwFlags & DPNSEND_GUARANTEED) ? DP8P_MESSAGEDATA_GUARANTEED : DP8P_MESSAGEDATA;
    msgData->unknown_02 = 0x00;
    msgData->packet_count = dp8gdData->dwPacketCount;
    msgData->unknown_04 = 0x00;
    if (pBufferDesc[0].dwBufferSize && pBufferDesc[0].pBufferData)
        memcpy(msgData->data, pBufferDesc[0].pBufferData,
               pBufferDesc[0].dwBufferSize);

    FIXME("we need to figure out sequential stuff (and fragments).\n");
    if (dwTimeOut) FIXME("we dont properly support timeout\n");

    if (dpnid == DPNID_ALL_PLAYERS_GROUP)
    {
        PEER_player *curPlayer = dp8gdData->remotePlayers;
        while (curPlayer)
        {
            DPNET_SendMessageToAddress(dp8gdData, curPlayer->pAddress, msgData,
                                       sizeof(dp8pmsg_MessageData) + pBufferDesc[0].dwBufferSize,
                                       0, ((dwFlags & DPNSEND_GUARANTEED) ? 1024 : 0),
                                       500, ((dwFlags & DPNSEND_GUARANTEED) ? INFINITE : 1000),
                                       dp8gdData->dwPacketCount, NULL);
            curPlayer = curPlayer->prev;
        }
        if (!(dwFlags & DPNSEND_NOLOOPBACK))
        {
            DPNMSG_RECEIVE *dpnMsgReceive;
            DPNID ourPlayerId = 0;
            HRESULT hr;
            PVOID pvPlayerContext = NULL;

            TRACE("sending to ourselves, also\n");

            ourPlayerId = dp8gdData->localPlayer.dwPlayerID;
            pvPlayerContext = dp8gdData->localPlayer.pvPlayerContext;

            dpnMsgReceive = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DPNMSG_RECEIVE) +
                                      pBufferDesc[0].dwBufferSize);
            dpnMsgReceive->dwSize = sizeof(DPNMSG_RECEIVE);
            dpnMsgReceive->dpnidSender = ourPlayerId;
            dpnMsgReceive->pvPlayerContext = pvPlayerContext;
            dpnMsgReceive->pReceiveData = (void *)dpnMsgReceive + sizeof(DPNMSG_RECEIVE);
            memcpy(dpnMsgReceive->pReceiveData, pBufferDesc[0].pBufferData, pBufferDesc[0].dwBufferSize);
            dpnMsgReceive->dwReceiveDataSize = pBufferDesc[0].dwBufferSize;
            dpnMsgReceive->hBufferHandle = 0;

            hr = dp8gdData->pfMessageHandler(dp8gdData->pvUserContext, DPN_MSGID_RECEIVE,
                                             dpnMsgReceive);
            if (hr == DPNSUCCESS_PENDING)
            {
                FIXME("we don't support letting the app hold onto the buffer. expect crash\n");
            }
            /* no, its now a memory leak! yay! */
            //HeapFree(GetProcessHeap(), 0, dpnMsgReceive);
        }
    }
    else
    {
        PEER_player *curPlayer = dp8gdData->remotePlayers;
        while (curPlayer)
        {
            if (curPlayer->dwPlayerID == dpnid)
            {
                DPNET_SendMessageToAddress(dp8gdData, curPlayer->pAddress, msgData,
                                           sizeof(dp8pmsg_MessageData) + pBufferDesc[0].dwBufferSize,
                                           0, ((dwFlags & DPNSEND_GUARANTEED) ? 1024 : 0),
                                           500, ((dwFlags & DPNSEND_GUARANTEED) ? INFINITE : 1000),
                                           dp8gdData->dwPacketCount, NULL);
            }
            curPlayer = curPlayer->prev;
        }
        hr = DPNERR_INVALIDPLAYER;
    }

    HeapFree(GetProcessHeap(), 0, msgData);

    dp8gdData->dwPacketCount++;

    return hr;
}

