[v3 PATCH 3/4] dpnet: Implement IDirectPlay8Client Enumhost when called synchronously
Alistair Leslie-Hughes
leslie_alistair at hotmail.com
Thu Nov 17 00:41:31 CST 2016
v2
Split tests into seperate patch
Removed extra structures/defines from dppacket.h
Rewrite to only deal with synchronously for now
v3
Correct Timeout being passed to process_enumhost
Renamed process_sync_enumhost to process_enumhost
Check return on User Message handler.
Signed-off-by: Alistair Leslie-Hughes <leslie_alistair at hotmail.com>
---
dlls/dpnet/client.c | 257 ++++++++++++++++++++++++++++++++++++++++++++-
dlls/dpnet/dpnet_private.h | 4 +
dlls/dpnet/dppacket.h | 80 ++++++++++++++
3 files changed, 339 insertions(+), 2 deletions(-)
create mode 100644 dlls/dpnet/dppacket.h
diff --git a/dlls/dpnet/client.c b/dlls/dpnet/client.c
index 904161e..a8d46b1 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,175 @@ 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_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;
+ 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");
+
+ 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));
+
+ hr = 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 a userhandler function returns anything other than DPN_OK we need to stop. */
+ if(!retrycnt || 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,
@@ -138,9 +355,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 +371,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 == INFINITE ? This->spcaps.dwDefaultEnumTimeout : dwTimeOut;
+
+ process_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 +640,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
--
1.9.1
More information about the wine-patches
mailing list