[PATCH 3/3] dpnet: Implement IDirectPlay8Client Enumhost when called synchronously

Alistair Leslie-Hughes leslie_alistair at hotmail.com
Thu Nov 10 01:37:32 CST 2016


v2
Split tests into seperate patch
Removed extra structures/defines from dppacket.h
Rewrite to only deal with synchronously for now

Signed-off-by: Alistair Leslie-Hughes <leslie_alistair at hotmail.com>
---
 dlls/dpnet/client.c        | 255 ++++++++++++++++++++++++++++++++++++++++++++-
 dlls/dpnet/dpnet_private.h |   4 +
 dlls/dpnet/dppacket.h      |  80 ++++++++++++++
 dlls/dpnet/tests/client.c  |   3 +
 4 files changed, 340 insertions(+), 2 deletions(-)
 create mode 100644 dlls/dpnet/dppacket.h

diff --git a/dlls/dpnet/client.c b/dlls/dpnet/client.c
index 0da7810..b00a8e3 100644
--- a/dlls/dpnet/client.c
+++ b/dlls/dpnet/client.c
@@ -56,6 +56,52 @@ static inline IDirectPlay8ClientImpl *impl_from_IDirectPlay8Client(IDirectPlay8C
     return CONTAINING_RECORD(iface, IDirectPlay8ClientImpl, IDirectPlay8Client_iface);
 }
 
+/*
+ * Direct Play clients bind to a port between 2300-2400.  Even though it's not a requirement,
+ *  Network Analysers fail to detect them as directplay packets when not in this range.
+ */
+static SOCKET find_free_socket(void)
+{
+    SOCKET sock;
+    struct sockaddr_in addr;
+    ULONG value;
+    int port = 2302;    /* Native IDirectPlay8Client likes this port for the client. */
+
+    sock = socket(AF_INET, SOCK_DGRAM,  IPPROTO_UDP);
+    if(sock == INVALID_SOCKET)
+    {
+        ERR("Cannot create socket.\n");
+        return sock;
+    }
+
+    memset(&addr, 0, sizeof(addr));
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(port);
+    addr.sin_addr.s_addr = htonl(INADDR_ANY);
+
+    value = 1;
+    setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char *)&value, sizeof(value));
+
+    while(port < 2400)
+    {
+       if(!bind(sock, (struct sockaddr*)&addr, sizeof(addr)))
+       {
+           TRACE("bound to port (%d).\n", port);
+           return sock;
+       }
+       if(WSAGetLastError() == WSAEADDRINUSE)
+           addr.sin_port = htons(++port);
+       else
+           break;
+    }
+
+    ERR("No free ports available.\n");
+    closesocket(sock);
+    sock = INVALID_SOCKET;
+
+    return sock;
+}
+
 /* IDirectPlay8Client IUnknown parts follow: */
 static HRESULT WINAPI IDirectPlay8ClientImpl_QueryInterface(IDirectPlay8Client *iface, REFIID riid,
         void **ppobj)
