[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