swift
directplaypeer.cpp
1 // SPDX-FileCopyrightText: Copyright (C) 2014 swift Project Community / Contributors
2 // SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-swift-pilot-client-1
3 
4 #include "directplaypeer.h"
5 
6 #include <QDebug>
7 #include <QFile>
8 #include <QScopedPointer>
9 #include <QStringList>
10 #include <QTimer>
11 
12 #include "directplayerror.h"
13 #include "directplayutils.h"
15 
16 #include "misc/logmessage.h"
17 
18 using namespace swift::misc;
19 using namespace swift::misc::aviation;
20 
21 namespace swift::simplugin::fs9
22 {
23  const QStringList &CDirectPlayPeer::getLogCategories()
24  {
25  static const QStringList cats { CLogCategories::driver() };
26  return cats;
27  }
28 
29  CDirectPlayPeer::CDirectPlayPeer(const CCallsign &callsign, QObject *parent)
30  : QObject(parent), m_callsign(callsign), m_callbackWrapper(this, &CDirectPlayPeer::directPlayMessageHandler)
31  {}
32 
34  {
35  if (m_directPlayPeer)
36  {
37  m_directPlayPeer->Close(DPNCLOSE_IMMEDIATE);
38  m_directPlayPeer->Release();
39  }
40 
42  if (m_coInitializeSucceeded) { CoUninitialize(); }
43  }
44 
45  HRESULT CDirectPlayPeer::directPlayMessageHandler(DWORD messageId, void *msgBuffer)
46  {
47  switch (messageId)
48  {
49  case DPN_MSGID_CREATE_PLAYER:
50  {
51  DPNMSG_CREATE_PLAYER *pCreatePlayerMsg = static_cast<DPNMSG_CREATE_PLAYER *>(msgBuffer);
52 
53  // Get the peer info and extract its name
54  DWORD dwSize = 0;
55  DPN_PLAYER_INFO *pdpPlayerInfo = nullptr;
56  HRESULT hr = DPNERR_CONNECTING;
57 
58  // GetPeerInfo might return DPNERR_CONNECTING when connecting,
59  // so just keep calling it if it does
60  while (hr == DPNERR_CONNECTING)
61  {
62  hr = m_directPlayPeer->GetPeerInfo(pCreatePlayerMsg->dpnidPlayer, pdpPlayerInfo, &dwSize, 0);
63  }
64 
65  if (hr == DPNERR_BUFFERTOOSMALL)
66  {
67  QScopedArrayPointer<unsigned char> memPtr(new unsigned char[dwSize]);
68  pdpPlayerInfo = reinterpret_cast<DPN_PLAYER_INFO *>(memPtr.data());
69  if (pdpPlayerInfo == nullptr) { break; }
70 
71  ZeroMemory(pdpPlayerInfo, dwSize);
72  pdpPlayerInfo->dwSize = sizeof(DPN_PLAYER_INFO);
73 
74  hr = m_directPlayPeer->GetPeerInfo(pCreatePlayerMsg->dpnidPlayer, pdpPlayerInfo, &dwSize, 0);
75  if (SUCCEEDED(hr))
76  {
77  if (pdpPlayerInfo->dwPlayerFlags & DPNPLAYER_LOCAL)
78  {
79  m_playerLocal = pCreatePlayerMsg->dpnidPlayer;
80  }
81  else
82  {
83  // The first connecting player should be the user
84  if (m_playerUser == 0) { m_playerUser = pCreatePlayerMsg->dpnidPlayer; }
85  }
86  }
87  }
88  break;
89  }
90 
91  case DPN_MSGID_RECEIVE:
92  {
93  PDPNMSG_RECEIVE pReceiveMsg = static_cast<PDPNMSG_RECEIVE>(msgBuffer);
94 
95  // Proceeed only, if the sender is our local player
96  if (pReceiveMsg->dpnidSender == m_playerUser)
97  {
98  const QByteArray messageData =
99  QByteArray((char *)pReceiveMsg->pReceiveData, pReceiveMsg->dwReceiveDataSize);
100  emit customPacketReceived(messageData);
101  }
102  break;
103  }
104 
105  case DPN_MSGID_ENUM_HOSTS_RESPONSE:
106  {
107  PDPNMSG_ENUM_HOSTS_RESPONSE enumHostsResponseMsg = static_cast<PDPNMSG_ENUM_HOSTS_RESPONSE>(msgBuffer);
108  const DPN_APPLICATION_DESC *applicationDescription = enumHostsResponseMsg->pApplicationDescription;
109 
110  auto iterator = std::find_if(m_hostNodeList.begin(), m_hostNodeList.end(), [&](const CHostNode &hostNode) {
111  return applicationDescription->guidInstance == hostNode.getApplicationDesc().guidInstance;
112  });
113 
114  if (iterator == m_hostNodeList.end())
115  {
116 
117  // This host session is not in the list then so insert it.
118  CHostNode hostNode;
119  HRESULT hr;
120 
121  // Copy the Host Address
122  if (FAILED(hr = enumHostsResponseMsg->pAddressSender->Duplicate(hostNode.getHostAddressPtr())))
123  {
124  CLogMessage(this).warning(u"Failed to duplicate host address!");
125  return hr;
126  }
127 
128  DPN_APPLICATION_DESC appDesc;
129 
130  ZeroMemory(&appDesc, sizeof(DPN_APPLICATION_DESC));
131  memcpy(&appDesc, applicationDescription, sizeof(DPN_APPLICATION_DESC));
132 
133  // Null out all the pointers we aren't copying
134  appDesc.pwszSessionName = nullptr;
135  appDesc.pwszPassword = nullptr;
136  appDesc.pvReservedData = nullptr;
137  appDesc.dwReservedDataSize = 0;
138  appDesc.pvApplicationReservedData = nullptr;
139  appDesc.dwApplicationReservedDataSize = 0;
140  // hostNode.setApplicationDesc(appDesc);
141  hostNode.setApplicationDesc(appDesc);
142  hostNode.setSessionName(QString::fromWCharArray(applicationDescription->pwszSessionName));
143  m_hostNodeList.append(hostNode);
144  }
145  break;
146  }
147  case DPN_MSGID_CONNECT_COMPLETE:
148  {
149  const PDPNMSG_CONNECT_COMPLETE connectCompleteMsg = static_cast<PDPNMSG_CONNECT_COMPLETE>(msgBuffer);
150  if (connectCompleteMsg->hResultCode == DPN_OK) { emit connectionComplete(); }
151  else
152  {
153  CLogMessage(this).warning(u"DirectPlay connection returned: %1") << connectCompleteMsg->hResultCode;
154  }
155  }
156  }
157 
158  // Directx9 SDK: Unless otherwise noted, this function should return S_OK.
159  // http://doc.51windows.net/Directx9_SDK/play/ref/callbacks/pfndpnmessagehandler.htm
160  return DPN_OK;
161  }
162 
164  {
165  // Initialize COM.
166  // https://docs.microsoft.com/en-us/windows/desktop/api/combaseapi/nf-combaseapi-coinitializeex
167  HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
168 
169  // RPC_E_CHANGED_MODE: CoInitializeEx was already called by someone else in this thread with a different mode.
170  if (hr == RPC_E_CHANGED_MODE)
171  {
172  CLogMessage(this).debug(u"CoInitializeEx was already called with a different mode. Trying again.");
173  hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
174  }
175 
176  // Continue here only if CoInitializeEx was successful
177  // S_OK: The COM library was initialized successfully on this thread.
178  // S_FALSE: The COM library is already initialized on this thread. Reference count was incremented. This is not
179  // an error.
180  if (hr == S_OK || hr == S_FALSE) { m_coInitializeSucceeded = true; }
181  else
182  {
183  CLogMessage(this).warning(u"CoInitializeEx returned error code %1");
184  return E_FAIL;
185  }
186 
187  // Create the IDirectPlay8Peer Object
188  if (FAILED(hr = CoCreateInstance(CLSID_DirectPlay8Peer, nullptr, CLSCTX_INPROC_SERVER, IID_IDirectPlay8Peer,
189  reinterpret_cast<void **>(&m_directPlayPeer))))
190  {
191  CLogMessage(this).warning(u"Failed to create DirectPlay8Peer object!");
192  return hr;
193  }
194 
195  // Init DirectPlay
196  if (FAILED(hr = m_directPlayPeer->Initialize(&m_callbackWrapper, m_callbackWrapper.messageHandler, 0)))
197  {
198  CLogMessage(this).warning(u"Failed to initialize directplay peer!");
199  return hr;
200  }
201 
202  // Ensure that TCP/IP is a valid Service Provider
203  if (!isServiceProviderValid(&CLSID_DP8SP_TCPIP))
204  {
205  hr = E_FAIL;
206  CLogMessage(this).warning(u"Service provider is invalid!");
207  return hr;
208  }
209 
210  return hr;
211  }
212 
213  bool CDirectPlayPeer::isServiceProviderValid(const GUID * /*pGuidSP*/)
214  {
215  DWORD dwItems = 0;
216  DWORD dwSize = 0;
217 
218  // The first call is to retrieve the size of the DPN_SERVICE_PROVIDER_INFO array
219  HRESULT hr = m_directPlayPeer->EnumServiceProviders(&CLSID_DP8SP_TCPIP, nullptr, nullptr, &dwSize, &dwItems, 0);
220 
221  if (hr != DPNERR_BUFFERTOOSMALL)
222  {
223  CLogMessage(this).warning(u"Failed to enumerate service providers!");
224  return false;
225  }
226 
227  // Allocating an array with new DPN_SERVICE_PROVIDER_INFO[items] does not work, because the struct has
228  // several pointers in it. Hence EnumServiceProviders tells us how much memory it exactly needs.
229  QScopedArrayPointer<unsigned char> memPtr(new unsigned char[dwSize]);
230  DPN_SERVICE_PROVIDER_INFO *dpnSPInfo = reinterpret_cast<DPN_SERVICE_PROVIDER_INFO *>(memPtr.data());
231 
232  if (FAILED(hr = m_directPlayPeer->EnumServiceProviders(&CLSID_DP8SP_TCPIP, nullptr, dpnSPInfo, &dwSize,
233  &dwItems, 0)))
234  {
235  CLogMessage(this).warning(u"Failed to enumerate service providers!");
236  return false;
237  }
238 
239  // There are no items returned so the requested SP is not available
240  if (dwItems == 0) { hr = E_FAIL; }
241 
242  if (SUCCEEDED(hr)) { return true; }
243  else { return false; }
244  }
245 
247  {
248  HRESULT hr = S_OK;
249 
250  // Create our IDirectPlay8Address Device Address
251  if (FAILED(hr = CoCreateInstance(CLSID_DirectPlay8Address, nullptr, CLSCTX_INPROC_SERVER,
252  IID_IDirectPlay8Address, reinterpret_cast<void **>(&m_deviceAddress))))
253  {
254  CLogMessage(this).warning(u"Failed to create DirectPlay8Address instance!");
255  return hr;
256  }
257 
258  // Set the SP for our Device Address
259  if (FAILED(hr = m_deviceAddress->SetSP(&CLSID_DP8SP_TCPIP)))
260  {
261  CLogMessage(this).warning(u"Failed to set SP!");
262  return hr;
263  }
264 
265  return hr;
266  }
267 
269  {
270  HRESULT hr = S_OK;
271 
272  // Create our IDirectPlay8Address Device Address
273  if (FAILED(hr = CoCreateInstance(CLSID_DirectPlay8Address, nullptr, CLSCTX_INPROC_SERVER,
274  IID_IDirectPlay8Address, reinterpret_cast<void **>(&m_deviceAddress))))
275  return logDirectPlayError(hr);
276 
277  // Set the SP for our Device Address
278  if (FAILED(hr = m_deviceAddress->SetSP(&CLSID_DP8SP_TCPIP))) { return logDirectPlayError(hr); }
279 
280  return S_OK;
281  }
282 
283  HRESULT CDirectPlayPeer::sendMessage(const QByteArray &message)
284  {
285  HRESULT hr = S_OK;
286  DPN_BUFFER_DESC dpBufferDesc;
287 
288  if ((dpBufferDesc.dwBufferSize = message.size()) == 0) { return S_FALSE; }
289 
291  dpBufferDesc.pBufferData = (BYTE *)message.data();
292 
293  DPNHANDLE asyncHandle;
294  // If m_playerUser is non zero, send it only to him
295  if (FAILED(hr = m_directPlayPeer->SendTo(m_playerUser, &dpBufferDesc, 1, 0, nullptr, &asyncHandle,
296  DPNSEND_GUARANTEED)))
297  {
298  const QString m(message);
299  CLogMessage(this).warning(u"DirectPlay: Failed to send message! Return value: %1 ") << hr;
300  CLogMessage(this).debug() << m;
301  }
302  return hr;
303  }
304 
306 } // namespace swift::simplugin::fs9
static const QString & driver()
Driver.
Class for emitting a log message.
Definition: logmessage.h:27
Derived & warning(const char16_t(&format)[N])
Set the severity to warning, providing a format string.
Derived & debug()
Set the severity to debug.
Value object encapsulating information of a callsign.
Definition: callsign.h:30
DirectPlay peer implementation More information can be found in the DirectX9 SDK documentation http:/...
HRESULT directPlayMessageHandler(DWORD messageId, void *msgBuffer)
DirectPlay message handler.
std::atomic< DPNID > m_playerLocal
Local player Id.
bool isServiceProviderValid(const GUID *pGuidSP)
Returns true of the service provider is a valid on this machine.
HRESULT createHostAddress()
Creates a new DirectPlay device address.
QList< CHostNode > m_hostNodeList
List of enumerated hosts.
std::atomic< DPNID > m_playerUser
User player Id.
IDirectPlay8Peer * m_directPlayPeer
DirectPlay peer address.
HRESULT initDirectPlay()
Initialize DirectPlay.
IDirectPlay8Address * m_deviceAddress
DirectPlay device address.
void connectionComplete()
Async operatione complete.
HRESULT sendMessage(const QByteArray &data)
Send a custom DirectPlay message.
TCallbackWrapper m_callbackWrapper
Callback wrapper.
void customPacketReceived(const QByteArray &data)
Received custom FS9 packet.
HRESULT createDeviceAddress()
Creates a new DirectPlay device address.
void SafeRelease(T *&pT)
Safely release a COM allocated object.
Free functions in swift::misc.
unsigned long DWORD
Fake Windows DWORD.
Class representing a enumerated host node.
Definition: hostnode.h:17
IDirectPlay8Address ** getHostAddressPtr()
Returns a pointer to the hosts address.
Definition: hostnode.h:34
void setApplicationDesc(const DPN_APPLICATION_DESC &appDesc)
Set the hosts application description.
Definition: hostnode.h:31
void setSessionName(const QString &name)
Set the session name.
Definition: hostnode.h:46
static ReturnType WINAPI messageHandler(void *userContext, Argument1 arg1, Argument2 arg2)
FS9 message handler callback.