@@ -92,6 +138,8 @@ static ULONG WINAPI IDirectPlay8ClientImpl_Release(IDirectPlay8Client *iface)
 
     if (!ref)
     {
+        closesocket(This->sock);
+
         heap_free(This->username);
         heap_free(This->data);
         heap_free(This);
@@ -130,6 +178,173 @@ static HRESULT WINAPI IDirectPlay8ClientImpl_EnumServiceProviders(IDirectPlay8Cl
   return DPN_OK; 
 }
 
+static IDirectPlay8Address *create_sender_address(struct sockaddr_in *addr)
+{
+    IDirectPlay8Address *sender = NULL;
+    char ip[INET6_ADDRSTRLEN] = {0};
+    int port;
+    HRESULT hr;
+
+    hr = DPNET_CreateDirectPlay8Address(NULL, NULL, &IID_IDirectPlay8Address, (void**)&sender);
+    if(FAILED(hr))
+        return sender;
+
+    inet_ntop(addr->sin_family, &addr->sin_addr, ip, sizeof(ip));
+    port = ntohs(addr->sin_port);
+    TRACE("Server IP address %s:%d\n", ip, port);
+
+    if(FAILED(IDirectPlay8Address_AddComponent(sender, DPNA_KEY_HOSTNAME, &ip, strlen(ip)+1, DPNA_DATATYPE_STRING_ANSI)))
+    {
+        ERR("Failed to add DPNA_KEY_HOSTNAME\n");
+        IDirectPlay8Address_Release(sender);
+        return NULL;
+    }
+    if(FAILED(IDirectPlay8Address_AddComponent(sender, DPNA_KEY_PORT, &port, sizeof(DWORD), DPNA_DATATYPE_DWORD)))
+    {
+        ERR("Failed to add DPNA_KEY_PORT\n");
+        IDirectPlay8Address_Release(sender);
+        sender = NULL;
+    }
+
+    return sender;
+}
+
+static int send_packet_enum_query(SOCKET sock, GUID application, struct sockaddr_in *from)
+{
+    struct ENUM_QUERY query;
+    static DWORD payloadvalue = 0;
+    int err;
+
+    query.lead        = 0x00;
+    query.command     = PACKET_ENUM_QUERY;
+    query.type        = PACKET_QUERY_GUID;
+    query.application = application;
+    query.payload     = payloadvalue++;
+
+    err = sendto(sock, (void *)&query, sizeof(query), 0, (struct sockaddr *)from, sizeof(struct sockaddr_in));
+    if(err == -1)
+        ERR("Sendto failed (%d).\n", WSAGetLastError());
+
+    return err;
+}
+
+static void process_sync_enumhost(SOCKET sock, IDirectPlay8Address *device, PFNDPNMESSAGEHANDLER msghandler, GUID application,
+        DWORD retrycnt, DWORD retrytime, DWORD timeout)
+{
+    char buffer[1024];
+    int buflen = sizeof(buffer);
+    DWORD starttime = 0;
+    struct sockaddr_in from;
+    int fromlen = sizeof(from);
+    int received = 0;
+
+    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
+
+    while(TRUE)
+    {
+        if(retrycnt && (GetTickCount() - starttime) >= retrytime)
+        {
+            TRACE("Sending Request.\n");
+
+            from.sin_family = AF_INET;
+            from.sin_port = htons(DPNA_DPNSVR_PORT);
+            from.sin_addr.s_addr = INADDR_BROADCAST;
+
+            if(send_packet_enum_query(sock, application, &from) == -1)
+                break;
+
+            starttime = GetTickCount();
+            retrycnt--;
+        }
+
+        /* Query Enumeration Packets */
+        received = recvfrom(sock, buffer, buflen, 0, (struct sockaddr *)&from, &fromlen);
+        if(received >= (int)sizeof(struct ENUM_HEADER))
+        {
+            struct ENUM_HEADER *header = (struct ENUM_HEADER *)buffer;
+
+            if(header->lead == 0)
+            {
+                /* Enum Query */
+                switch(header->command)
+                {
+                    case PACKET_ENUM_QUERY:
+                    {
+                        TRACE("Ignoring EnumQuery Packet.\n");
+                        break;
+                    }
+                    /* Enum response */
+                    case PACKET_ENUM_RESPONSE:
+                    {
+                        struct ENUM_QUERY_RESPONSE *data = (struct ENUM_QUERY_RESPONSE *)header;
+
+                        TRACE("PACKET_ENUM_RESPONSE.\n");
+
+                        if(received <= sizeof(struct ENUM_QUERY_RESPONSE))
+                        {
+                            ERR("Not enough data.\n");
+                            break;
+                        }
+
+                        if(IsEqualGUID(&application, &data->application) ||
+                           IsEqualGUID(&application, &IID_NULL))             /* all applications */
+                        {
+                            DPNMSG_ENUM_HOSTS_RESPONSE response;
+                            DPN_APPLICATION_DESC desc;
+                            IDirectPlay8Address *sender = NULL;
+
+                            memset(&response, 0, sizeof(DPNMSG_ENUM_HOSTS_RESPONSE));
+                            memset(&desc, 0, sizeof(DPN_APPLICATION_DESC));
+
+                            response.dwSize = sizeof(DPNMSG_ENUM_HOSTS_RESPONSE);
+
+                            /*
+                             *  We need to create an Address Object with the Port and IP address.
+                             *  Applications are required to AddRef this object if they want to keep a reference.
+                             */
+                            sender = create_sender_address(&from);
+                            if(!sender)
+                                return;
+
+                            response.pAddressSender          = sender;
+                            response.pApplicationDescription = &desc;
+                            response.pAddressDevice          = device;
+
+                            desc.guidInstance     = data->instance;
+                            desc.guidApplication  = data->application;
+                            desc.dwCurrentPlayers = data->current_players;
+                            desc.dwMaxPlayers     = data->max_players;
+
+                            if(data->session_offset && data->session_offset < received)
+                                desc.pwszSessionName  = (WCHAR*)(((char*)data)+data->session_offset+sizeof(struct ENUM_HEADER));
+
+                            msghandler(NULL, DPN_MSGID_ENUM_HOSTS_RESPONSE, &response);
+
+                            IDirectPlay8Address_Release(sender);
+
+                            /* We have found a server, so exit now. */
+                            retrycnt = 0;
+                        }
+                        break;
+                    }
+                    default:
+                    {
+                        FIXME("Unsupported query command 0x%08x.\n", header->command);
+                    }
+                }
+            }
+            else
+            {
+                struct DFRAME_PACKET *dframe = (struct DFRAME_PACKET *)buffer;
+                FIXME("DFRAME received - command 0x%08x, control 0x%08x.\n", dframe->command, dframe->control);
+            }
+        }
+
+        if(retrycnt)
+            break;
+    }
+}
+
 static HRESULT WINAPI IDirectPlay8ClientImpl_EnumHosts(IDirectPlay8Client *iface,
         PDPN_APPLICATION_DESC const pApplicationDesc, IDirectPlay8Address * const pAddrHost,
         IDirectPlay8Address * const pDeviceInfo, void * const pUserEnumData,
@@ -138,9 +353,12 @@ static HRESULT WINAPI IDirectPlay8ClientImpl_EnumHosts(IDirectPlay8Client *iface
         const DWORD dwFlags)
 {
     IDirectPlay8ClientImpl *This = impl_from_IDirectPlay8Client(iface);
+    IDirectPlay8Address *device = NULL;
+    HRESULT hr;
 
-    FIXME("(%p):(%p,%p,%p,%p,%u,%u,%u,%u,%p,%p,%x)\n", This, pApplicationDesc, pAddrHost, pDeviceInfo, pUserEnumData,
-        dwUserEnumDataSize, dwEnumCount, dwRetryInterval, dwTimeOut, pvUserContext, pAsyncHandle, dwFlags);
+    FIXME("(%p):(%p,%p,%p,%p,%u,%u,%u,%u,%p,%p,%x) Semi-stub\n", This, pApplicationDesc, pAddrHost, pDeviceInfo,
+        pUserEnumData, dwUserEnumDataSize, dwEnumCount, dwRetryInterval, dwTimeOut, pvUserContext, pAsyncHandle,
+        dwFlags);
 
     if(!This->msghandler)
         return DPNERR_UNINITIALIZED;
@@ -151,6 +369,38 @@ static HRESULT WINAPI IDirectPlay8ClientImpl_EnumHosts(IDirectPlay8Client *iface
     if(dwUserEnumDataSize > This->spcaps.dwMaxEnumPayloadSize)
         return DPNERR_ENUMQUERYTOOLARGE;
 
+    if(This->sock == INVALID_SOCKET)
+    {
+        This->sock = find_free_socket();
+        if(This->sock == INVALID_SOCKET)
+            return DPNERR_USERCANCEL; /* Pretend the user cancalled this operation. */
+    }
+
+    hr = IDirectPlay8Address_Duplicate(pDeviceInfo, &device);
+    if(FAILED(hr))
+    {
+        ERR("Failed to duplicate Device address (0x%08x).\n", hr);
+
+        closesocket(This->sock);
+        This->sock = INVALID_SOCKET;
+
+        return E_OUTOFMEMORY;
+    }
+
+    if(dwFlags & DPNENUMHOSTS_SYNC)
+    {
+        DWORD retrycnt  = !dwEnumCount ? This->spcaps.dwDefaultEnumCount : dwEnumCount;
+        DWORD retrytime = dwRetryInterval == INFINITE ? This->spcaps.dwDefaultEnumRetryInterval : dwRetryInterval;
+        DWORD timeout   = !dwTimeOut ? This->spcaps.dwDefaultEnumTimeout : dwTimeOut;
+
+        process_sync_enumhost(This->sock, device, This->msghandler, pApplicationDesc->guidApplication,
+                        retrycnt, retrytime, timeout);
+
+        IDirectPlay8Address_Release(device);
+    }
+    else
+        FIXME("Async EnumHost currently not supported.\n");
+
     return (dwFlags & DPNENUMHOSTS_SYNC) ? DPN_OK : DPNSUCCESS_PENDING;
 }
 
@@ -388,6 +638,7 @@ HRESULT DPNET_CreateDirectPlay8Client(IClassFactory *iface, IUnknown *pUnkOuter,
 
     client->IDirectPlay8Client_iface.lpVtbl = &DirectPlay8Client_Vtbl;
     client->ref = 1;
+    client->sock = INVALID_SOCKET;
 
     init_dpn_sp_caps(&client->spcaps);
 
diff --git a/dlls/dpnet/dpnet_private.h b/dlls/dpnet/dpnet_private.h
index 89e0777..dee1654 100644
--- a/dlls/dpnet/dpnet_private.h
+++ b/dlls/dpnet/dpnet_private.h
@@ -27,10 +27,12 @@
 
 #include <wine/list.h>
 #include "winsock2.h"
+#include "ws2tcpip.h"
 #include "wine/unicode.h"
 
 #include "dplay8.h"
 #include "dplobby8.h"
+#include "dppacket.h"
 /*
  *#include "dplay8sp.h"
  */
@@ -62,6 +64,8 @@ struct IDirectPlay8ClientImpl
     DWORD datasize;
 
     DPN_SP_CAPS spcaps;
+
+    SOCKET sock;
 };
 
 /* ------------------- */
diff --git a/dlls/dpnet/dppacket.h b/dlls/dpnet/dppacket.h
new file mode 100644
index 0000000..f3ca955
--- /dev/null
+++ b/dlls/dpnet/dppacket.h
@@ -0,0 +1,80 @@
+/*
+ * dpnet8 packet structures
+ *
+ * Copyright 2016 Alistair Leslie-Hughes
+ *
+ * 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
+ */
+#ifndef __WINE_DPNET_PACKET_H
+#define __WINE_DPNET_PACKET_H
+
+#include <pshpack1.h>
+
+struct ENUM_HEADER
+{
+    BYTE lead;
+    BYTE command;
+    WORD payload;
+};
+
+struct ENUM_QUERY
+{
+    BYTE lead;
+    BYTE command;
+    WORD payload;
+    BYTE type;
+    GUID application;
+};
+
+struct ENUM_QUERY_RESPONSE
+{
+    BYTE   lead;
+    BYTE   command;
+    WORD   payload;
+    DWORD  reply_offset;
+    DWORD  response_size;
+    DWORD  desc_size;
+    DWORD  desc_flags;
+    DWORD  max_players;
+    DWORD  current_players;
+    DWORD  session_offset;
+    DWORD  session_size;
+    DWORD  password_offset;
+    DWORD  password_size;
+    DWORD  reserved_offset;
+    DWORD  reserved_size;
+    DWORD  application_offset;
+    DWORD  application_size;
+    GUID   instance;
+    GUID   application;
+};
+
+struct DFRAME_PACKET
+{
+    BYTE command;
+    BYTE control;
+    BYTE seq;
+    BYTE nseq;
+};
+
+#define PACKET_ENUM_QUERY                     0x02
+#define PACKET_ENUM_RESPONSE                  0x03
+
+#define PACKET_QUERY_GUID                     0x01
+#define PACKET_QUERY_NO_GUID                  0x02
+
+#include "poppack.h"
+
+#endif
diff --git a/dlls/dpnet/tests/client.c b/dlls/dpnet/tests/client.c
index aff99d8..85d3768 100644
--- a/dlls/dpnet/tests/client.c
+++ b/dlls/dpnet/tests/client.c
@@ -250,6 +250,9 @@ static void test_enum_hosts(void)
     ok(hr == S_OK, "IDirectPlay8Client_CancelAsyncOperation failed with 0x%08x\n", hr);
     todo_wine { CHECK_LAST_ASYNC_OP(DPNERR_USERCANCEL, async2); }
 
+    hr = IDirectPlay8Client_EnumHosts(client, &appdesc, host, local, NULL, 0, 2, 500, 500, NULL,  NULL, DPNENUMHOSTS_SYNC);
+    ok(hr == S_OK, "IDirectPlay8Client_EnumHosts failed with 0x%08x\n", hr);
+
     IDirectPlay8Address_Release(local);
     IDirectPlay8Address_Release(host);
     IDirectPlay8Client_Release(client2);
-- 
2.10.2




More information about the wine-patches mailing list