[PATCH] dpnet: Implement IDirectPlay8Client Enumhost when called synchronously

Alistair Leslie-Hughes leslie_alistair at hotmail.com
Tue Mar 7 23:42:53 CST 2017


This is work in progress.

Currently the only game I know of that uses this functionally is
S.T.A.L.K.E.R.: Shadow of Chernobyl.  However, the game queries for a
specific host/port which hasnt been implemented.
(I'm trying to minimumize the size of this patch.)

Yes, INET6 must be support.

Things that are missing, I think could be seperate patches.
- Support Enum query on a specific host/port
- Sending the user data in the query call (required for Civ 3 to work).
- Always sending a GUID in the query packet (should be optional)

Should any of the above items be merged into this patch?
Could this patch be split?

Any feedback would be great.

Alistair.

---
 dlls/dpnet/client.c        | 289 ++++++++++++++++++++++++++++++++++++++++++++-
 dlls/dpnet/dpnet_private.h |   4 +
 dlls/dpnet/dppacket.h      |  80 +++++++++++++
 3 files changed, 371 insertions(+), 2 deletions(-)
 create mode 100644 dlls/dpnet/dppacket.h

diff --git a/dlls/dpnet/client.c b/dlls/dpnet/client.c
index 3b3361fe72..39fa725759 100644
--- a/dlls/dpnet/client.c
+++ b/dlls/dpnet/client.c
@@ -55,6 +55,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)
@@ -91,6 +137,8 @@ static ULONG WINAPI IDirectPlay8ClientImpl_Release(IDirectPlay8Client *iface)
 
     if (!ref)
     {
+        closesocket(This->sock);
+
         heap_free(This->username);
         heap_free(This->data);
         heap_free(This);
@@ -137,6 +185,201 @@ static HRESULT WINAPI IDirectPlay8ClientImpl_EnumServiceProviders(IDirectPlay8Cl
     return enum_services_providers(pguidServiceProvider, pSPInfoBuffer, pcbEnumData, pcReturned);
 }
 
+static IDirectPlay8Address *create_sender_address(SOCKADDR_STORAGE *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;
+
+    if(addr->ss_family == AF_INET6)
+    {
+        inet_ntop(addr->ss_family, &(((struct sockaddr_in6 *)addr)->sin6_addr), ip, sizeof(ip));
+        port = ntohs( ((struct sockaddr_in6 *)addr)->sin6_port);
+    }
+    else
+    {
+        inet_ntop(addr->ss_family, &(((struct sockaddr_in *)addr)->sin_addr), ip, sizeof(ip));
+        port = ntohs( ((struct sockaddr_in *)addr)->sin_port);
+    }
+
+    TRACE("Server IP address %s port (%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, void *addr, size_t addrsize)
+{
+    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 *)addr, addrsize);
+    if(err == -1)
+        ERR("Sendto failed (%d).\n", WSAGetLastError());
+
+    return err;
+}
+
+static HRESULT enumhost_data(SOCKET sock, IDirectPlay8Address *device, PFNDPNMESSAGEHANDLER msghandler,
+                             GUID application, char *buffer, size_t datasize, void *addr, DWORD *retrycnt)
+{
+    struct ENUM_HEADER *header = (struct ENUM_HEADER *)buffer;
+    HRESULT hr = E_FAIL;
+
+    /* Enum Query */
+    switch(header->command)
+    {
+        case PACKET_ENUM_QUERY:
+        {
+            WARN("Recieved EnumQuery Packet.\n");
+            break;
+        }
+        /* Enum response */
+        case PACKET_ENUM_RESPONSE:
+        {
+            struct ENUM_QUERY_RESPONSE *data = (struct ENUM_QUERY_RESPONSE *)buffer;
+
+            TRACE("PACKET_ENUM_RESPONSE.\n");
+
+            if(datasize <= 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( (SOCKADDR_STORAGE *)&addr);
+                if(!sender)
+                    return E_FAIL;
+
+                response.pAddressSender          = sender;
+                response.pApplicationDescription = &desc;
+                response.pAddressDevice          = device;
+
+                if(data->reply_offset < datasize)
+                {
+                    response.pvResponseData = ((char*)data)+data->reply_offset+sizeof(struct ENUM_HEADER);
+                    response.dwResponseDataSize = data->response_size;
+                }
+
+                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 < datasize)
+                    desc.pwszSessionName  = (WCHAR*)(((char*)data)+data->session_offset+sizeof(struct ENUM_HEADER));
+
+                TRACE("Calling message handler\n");
+                hr = msghandler(NULL, DPN_MSGID_ENUM_HOSTS_RESPONSE, &response);
+                TRACE("Message handler return 0x%08x\n", hr);
+
+                IDirectPlay8Address_Release(sender);
+
+                /* We have found a server, so exit now. */
+                *retrycnt = 0;
+            }
+            break;
+        }
+        default:
+        {
+            FIXME("Unsupported query command 0x%08x.\n", header->command);
+        }
+    }
+
+    return hr;
+}
+
+static void directplay_data(SOCKET sock, void *host, size_t hostsize, 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 addr;
+    int addrlen = sizeof(addr);
+    int received = 0;
+    HRESULT hr = DPN_OK;
+
+    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout, sizeof(timeout));
+
+    while(TRUE)
+    {
+        if(retrycnt && (GetTickCount() - starttime) >= retrytime)
+        {
+            TRACE("Sending Request.\n");
+
+            if(send_packet_enum_query(sock, application, host, hostsize) == -1)
+                break;
+
+            starttime = GetTickCount();
+            retrycnt--;
+        }
+
+        /* Query Enumeration Packets */
+        received = recvfrom(sock, buffer, buflen, 0, (struct sockaddr *)&addr, &addrlen);
+        if(received >= (int)sizeof(struct ENUM_HEADER))
+        {
+            struct ENUM_HEADER *header = (struct ENUM_HEADER *)buffer;
+
+            if(header->lead == 0)
+            {
+                hr = enumhost_data(sock, device, msghandler, application, buffer, received, &addr, &retrycnt);
+                if(!retrycnt)
+                    break;
+            }
+            else
+            {
+                struct DFRAME_PACKET *dframe = (struct DFRAME_PACKET *)buffer;
+                FIXME("DFRAME received - command 0x%08x, control 0x%08x.\n", dframe->command, dframe->control);
+            }
+        }
+
+        /* If a userhandler function returns anything other than DPN_OK we need to stop. */
+        if(hr != DPN_OK)
+            break;
+    }
+}
+
 static HRESULT WINAPI IDirectPlay8ClientImpl_EnumHosts(IDirectPlay8Client *iface,
         PDPN_APPLICATION_DESC const pApplicationDesc, IDirectPlay8Address * const pAddrHost,
         IDirectPlay8Address * const pDeviceInfo, void * const pUserEnumData,
@@ -145,9 +388,13 @@ static HRESULT WINAPI IDirectPlay8ClientImpl_EnumHosts(IDirectPlay8Client *iface
         const DWORD dwFlags)
 {
     IDirectPlay8ClientImpl *This = impl_from_IDirectPlay8Client(iface);
+    IDirectPlay8Address *device = NULL;
+    HRESULT hr;
+    struct sockaddr_in host;
 
-    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;
@@ -158,6 +405,43 @@ 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 ? This->spcaps.dwDefaultEnumRetryInterval : dwRetryInterval;
+        DWORD timeout   = dwTimeOut == INFINITE ? This->spcaps.dwDefaultEnumTimeout : dwTimeOut;
+
+        memset(&host, 0 ,sizeof(host));
+        host.sin_family = AF_INET;
+        host.sin_port = htons(DPNA_DPNSVR_PORT);
+        host.sin_addr.s_addr = INADDR_BROADCAST;
+
+        directplay_data(This->sock, &host, sizeof(host), 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;
 }
 
@@ -395,6 +679,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 7b6de4a861..1712bba6c6 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 0000000000..f3ca955f51
--- /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
-- 
2.11.0




More information about the wine-patches mailing list