swift
lobbyclient.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 #define _CRT_SECURE_NO_WARNINGS
5 
6 #include "lobbyclient.h"
7 
8 #include <QDebug>
9 #include <QFile>
10 #include <QMutexLocker>
11 #include <QScopedPointer>
12 #include <QStringList>
13 #include <QTimer>
14 
15 #include "directplayerror.h"
16 #include "directplayutils.h"
17 #include "fs9.h"
18 
19 #include "core/actionbind.h"
20 #include "core/application.h"
21 #include "misc/logmessage.h"
22 
23 using namespace swift::misc;
24 
25 namespace swift::simplugin::fs9
26 {
27  CLobbyClient::CLobbyClient(QObject *parent)
28  : QObject(parent), m_callbackWrapper(this, &CLobbyClient::directPlayMessageHandler),
29  m_lobbyCallbackWrapper(this, &CLobbyClient::directPlayLobbyMessageHandler)
30  {
31  Q_ASSERT_X(sApp, Q_FUNC_INFO, "Missing global object");
33  }
34 
36  {
37  if (m_directPlayPeer)
38  {
39  m_directPlayPeer->Close(DPNCLOSE_IMMEDIATE);
40  m_directPlayPeer->Release();
41  }
42 
43  SafeRelease(m_deviceAddress);
44  SafeRelease(m_hostAddress);
45 
46  if (m_dpLobbyClient)
47  {
48  m_dpLobbyClient->ReleaseApplication(DPLHANDLE_ALLCONNECTIONS, 0);
49  m_dpLobbyClient->Close(0);
50  }
51 
52  CoUninitialize();
53  }
54 
56  {
57  HRESULT hr;
58 
59  // Create and init IDirectPlay8Peer
60  if (FAILED(hr = CoCreateInstance(CLSID_DirectPlay8Peer, nullptr, CLSCTX_INPROC_SERVER, IID_IDirectPlay8Peer,
61  (LPVOID *)&m_directPlayPeer)))
62  return logDirectPlayError(hr);
63 
64  // Turn off parameter validation in release builds
65  const DWORD dwInitFlags = 0;
66  // const DWORD dwInitFlags = DPNINITIALIZE_DISABLEPARAMVAL;
67 
68  if (FAILED(hr =
69  m_directPlayPeer->Initialize(&m_callbackWrapper, m_callbackWrapper.messageHandler, dwInitFlags)))
70  return logDirectPlayError(hr);
71 
72  // Create and init IDirectPlay8LobbyClient
73  if (FAILED(hr = CoCreateInstance(CLSID_DirectPlay8LobbyClient, nullptr, CLSCTX_INPROC_SERVER,
74  IID_IDirectPlay8LobbyClient, (LPVOID *)&m_dpLobbyClient)))
75  return logDirectPlayError(hr);
76 
77  if (FAILED(hr = m_dpLobbyClient->Initialize(&m_lobbyCallbackWrapper, m_lobbyCallbackWrapper.messageHandler,
78  dwInitFlags)))
79  return logDirectPlayError(hr);
80 
81  return S_OK;
82  }
83 
85  {
86  if (!m_dpLobbyClient) { return false; }
87  GUID appGuid = CFs9Sdk::guid();
88  DWORD dwSize = 0;
89  DWORD dwItems = 0;
90  const HRESULT hr = m_dpLobbyClient->EnumLocalPrograms(&appGuid, nullptr, &dwSize, &dwItems, 0);
91  if (hr == DPNERR_BUFFERTOOSMALL)
92  {
93  QScopedArrayPointer<BYTE> memPtr(new BYTE[dwSize]);
94  DPL_APPLICATION_INFO *appInfo = reinterpret_cast<DPL_APPLICATION_INFO *>(memPtr.data());
95 
96  m_dpLobbyClient->EnumLocalPrograms(&appGuid, memPtr.data(), &dwSize, &dwItems, 0);
97 
98  if (dwItems > 0)
99  {
100  CLogMessage(this).debug()
101  << "Found lobby application:" << QString::fromWCharArray(appInfo->pwszApplicationName);
102  return true;
103  }
104  else { return false; }
105  }
106  else { return false; }
107  }
108 
109  HRESULT CLobbyClient::connectFs9ToHost(const QString &address)
110  {
111  HRESULT hr = S_OK;
112 
113  GUID pAppGuid = CFs9Sdk::guid();
114 
115  // Setup the DPL_CONNECT_INFO struct
116  DPL_CONNECT_INFO dnConnectInfo;
117  ZeroMemory(&dnConnectInfo, sizeof(DPL_CONNECT_INFO));
118  dnConnectInfo.dwSize = sizeof(DPL_CONNECT_INFO);
119  dnConnectInfo.pvLobbyConnectData = nullptr;
120  dnConnectInfo.dwLobbyConnectDataSize = 0;
121  dnConnectInfo.dwFlags = 0;
122  dnConnectInfo.guidApplication = pAppGuid;
123 
124  if (FAILED(hr = allocAndInitConnectSettings(address, &pAppGuid, &dnConnectInfo.pdplConnectionSettings)))
125  {
126  return S_FALSE;
127  }
128 
129  hr = m_dpLobbyClient->ConnectApplication(&dnConnectInfo, nullptr, &m_applicationHandle, INFINITE, 0);
130  if (FAILED(hr)) { return hr; }
131 
132  CLogMessage(this).info(u"Lobby client '%1' connected!") << address;
133  freeConnectSettings(dnConnectInfo.pdplConnectionSettings);
134  return S_OK;
135  }
136 
137  HRESULT CLobbyClient::allocAndInitConnectSettings(const QString &address, GUID *pAppGuid,
138  DPL_CONNECTION_SETTINGS **ppdplConnectSettings)
139  {
140  HRESULT hr;
141 
142  IDirectPlay8Address *pHostAddress = nullptr;
143  IDirectPlay8Address *pDeviceAddress = nullptr;
144 
145  QScopedPointer<GUID> pSPGuid(new GUID);
146  memcpy(pSPGuid.data(), &CLSID_DP8SP_TCPIP, sizeof(GUID));
147 
148  // Create a host address
149  if (FAILED(hr = CoCreateInstance(CLSID_DirectPlay8Address, nullptr, CLSCTX_INPROC_SERVER,
150  IID_IDirectPlay8Address, reinterpret_cast<void **>(&pHostAddress))))
151  {
152  return logDirectPlayError(hr);
153  }
154 
155  // Set the SP to pHostAddress
156  if (FAILED(hr = pHostAddress->SetSP(pSPGuid.data()))) { return logDirectPlayError(hr); }
157 
158  // Create a device address to specify which device we are using
159  if (FAILED(hr = CoCreateInstance(CLSID_DirectPlay8Address, NULL, CLSCTX_INPROC_SERVER, IID_IDirectPlay8Address,
160  reinterpret_cast<void **>(&pDeviceAddress))))
161  {
162  return logDirectPlayError(hr);
163  }
164 
165  // Set the SP to pDeviceAddress
166  if (FAILED(hr = pDeviceAddress->SetSP(&CLSID_DP8SP_TCPIP))) { return logDirectPlayError(hr); }
167 
168  if (FAILED(hr = pHostAddress->BuildFromURLA(address.toLocal8Bit().data()))) { return logDirectPlayError(hr); }
169 
170  // Setup the DPL_CONNECTION_SETTINGS
171  DPL_CONNECTION_SETTINGS *pSettings = new DPL_CONNECTION_SETTINGS;
172 
173  // Allocate space for device address pointers
174  // We cannot use QScopedArrayPointer, because memory needs to be valid
175  // for the lifetime of DPL_CONNECTION_SETTINGS.
176  IDirectPlay8Address **apDevAddress = new IDirectPlay8Address *[1];
177 
178  // Set the device addresses
179  apDevAddress[0] = pDeviceAddress;
180 
181  QString session = sApp->swiftVersionString();
182  QScopedArrayPointer<wchar_t> wstrSessionName(new wchar_t[session.size() + 1]);
183  session.toWCharArray(wstrSessionName.data());
184  wstrSessionName[session.size()] = 0;
185 
186  // Fill in the connection settings
187  ZeroMemory(pSettings, sizeof(DPL_CONNECTION_SETTINGS));
188  pSettings->dwSize = sizeof(DPL_CONNECTION_SETTINGS);
189  pSettings->dpnAppDesc.dwSize = sizeof(DPN_APPLICATION_DESC);
190  pSettings->dwFlags = 0;
191  pSettings->dpnAppDesc.guidApplication = *pAppGuid;
192  pSettings->dpnAppDesc.guidInstance = GUID_NULL;
193  pSettings->dpnAppDesc.dwFlags = DPNSESSION_NODPNSVR;
194  pSettings->pdp8HostAddress = pHostAddress;
195  pSettings->ppdp8DeviceAddresses = apDevAddress;
196  pSettings->cNumDeviceAddresses = 1;
197  pSettings->dpnAppDesc.pwszSessionName = new WCHAR[wcslen(wstrSessionName.data()) + 1];
198  wcscpy(pSettings->dpnAppDesc.pwszSessionName, wstrSessionName.data());
199 
200  // FIXME: Use players callsign
201  QString playerName("Player");
202  WCHAR wstrPlayerName[m_maxSizePlayerName];
203  playerName.toWCharArray(wstrPlayerName);
204  wstrPlayerName[playerName.size()] = 0;
205  pSettings->pwszPlayerName = new WCHAR[wcslen(wstrPlayerName) + 1];
206  wcscpy(pSettings->pwszPlayerName, wstrPlayerName);
207 
208  *ppdplConnectSettings = pSettings;
209 
210  return S_OK;
211  }
212 
213  void CLobbyClient::freeConnectSettings(DPL_CONNECTION_SETTINGS *pSettings)
214  {
215  if (!pSettings) return;
216 
217  SafeDeleteArray(pSettings->pwszPlayerName);
218  SafeDeleteArray(pSettings->dpnAppDesc.pwszSessionName);
219  SafeDeleteArray(pSettings->dpnAppDesc.pwszPassword);
220 
221  // The following two lines came from DX9 SDK samples, but they don't make sense.
222  // Deleteing a void* pointer is considered unsafe. If you really had to pass
223  // anything non-void here, make sure the original object is cleaned up properly.
224  // SafeDeleteArray(pSettings->dpnAppDesc.pvReservedData);
225  // SafeDeleteArray(pSettings->dpnAppDesc.pvApplicationReservedData);
226  SafeRelease(pSettings->pdp8HostAddress);
227  SafeRelease(pSettings->ppdp8DeviceAddresses[0]);
228  SafeDeleteArray(pSettings->ppdp8DeviceAddresses);
229  SafeDelete(pSettings);
230  }
231 
232  HRESULT CLobbyClient::directPlayMessageHandler(DWORD /* messageId */, void * /* msgBuffer */) { return S_OK; }
233 
234  HRESULT CLobbyClient::directPlayLobbyMessageHandler(DWORD messageId, void *msgBuffer)
235  {
236  switch (messageId)
237  {
238  case DPL_MSGID_DISCONNECT:
239  {
240  PDPL_MESSAGE_DISCONNECT pDisconnectMsg;
241  pDisconnectMsg = (PDPL_MESSAGE_DISCONNECT)msgBuffer;
242  Q_UNUSED(pDisconnectMsg)
243 
244  emit disconnected();
245 
246  // We should free any data associated with the
247  // app here, but there is none.
248  break;
249  }
250 
251  case DPL_MSGID_RECEIVE:
252  {
253  PDPL_MESSAGE_RECEIVE pReceiveMsg;
254  pReceiveMsg = (PDPL_MESSAGE_RECEIVE)msgBuffer;
255  Q_UNUSED(pReceiveMsg)
256 
257  // The lobby app sent us data. This sample doesn't
258  // expected data from the app, but it is useful
259  // for more complex clients.
260  break;
261  }
262 
263  case DPL_MSGID_SESSION_STATUS:
264  {
265  PDPL_MESSAGE_SESSION_STATUS pStatusMsg;
266  pStatusMsg = (PDPL_MESSAGE_SESSION_STATUS)msgBuffer;
267 
268  QString message;
269  message.append(QString("%1: ").arg(pStatusMsg->hSender, 0, 16));
270  switch (pStatusMsg->dwStatus)
271  {
272  case DPLSESSION_CONNECTED: message.append("Session connected"); break;
273  case DPLSESSION_COULDNOTCONNECT: message.append("Session could not connect"); break;
274  case DPLSESSION_DISCONNECTED: message.append("Session disconnected"); break;
275  case DPLSESSION_TERMINATED: message.append("Session terminated"); break;
276  case DPLSESSION_HOSTMIGRATED: message.append("Host migrated"); break;
277  case DPLSESSION_HOSTMIGRATEDHERE: message.append("Host migrated to this client"); break;
278  default:
279  message.append(QStringLiteral("Unknown PDPL_MESSAGE_SESSION_STATUS: %1").arg(pStatusMsg->dwStatus));
280  break;
281  }
282 
283  CLogMessage(this).info(message);
284  break;
285  }
286 
287  case DPL_MSGID_CONNECTION_SETTINGS:
288  {
289  PDPL_MESSAGE_CONNECTION_SETTINGS pConnectionStatusMsg;
290  pConnectionStatusMsg = (PDPL_MESSAGE_CONNECTION_SETTINGS)msgBuffer;
291  Q_UNUSED(pConnectionStatusMsg)
292 
293  // The app has changed the connection settings.
294  // This simple client doesn't handle this, but more complex clients may
295  // want to.
296  break;
297  }
298  }
299  return S_OK;
300  }
301 } // namespace swift::simplugin::fs9
SWIFT_CORE_EXPORT swift::core::CApplication * sApp
Single instance of application object.
Definition: application.cpp:71
const QString & swiftVersionString() const
swift info string
Class for emitting a log message.
Definition: logmessage.h:27
Derived & debug()
Set the severity to debug.
Derived & info(const char16_t(&format)[N])
Set the severity to info, providing a format string.
static GUID guid()
Get FS9 application GUID.
Definition: fs9.h:47
Lobby client launching and connecting FS9.
Definition: lobbyclient.h:18
HRESULT initDirectPlay()
Initialize DirectPlay.
Definition: lobbyclient.cpp:55
void disconnected()
Emitted when FS9 is closed.
virtual ~CLobbyClient()
Destructor.
Definition: lobbyclient.cpp:35
HRESULT connectFs9ToHost(const QString &address)
Connect FS9 simulator to our host.
bool canLobbyConnect()
Can FS9 be lobby connected?
Definition: lobbyclient.cpp:84
void SafeDeleteArray(T *&pT)
Safely delete an allocated array.
void SafeRelease(T *&pT)
Safely release a COM allocated object.
void SafeDelete(T *&pT)
Safely delete an allocated pointer.
Free functions in swift::misc.
unsigned long DWORD
Fake Windows DWORD.
static ReturnType WINAPI messageHandler(void *userContext, Argument1 arg1, Argument2 arg2)
FS9 message handler callback.