From: Alistair Leslie-Hughes Subject: [PATCH] dpnet: Implement IDirectPlay8Client Enumhost when called synchronously Message-Id: Date: Wed, 8 Mar 2017 05:42:53 +0000 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 #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 + +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