the game where you go into mines and start crafting! but for consoles (forked directly from smartcmd's github)
1#include "stdafx.h"
2
3#include "DQRNetworkManager.h"
4#include "PartyController.h"
5#include <collection.h>
6#include <ppltasks.h>
7#include <ws2tcpip.h>
8#include "..\Minecraft.World\StringHelpers.h"
9#include "base64.h"
10
11#ifdef _DURANGO
12#include "..\Minecraft.World\DurangoStats.h"
13#endif
14
15#include "ChatIntegrationLayer.h"
16
17
18using namespace Concurrency;
19using namespace Windows::Foundation::Collections;
20
21DQRNetworkManager::ePartyProcessType DQRNetworkManager::m_partyProcess = DQRNetworkManager::DNM_PARTY_PROCESS_NONE;
22
23bool DQRNetworkManager::m_inviteReceived = false;
24int DQRNetworkManager::m_bootUserIndex;
25wstring DQRNetworkManager::m_bootSessionName;
26wstring DQRNetworkManager::m_bootServiceConfig;
27wstring DQRNetworkManager::m_bootSessionTemplate;
28DQRNetworkManager * DQRNetworkManager::s_pDQRManager = NULL;
29
30//using namespace Windows::Xbox::Networking;
31
32DQRNetworkManager::SessionInfo::SessionInfo(wstring& sessionName, wstring& serviceConfig, wstring& sessionTemplate)
33{
34 m_detailsValid = true;
35 m_sessionName = sessionName;
36 m_serviceConfig = serviceConfig;
37 m_sessionTemplate = sessionTemplate;
38}
39
40DQRNetworkManager::SessionInfo::SessionInfo()
41{
42 m_detailsValid = false;
43}
44
45// This maps internal to extern states, and needs to match element-by-element the eSQRNetworkManagerInternalState enumerated type
46const DQRNetworkManager::eDQRNetworkManagerState DQRNetworkManager::m_INTtoEXTStateMappings[DQRNetworkManager::DNM_INT_STATE_COUNT] =
47{
48 DNM_STATE_INITIALISING, // DNM_INT_STATE_INITIALISING
49 DNM_STATE_INITIALISE_FAILED, // DNM_INT_STATE_INITIALISE_FAILED
50 DNM_STATE_IDLE, // DNM_INT_STATE_IDLE
51 DNM_STATE_HOSTING, // DNM_INT_STATE_HOSTING
52 DNM_STATE_HOSTING, // DNM_INT_STATE_HOSTING_WAITING_TO_PLAY
53 DNM_STATE_HOSTING, // DNM_INT_STATE_HOSTING_FAILED
54 DNM_STATE_JOINING, // DNM_INT_STATE_JOINING
55 DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS
56 DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_GET_SDA
57 DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_WAITING_FOR_SDA
58 DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_CREATE_SESSION
59 DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_WAITING_FOR_ACTIVE_SESSION
60 DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_SENDING_UNRELIABLE
61 DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_FAILED_TIDY_UP
62 DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_FAILED
63 DNM_STATE_STARTING, // DNM_INT_STATE_STARTING
64 DNM_STATE_PLAYING, // DNM_INT_STATE_PLAYING
65 DNM_STATE_LEAVING, // DNM_INT_STATE_LEAVING
66 DNM_STATE_LEAVING, // DNM_INT_STATE_LEAVING_FAILED
67 DNM_STATE_ENDING, // DNM_INT_STATE_ENDING
68};
69
70DQRNetworkManager::DQRNetworkManager(IDQRNetworkManagerListener *listener)
71{
72 s_pDQRManager = this;
73 m_listener = listener;
74 m_eventHandlers = ref new DQRNetworkManagerEventHandlers(this);
75 m_XRNS_Session = nullptr;
76 m_multiplayerSession = nullptr;
77 m_sda = nullptr;
78 m_currentSmallId = 0;
79 m_hostSmallId = 0;
80 m_isHosting = false;
81 m_isInSession = false;
82 m_partyController = new PartyController(this);
83 m_partyController->RegisterEventHandlers();
84 memset(m_sessionAddressFromSmallId,0,sizeof(m_sessionAddressFromSmallId));
85 memset(m_channelFromSmallId,0,sizeof(m_channelFromSmallId));
86
87 memset(&m_roomSyncData, 0, sizeof(m_roomSyncData));
88 memset(m_players, 0, sizeof(m_players));
89
90 m_CreateSessionThread = NULL;
91 m_GetFriendPartyThread = NULL;
92 m_UpdateCustomSessionDataThread = NULL;
93
94 m_CheckPartyInviteThread = NULL;
95 m_notifyForFullParty = false;
96
97 m_customDataDirtyUpdateTicks = 0;
98 m_sessionResultCount = 0;
99 m_sessionSearchResults = NULL;
100 m_joinSessionUserMask = 0;
101 m_cancelJoinFromSearchResult = false;
102
103 InitializeCriticalSection(&m_csStateChangeQueue);
104 InitializeCriticalSection(&m_csHostGamertagResolveResults);
105 InitializeCriticalSection(&m_csRTSMessageQueueIncoming);
106 InitializeCriticalSection(&m_csRTSMessageQueueOutgoing);
107 InitializeCriticalSection(&m_csSendBytes);
108 InitializeCriticalSection(&m_csVecChatPlayers);
109 InitializeCriticalSection(&m_csRoomSyncData);
110 InitializeCriticalSection(&m_csPartyViewVector);
111
112 m_joinSessionXUIDs = ref new Platform::Array<Platform::String ^>(4);
113 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_IDLE;
114 m_removeLocalPlayerState = DNM_REMOVE_PLAYER_STATE_IDLE;
115
116 m_playersLeftParty = 0;
117
118 m_handleForcedSignOut = false;
119
120 m_RTS_Stat_totalBytes = 0;
121 m_RTS_Stat_totalSends = 0;
122
123 m_RTS_DoWorkThread = new C4JThread(DQRNetworkManager::_RTSDoWorkThread, this, "Realtimesession processing");
124 m_RTS_DoWorkThread->SetProcessor(CPU_CORE_DQR_REALTIMESESSION);
125 m_RTS_DoWorkThread->SetPriority(THREAD_PRIORITY_ABOVE_NORMAL);
126 m_RTS_DoWorkThread->Run();
127}
128
129void DQRNetworkManager::Initialise()
130{
131 m_associationTemplate = WXN::SecureDeviceAssociationTemplate::GetTemplateByName( L"MultiplayerUdp" );
132
133 m_state = DNM_INT_STATE_IDLE;
134 m_stateExternal = DNM_STATE_IDLE;
135
136 m_chat = GetChatIntegrationLayer();
137 m_chat->InitializeChatManager(true, true, false, false, this);
138}
139
140// This method can be called on any xbox live context, to enable tracing of the service calls that go on internally when anything is done using that context
141void DQRNetworkManager::EnableDebugXBLContext(MXS::XboxLiveContext^ XBLContext)
142{
143#ifndef _CONTENT_PACKAGE
144 // Turn on debug logging to Output debug window for Xbox Services
145 XBLContext->Settings->DiagnosticsTraceLevel = MXS::XboxServicesDiagnosticsTraceLevel::Verbose;
146
147 // Show service calls from Xbox Services on the UI for easy debugging
148 XBLContext->Settings->EnableServiceCallRoutedEvents = true;
149 XBLContext->Settings->ServiceCallRouted += ref new Windows::Foundation::EventHandler<Microsoft::Xbox::Services::XboxServiceCallRoutedEventArgs^>(
150 [=]( Platform::Object^, Microsoft::Xbox::Services::XboxServiceCallRoutedEventArgs^ args )
151 {
152 //if( args->HttpStatus != 200 )
153 {
154 LogComment(L"[URL]: " + args->HttpMethod + " " + args->Url->AbsoluteUri);
155 if( !args->RequestBody->IsEmpty() )
156 {
157 LogComment(L"[RequestBody]: " + args->RequestBody );
158 }
159 LogComment(L"");
160 LogComment(L"[Response]: " + args->HttpStatus.ToString() + " " + args->ResponseBody);
161 LogComment(L"");
162 }
163 });
164#endif
165}
166
167// This is the top level method called when starting to host a network game. Most of the functionality is asynchronously run in a separate thread kicked off here - see ::HostGameThreadProc
168void DQRNetworkManager::CreateAndJoinSession(int usersMask, unsigned char *customSessionData, unsigned int customSessionDataSize, bool offline)
169{
170 m_isHosting = true;
171 m_isInSession = true;
172 m_isOfflineGame = offline;
173 m_currentUserMask = usersMask;
174 SetState(DNM_INT_STATE_HOSTING);
175 m_customSessionData = customSessionData;
176 m_customSessionDataSize = customSessionDataSize;
177
178 m_CreateSessionThread = new C4JThread(&DQRNetworkManager::_HostGameThreadProc, this, "Create session");
179 m_CreateSessionThread->Run();
180}
181
182// Flag that the custom session data has been updated - this isn't actually updated here since updating is an asynchronous process and we may already be in the middle of doing an
183// update. Instead the custom data is flagged flagged as dirty here, and it will be considered for updated when next appropriate during a tick.
184void DQRNetworkManager::UpdateCustomSessionData()
185{
186 if( !m_isHosting) return;
187 if( m_isOfflineGame ) return;
188
189 // Update data on next tick
190 m_customDataDirtyUpdateTicks = 1;
191}
192
193// This is the main method for finishing joining a game session itself, by any method.
194// By the point this is called, we should already have a reserved slot in the game session, by virtue
195// of adding our local players to the party, having this noticed by the host, and the host add reserved slots for us in the game session.
196// At this point we need to:
197// (1) Set out players state in the session to active, so that they won't timeout & be removed
198// (2) Get the network details of the host that we need to connect to
199// (3) Set state up so that in the next tick we'll attempt to set up the network communications for this endpoint
200// Note that the reason that the final setting up of the network endpoint isn't just directly in this method itself, is that we have seen it fail in
201// the past and so we need to be able to retry it, which is simpler if it is part of our general state machine to be able to repeat this operation.
202void DQRNetworkManager::JoinSession(int playerMask)
203{
204 // Establish a primary user & xbox live context for this user. We can use these for operations which aren't particular to any specific user on the local console
205 m_primaryUser = ProfileManager.GetUser(0);
206 if( m_primaryUser == nullptr )
207 {
208 app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting primary user\n");
209 SetState(DNM_INT_STATE_JOINING_FAILED);
210 return;
211 }
212
213 m_primaryUserXboxLiveContext = ref new MXS::XboxLiveContext(m_primaryUser);
214 if( m_primaryUserXboxLiveContext == nullptr )
215 {
216 app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting primary context\n");
217 SetState(DNM_INT_STATE_JOINING_FAILED);
218 return;
219 }
220 EnableDebugXBLContext(m_primaryUserXboxLiveContext);
221
222 SetState(DNM_INT_STATE_JOINING);
223
224 m_partyController->RefreshPartyView();
225 m_isInSession = true;
226 m_isOfflineGame = false;
227
228 for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ )
229 {
230 // Get the game session associated with our party. We need to get this once for every person joining to set them individually to be active
231 if( playerMask & ( 1 << i ) )
232 {
233 MXSM::MultiplayerSession^ session = nullptr;
234
235 // Get user & xbox live context for this specific local user that we are attempting to join
236 WXS::User^ joiningUser = ProfileManager.GetUser(i);
237 if( joiningUser == nullptr )
238 {
239 app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting joining user\n");
240 SetState(DNM_INT_STATE_JOINING_FAILED);
241 return;
242 }
243
244 MXS::XboxLiveContext^ joiningUserXBLContext = ref new MXS::XboxLiveContext(joiningUser);
245 if( joiningUserXBLContext == nullptr )
246 {
247 app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting joining context\n");
248 SetState(DNM_INT_STATE_JOINING_FAILED);
249 return;
250 }
251
252 if( m_partyController->GetPartyView() == nullptr )
253 {
254 app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting party view\n");
255 SetState(DNM_INT_STATE_JOINING_FAILED);
256 return;
257 }
258
259 // Get a copy of the session document, for this user
260 auto multiplayerSessionAsync = joiningUserXBLContext->MultiplayerService->GetCurrentSessionAsync( ConvertToMicrosoftXboxServicesMultiplayerSessionReference(m_partyController->GetPartyView()->GameSession));
261 create_task(multiplayerSessionAsync).then([&session,this](task<MXSM::MultiplayerSession^> t)
262 {
263 try
264 {
265 session = t.get(); // if t.get() didn't throw, it succeeded
266 }
267 catch (Platform::COMException^ ex)
268 {
269 LogCommentWithError( L"MultiplayerSession failed", ex->HResult );
270 }
271 })
272 .wait();
273
274 // If we found the session, then set the status of this member to be active (should be reserved). This will stop our slot timing out and us being dropped out of the session.
275 if( session != nullptr )
276 {
277 if(!IsPlayerInSession(joiningUser->XboxUserId, session, NULL) )
278 {
279 app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED didn't find required player in session\n");
280 SetState(DNM_INT_STATE_JOINING_FAILED);
281 return;
282 }
283 session->SetCurrentUserStatus(MXSM::MultiplayerSessionMemberStatus::Active);
284 HRESULT hr = S_OK;
285 session = WriteSessionHelper( joiningUserXBLContext, session, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr );
286 HandleSessionChange(session);
287 }
288 else
289 {
290 app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED didn't find session\n");
291 SetState(DNM_INT_STATE_JOINING_FAILED);
292 return;
293 }
294 }
295 }
296
297 MXSM::MultiplayerSession^ session = m_multiplayerSession;
298
299 if( session != nullptr )
300 {
301 // Get the secure device address for the host player, and then attempt to create a association with it
302 int hostSessionIndex = GetSessionIndexAndSmallIdForHost(&m_hostSmallId);
303
304 MXSM::MultiplayerSessionMember^ member = m_multiplayerSession->Members->GetAt(hostSessionIndex);
305
306 m_secureDeviceAddressBase64 = member->SecureDeviceAddressBase64;
307
308 m_isHosting = false;
309
310 sockaddr_in6 localSocketAddressStorage;
311
312 ZeroMemory(&localSocketAddressStorage, sizeof(localSocketAddressStorage));
313
314 localSocketAddressStorage.sin6_family = AF_INET6;
315 localSocketAddressStorage.sin6_port = htons(m_associationTemplate->AcceptorSocketDescription->BoundPortRangeLower);
316
317 memcpy(&localSocketAddressStorage.sin6_addr, &in6addr_any, sizeof(in6addr_any));
318
319 m_localSocketAddress = Platform::ArrayReference<BYTE>(reinterpret_cast<BYTE*>(&localSocketAddressStorage), sizeof(localSocketAddressStorage));
320
321 m_joinCreateSessionAttempts = 0;
322
323 m_joinSmallIdMask = playerMask;
324
325 SetState(DNM_INT_STATE_JOINING_GET_SDA);
326 }
327 else
328 {
329 app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting session\n");
330 SetState(DNM_INT_STATE_JOINING_FAILED);
331 }
332}
333
334void DQRNetworkManager::JoinSessionFromInviteInfo(int playerMask)
335{
336 // Gather set of XUIDs for the players that we are joining with
337 for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ )
338 {
339 if( playerMask & ( 1 << i ) )
340 {
341 WXS::User^ user = ProfileManager.GetUser(i);
342 if( user == nullptr )
343 {
344 return;
345 }
346 m_joinSessionXUIDs[i] = user->XboxUserId;
347 }
348 else
349 {
350 m_joinSessionXUIDs[i] = nullptr;
351 }
352 }
353
354 // It is possible that in addition to the player that has been invited (and will already have a slot in the session) that we added more local player(s) from the quadrant sign in.
355 // In this case, then we need to attempt to add these to the party at this stage, and then wait for another gameSession ready event(s) before trying to progress to getting the whole
356 // set of local players into the game
357
358 bool playerAdded = m_partyController->AddLocalUsersToParty( playerMask, ProfileManager.GetUser(0) );
359
360 if( playerAdded )
361 {
362 app.DebugPrintf("Joining from invite, but extra non-party user(s) found so waiting for reservations\n");
363 // Wait until we get notification via game session ready that our newly added party members have slots, then proceed to join session
364 m_isInSession = true;
365 m_startedWaitingForReservationsTime = System::currentTimeMillis();
366 m_joinSessionUserMask = playerMask;
367 m_currentUserMask = 0;
368 m_isOfflineGame = false;
369
370 SetState(DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS);
371 }
372 else
373 {
374 app.DebugPrintf("Joining from invite, no extra non-party users required\n");
375 // No further players added - continue directly on with joining
376 JoinSession(playerMask);
377 }
378}
379
380
381// Add one or more local users (specified by bits set in playerMask) to the session. We use this as a client in a network game. At the stage this
382// is called, the players being added should already have reserved slots in the session - ie we've already put the plyers in the party, this has
383// been detected by the host, and it has added the reserved slots in the sesion that we require.
384//
385// At this stage we need to:
386// (1) Set the player's state in the session to active
387// (2) Send the small Id of the player to the host - we've already got reliable network communications to the host at this point. This lets the
388// host know that there is an active player on this communication channel (we are multiplexing 4 channels, one for each local player)
389
390bool DQRNetworkManager::AddUsersToSession(int playerMask, MXSM::MultiplayerSessionReference^ sessionRef )
391{
392 if( m_isHosting )
393 {
394 return false;
395 }
396
397 bool bSuccess = true;
398 for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ )
399 {
400 if( playerMask & ( 1 << i ) )
401 {
402 // We need to get a MultiplayerSession for each player that is joining
403
404 MXSM::MultiplayerSession^ session = nullptr;
405
406 WXS::User^ newUser = ProfileManager.GetUser(i);
407 if( newUser == nullptr )
408 {
409 bSuccess = false;
410 continue;
411 }
412 MXS::XboxLiveContext^ newUserXBLContext = ref new MXS::XboxLiveContext(newUser);
413
414 auto multiplayerSessionAsync = newUserXBLContext->MultiplayerService->GetCurrentSessionAsync( sessionRef );
415 create_task(multiplayerSessionAsync).then([&session,this](task<MXSM::MultiplayerSession^> t)
416 {
417 try
418 {
419 session = t.get(); // if t.get() didn't throw, it succeeded
420 }
421 catch (Platform::COMException^ ex)
422 {
423 LogCommentWithError( L"MultiplayerSession failed", ex->HResult );
424 }
425 })
426 .wait();
427
428 // If we found the session, then set the status of this member to be active (should be reserved). This will stop our slot timing out and us being dropped out of the session.
429 if( session != nullptr )
430 {
431 int smallId = -1;
432 if(!IsPlayerInSession(newUser->XboxUserId, session, &smallId) )
433 {
434 bSuccess = false;
435 continue;
436 }
437 session->SetCurrentUserStatus(MXSM::MultiplayerSessionMemberStatus::Active);
438 HRESULT hr = S_OK;
439 session = WriteSessionHelper( newUserXBLContext, session, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr );
440 HandleSessionChange(session);
441
442 SendSmallId(true, 1 << i);
443 }
444 }
445 }
446 return bSuccess;
447}
448
449bool DQRNetworkManager::AddLocalPlayerByUserIndex(int userIndex)
450{
451 // We need to handle this differently for the host and other machines. As the procedure for adding a reserved slot for a local player whilst on the host doesn't seem to work
452 //
453 // On the host machine, we:
454 //
455 // (1) Get a MPSD for the player that is being added
456 // (2) Call the join method
457 // (3) Write the MPSD
458 // (4) Update the player sync data, and broadcast out to all clients
459
460 // On remote machines, we:
461 //
462 // (1) join the party
463 // (2) the host should (if a slot is available) put a reserved slot in the session and attempt to pull reserved players
464 // (3) the client will respond to the gamesessionready event that is then received to set the added player to be active
465
466 // If we're already in some of the asynchronous processing for adding as player, then we can't add another one - just fail straight away
467 if( m_addLocalPlayerState != DNM_ADD_PLAYER_STATE_IDLE ) return false;
468
469 if( m_isHosting )
470 {
471 WXS::User^ newUser = ProfileManager.GetUser(userIndex);
472 if( newUser == nullptr )
473 {
474 return false;
475 }
476
477 if( !m_isOfflineGame )
478 {
479 // This is going to involve some async processing
480
481 MXS::XboxLiveContext^ newUserXBLContext = ref new MXS::XboxLiveContext(newUser);
482 if( newUserXBLContext == nullptr )
483 {
484 return false;
485 }
486
487 EnableDebugXBLContext( newUserXBLContext);
488
489 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_PROCESSING;
490
491 auto asyncOp = newUserXBLContext->MultiplayerService->GetCurrentSessionAsync( m_multiplayerSession->SessionReference );
492 create_task(asyncOp)
493 .then([this,newUserXBLContext,userIndex] (task<Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^> t)
494 {
495 try
496 {
497 Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ currentSession = t.get();
498
499 // Don't attempt to join a player if we've no slots left in the session (this will include reserved slots)
500 if( currentSession->Members->Size < currentSession->SessionConstants->MaxMembersInSession )
501 {
502 int smallId = m_currentSmallId;
503 MXSM::MultiplayerSessionMember ^member = currentSession->Join(GetNextSmallIdAsJsonString(), false);
504 currentSession->SetCurrentUserStatus( MXSM::MultiplayerSessionMemberStatus::Active );
505 m_currentUserMask |= (1 << userIndex );
506
507 // Get device ID for current user & set in the session
508 Platform::String^ secureDeviceAddress = WXN::SecureDeviceAddress::GetLocal()->GetBase64String();
509 currentSession->SetCurrentUserSecureDeviceAddressBase64( secureDeviceAddress );
510
511 HRESULT result;
512 WriteSessionHelper(newUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, result); // ************ WAITING **************
513
514 DQRNetworkPlayer* pPlayer = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_LOCAL, true, userIndex, m_XRNS_Session->LocalSessionAddress);
515 pPlayer->SetSmallId(smallId);
516 pPlayer->SetName(ProfileManager.GetUser(userIndex)->DisplayInfo->Gamertag->Data());
517 pPlayer->SetDisplayName(ProfileManager.GetDisplayName(userIndex));
518 pPlayer->SetUID(PlayerUID(ProfileManager.GetUser(userIndex)->XboxUserId->Data()));
519
520 // Also add to the party so that our friends can find us. The host will get notified of this additional player in the party, but we should ignore since we're already in the session
521 m_partyController->AddLocalUsersToParty(1 << userIndex, ProfileManager.GetUser(0));
522
523 m_addLocalPlayerSuccessPlayer = pPlayer;
524 m_addLocalPlayerSuccessIndex = userIndex;
525 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_SUCCESS;
526 }
527 else
528 {
529 m_addLocalPlayerFailedIndex = userIndex;
530 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL_FULL;
531 }
532 }
533 catch ( Platform::COMException^ ex )
534 {
535 m_addLocalPlayerFailedIndex = userIndex;
536 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL;
537 }
538 catch ( Platform::Exception ^ex )
539 {
540 m_addLocalPlayerFailedIndex = userIndex;
541 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL;
542 }
543 });
544
545 return true;
546 }
547 else
548 {
549 DQRNetworkPlayer* pPlayer = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_LOCAL, true, userIndex, 0);
550 pPlayer->SetSmallId(m_currentSmallId++);
551 pPlayer->SetName(ProfileManager.GetUser(userIndex)->DisplayInfo->Gamertag->Data());
552 pPlayer->SetDisplayName(ProfileManager.GetDisplayName(userIndex));
553 pPlayer->SetUID(PlayerUID(ProfileManager.GetUser(userIndex)->XboxUserId->Data()));
554
555 // TODO - could this add fail?
556 if(AddRoomSyncPlayer( pPlayer, 0, userIndex))
557 {
558 SendRoomSyncInfo();
559 m_listener->HandlePlayerJoined(pPlayer); // This is for notifying of local players joining in an offline game
560 }
561 else
562 {
563 // Can fail (notably if m_roomSyncData contains players who've left)
564 assert(0);
565 return false;
566 }
567 }
568 return true;
569 }
570 else
571 {
572 // Check if there's any available slots before attempting to add the player to the party. We can still fail joining later if
573 // the host can't add a reserved slot for us for some reason but better checking on the client side before even attempting.
574
575 WXS::User^ newUser = ProfileManager.GetUser(userIndex);
576
577 MXS::XboxLiveContext^ newUserXBLContext = ref new MXS::XboxLiveContext(newUser);
578 if( newUserXBLContext == nullptr )
579 {
580 return false;
581 }
582
583 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_PROCESSING;
584 auto asyncOp = newUserXBLContext->MultiplayerService->GetCurrentSessionAsync( m_multiplayerSession->SessionReference );
585 create_task(asyncOp)
586 .then([this,newUserXBLContext,userIndex] (task<Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^> t)
587 {
588 try
589 {
590 Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ currentSession = t.get();
591
592 if( currentSession->Members->Size == currentSession->SessionConstants->MaxMembersInSession )
593 {
594 m_addLocalPlayerFailedIndex = userIndex;
595 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL_FULL;
596 }
597 else
598 {
599 m_joinSessionUserMask |= (1 << userIndex);
600 m_joinSessionXUIDs[userIndex] = ProfileManager.GetUser(userIndex)->XboxUserId;
601 m_partyController->AddLocalUsersToParty(1 << userIndex, ProfileManager.GetUser(0));
602
603 m_addLocalPlayerSuccessPlayer = NULL;
604 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_SUCCESS;
605 }
606 }
607 catch( Platform::COMException^ ex )
608 {
609 m_addLocalPlayerFailedIndex = userIndex;
610 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL;
611 }
612 catch ( Platform::Exception ^ex )
613 {
614 m_addLocalPlayerFailedIndex = userIndex;
615 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL;
616 }
617 });
618
619 return true;
620 }
621}
622
623bool DQRNetworkManager::RemoveLocalPlayerByUserIndex(int userIndex)
624{
625 // We need to handle this differently for the host and other machines.
626 //
627 // On the host machine, we:
628 //
629 // (1) Get a MPSD for the player that is being removed
630 // (2) Call the leave method
631 // (3) Write the MPSD
632 // (4) Leave the party
633 // (5) Update the player sync data, and broadcast out to all clients
634
635 // On remote machines, we:
636 //
637 // (1) Get a MPSD for the player that is being removed
638 // (2) Call the leave method
639 // (3) Write the MPSD
640 // (4) Leave the party
641 // (5) send message to the host that this player has left
642 // (6) host should respond to this message by removing the player from the player sync data, and notifying all clients of updated players
643 // (7) the client should respond to the player leaving that will happen and this will actually notify the game that the player has left
644
645 // TODO - this should be rearranged so that the async stuff isn't waited on here, and so that the callbacks don't get called from the task's thread
646
647 if( m_removeLocalPlayerState != DNM_REMOVE_PLAYER_STATE_IDLE ) return false;
648
649 WXS::User^ leavingUser = ProfileManager.GetUser(userIndex, true);
650 if( leavingUser == nullptr )
651 {
652 return false;
653 }
654
655 if( !m_isOfflineGame )
656 {
657 if( m_chat )
658 {
659 m_chat->RemoveLocalUser(leavingUser);
660 }
661 MXS::XboxLiveContext^ leavingUserXBLContext = ref new MXS::XboxLiveContext(leavingUser);
662 if( leavingUserXBLContext == nullptr )
663 {
664 return false;
665 }
666 EnableDebugXBLContext( leavingUserXBLContext);
667 m_removeLocalPlayerState = DNM_REMOVE_PLAYER_STATE_PROCESSING;
668 auto asyncOp = leavingUserXBLContext->MultiplayerService->GetCurrentSessionAsync( m_multiplayerSession->SessionReference );
669 create_task(asyncOp)
670 .then([this,leavingUserXBLContext,userIndex,leavingUser] (task<Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^> t)
671 {
672 try
673 {
674 Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ currentSession = t.get();
675
676 // Remove from the party
677 LogComment(L"Removing user from party");
678 m_partyController->RemoveLocalUserFromParty(leavingUser);
679 LogComment(L"Removed user from party, now leaving session");
680
681 // Then leave & update the session
682 currentSession->Leave();
683 HRESULT result;
684 WriteSessionHelper(leavingUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, result);
685 m_currentUserMask &= ~(1<<userIndex);
686 LogComment(L"Finished leaving session");
687
688 // Complete any deferred sign outs
689 ProfileManager.CompleteDeferredSignouts();
690
691 m_removeLocalPlayerIndex = userIndex;
692 m_removeLocalPlayerState = DNM_REMOVE_PLAYER_STATE_COMPLETE_SUCCESS;
693 }
694 catch ( Platform::COMException^ ex )
695 {
696 app.DebugPrintf("DQRNetworkManager::RemoveLocalPlayerByUserIndex: Failed to remove local player %i (0x%x)\n", userIndex, ex->HResult);
697
698 m_removeLocalPlayerState = DNM_REMOVE_PLAYER_STATE_COMPLETE_FAIL;
699 m_removeLocalPlayerIndex = userIndex;
700 }
701 });
702 }
703 else
704 {
705 DQRNetworkPlayer* pPlayer = GetLocalPlayerByUserIndex( userIndex );
706 RemoveRoomSyncPlayer( pPlayer );
707 }
708 return true;
709}
710
711bool DQRNetworkManager::IsHost()
712{
713 return m_isHosting;
714}
715
716// Consider as "in session" from the moment that a game is created or joined, until the point where the game itself has been told via state change that we are now idle. The
717// game code requires IsInSession to return true as soon as it has asked to do one of these things (even if the state system hasn't really caught up with this request yet), and
718// it also requires that it is informed of the state changes leading up to not being in the session, before this should report false.
719bool DQRNetworkManager::IsInSession()
720{
721 return m_isInSession;
722}
723
724// Get count of players currently in the session
725int DQRNetworkManager::GetPlayerCount()
726{
727 return m_roomSyncData.playerCount;
728}
729
730// Get count of players who are in the session, but not local to this machine
731int DQRNetworkManager::GetOnlinePlayerCount()
732{
733 int count = 0;
734 for( int i = 0; i < MAX_ONLINE_PLAYER_COUNT; i++ )
735 {
736 if( m_players[i] )
737 {
738 if( !m_players[i]->IsLocal() )
739 {
740 count++;
741 }
742 }
743 }
744 return count;
745}
746
747
748DQRNetworkPlayer *DQRNetworkManager::GetPlayerByIndex(int idx)
749{
750 return m_players[idx];
751
752}
753
754DQRNetworkPlayer *DQRNetworkManager::GetPlayerBySmallId(int idx)
755{
756 for( int i = 0; i < MAX_ONLINE_PLAYER_COUNT; i++ )
757 {
758 if( m_players[i] )
759 {
760 if( m_players[i]->GetSmallId() == idx)
761 {
762 return m_players[i];
763 }
764 }
765 }
766 return NULL;
767}
768
769DQRNetworkPlayer *DQRNetworkManager::GetPlayerByXuid(PlayerUID xuid)
770{
771 for( int i = 0; i < MAX_ONLINE_PLAYER_COUNT; i++ )
772 {
773 if( m_players[i] )
774 {
775 if( m_players[i]->GetUID() == xuid)
776 {
777 return m_players[i];
778 }
779 }
780 }
781 return NULL;
782}
783
784// Retrieve player display name by gamertag
785wstring DQRNetworkManager::GetDisplayNameByGamertag(wstring gamertag)
786{
787 if (m_displayNames.find(gamertag) != m_displayNames.end())
788 {
789 return m_displayNames[gamertag];
790 }
791 else
792 {
793 return gamertag;
794 }
795}
796
797DQRNetworkPlayer *DQRNetworkManager::GetLocalPlayerByUserIndex(int idx)
798{
799 for( int i = 0; i < MAX_ONLINE_PLAYER_COUNT; i++ )
800 {
801 if( m_players[i] )
802 {
803 if( m_players[i]->IsLocal() )
804 {
805 if( m_players[i]->GetLocalPlayerIndex() == idx )
806 {
807 return m_players[i];
808 }
809 }
810 }
811 }
812 return NULL;
813}
814
815DQRNetworkPlayer *DQRNetworkManager::GetHostPlayer()
816{
817 return GetPlayerBySmallId(m_hostSmallId);
818}
819
820
821int DQRNetworkManager::GetSessionIndex(DQRNetworkPlayer *player)
822{
823 for( int i = 0; i < MAX_ONLINE_PLAYER_COUNT; i++ )
824 {
825 if( m_players[i] == player )
826 {
827 return i;
828 }
829 }
830 return 0;
831}
832
833void DQRNetworkManager::SetState(DQRNetworkManager::eDQRNetworkManagerInternalState state)
834{
835 eDQRNetworkManagerState oldState = m_INTtoEXTStateMappings[m_state];
836 eDQRNetworkManagerState newState = m_INTtoEXTStateMappings[state];
837 m_state = state;
838
839 // Queue any important (ie externally relevant) state changes - we will do a call back for these in our main tick. Don't do it directly here
840 // as we could be coming from any thread at this stage, with any stack size etc. and so we don't generally want to expect the game to be able to handle itself in such circumstances.
841 if( newState != oldState )
842 {
843 EnterCriticalSection(&m_csStateChangeQueue);
844 m_stateChangeQueue.push(StateChangeInfo(oldState,newState));
845 LeaveCriticalSection(&m_csStateChangeQueue);
846 }
847}
848
849DQRNetworkManager::eDQRNetworkManagerState DQRNetworkManager::GetState()
850{
851 return m_stateExternal;;
852}
853
854void DQRNetworkManager::Tick()
855{
856 Tick_XRNS();
857 Tick_VoiceChat();
858 Tick_Party();
859 Tick_CustomSessionData();
860 Tick_AddAndRemoveLocalPlayers();
861 Tick_ResolveGamertags();
862 Tick_PartyProcess();
863 Tick_StateMachine();
864 Tick_CheckInviteParty();
865}
866
867void DQRNetworkManager::Tick_XRNS()
868{
869 ProcessRTSMessagesIncoming();
870}
871
872void DQRNetworkManager::Tick_VoiceChat()
873{
874#if 0
875 static int chatDumpCount = 0;
876 chatDumpCount++;
877 if( ( chatDumpCount % 40 ) == 0 )
878 {
879 if( m_chat )
880 {
881 LogCommentFormat(L"ChatManager: hasFocus:%d\n",m_chat->HasMicFocus());
882 IVectorView<Microsoft::Xbox::GameChat::ChatUser^>^ chatUsers = m_chat->GetChatUsers();
883 for( int i = 0; i < chatUsers->Size; i++ )
884 {
885 Microsoft::Xbox::GameChat::ChatUser^ chatUser = chatUsers->GetAt(i);
886 LogCommentFormat(L"local: %d muted: %d type:%s restriction:%s mode:%s volume:%f [xuid:%s]\n",
887 chatUser->IsLocal,chatUser->IsMuted,chatUser->ParticipantType.ToString()->Data(),chatUser->RestrictionMode.ToString()->Data(), chatUser->TalkingMode.ToString()->Data(),chatUser->Volume,
888 chatUser->XboxUserId->Data());
889 }
890 }
891 }
892#endif
893 // If we have to inform the chat integration layer of any players that have joined, do that now
894 EnterCriticalSection(&m_csVecChatPlayers);
895 for( int i = 0; i < m_vecChatPlayersJoined.size(); i++ )
896 {
897 int idx = m_vecChatPlayersJoined[i];
898 if( m_chat )
899 {
900 WXS::User^ user = ProfileManager.GetUser(idx);
901 if( user != nullptr )
902 {
903 m_chat->AddLocalUser(user);
904 }
905 }
906 }
907 m_vecChatPlayersJoined.clear();
908 LeaveCriticalSection(&m_csVecChatPlayers);
909}
910
911void DQRNetworkManager::Tick_Party()
912{
913 // If the primary player has been flagged as having left the party, then we don't respond immediately as it is possible we are just transitioning from one party to another, and it would be much
914 // nicer to handle this kind of transition directly. If we do get a new party within this time period, then we'll handle by asking the user if they want to leave the game they are currently in etc.
915 if( m_playersLeftParty )
916 {
917 if( ( System::currentTimeMillis() - m_playersLeftPartyTime ) > PRIMARY_PLAYER_LEAVING_PARTY_WAIT_MS )
918 {
919 // We've waited long enough. User must (hopefully) have just left the party
920 // Previously we'd switch to offline but that causes a world of pain with forced sign-outs
921 if( m_playersLeftParty & 1 )
922 {
923 // Before we switch to an offline game, check to see if there is currently a new party. If this is the case and
924 // we're here, then its because we were added to a party, but didn't receive a gamesessionready event. So if we have
925 // a party here that we've joined, and the number of players in the party (including us) is more than MAX_PLAYERS_IN_TEMPLATE,
926 // then it seems reasonable to assume that the reason we're not in the game is due to lack of space, and we can inform the
927 // user of this when converting to an offline game
928
929 m_partyController->RefreshPartyView();
930 WXM::PartyView^ partyView = m_partyController->GetPartyView();
931 if( partyView )
932 {
933 int partySize = partyView->Members->Size;
934 if( partySize > MAX_PLAYERS_IN_TEMPLATE )
935 {
936 g_NetworkManager.SetFullSessionMessageOnNextSessionChange();
937 }
938 }
939
940 DQRNetworkManager::LogComment(L"Primary player on this system has left the party, switching to offline\n");
941 app.SetAction(0, eAppAction_EthernetDisconnected);
942 }
943 else
944 {
945 // Secondary player(s) leaving, just remove as if they had chosen to exit themselves from the game
946 for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ )
947 {
948 if( m_playersLeftParty & ( 1 << i ) )
949 {
950 RemoveLocalPlayerByUserIndex(i);
951 }
952 }
953 }
954
955 m_playersLeftParty = 0;
956 }
957 }
958
959 // Forced sign out
960 if (m_handleForcedSignOut)
961 {
962 HandleForcedSignOut();
963 m_handleForcedSignOut = false;
964 }
965}
966
967void DQRNetworkManager::Tick_CustomSessionData()
968{
969 // If there was a thread updaing our custom session data, then clear it up if it is done
970 if( m_UpdateCustomSessionDataThread != NULL )
971 {
972 if( !m_UpdateCustomSessionDataThread->isRunning() )
973 {
974 delete m_UpdateCustomSessionDataThread;
975 m_UpdateCustomSessionDataThread = NULL;
976 }
977 }
978
979 // If our custom data is dirty, and we aren't currently updating, then kick off a thread to update it
980 if( m_isHosting && ( !m_isOfflineGame ) )
981 {
982 if( m_UpdateCustomSessionDataThread == NULL )
983 {
984 if( m_customDataDirtyUpdateTicks )
985 {
986 m_customDataDirtyUpdateTicks--;
987 if( m_customDataDirtyUpdateTicks == 0 )
988 {
989 m_UpdateCustomSessionDataThread = new C4JThread(&DQRNetworkManager::_UpdateCustomSessionDataThreadProc, this, "Updating custom data");
990 m_UpdateCustomSessionDataThread->Run();
991 }
992 }
993 }
994 }
995 else
996 {
997 m_customDataDirtyUpdateTicks = 0;
998 }
999}
1000
1001void DQRNetworkManager::Tick_AddAndRemoveLocalPlayers()
1002{
1003 // A lot of handling adding local players is handled asynchronously. Trying to avoid having the callbacks that may result from this being called from the task threads, so handling this aspect of it here in the tick
1004 if( m_addLocalPlayerState == DNM_ADD_PLAYER_STATE_COMPLETE_SUCCESS )
1005 {
1006 // If we've completed, and we're the host, then we should have the new player to create stored here in m_localPlayerSuccessCreated. For clients, this will just be NULL as the actual
1007 // adding of the player happens as part of a longer process of the host creating us a reserved slot etc. etc.
1008 if( m_addLocalPlayerSuccessPlayer )
1009 {
1010 if( AddRoomSyncPlayer( m_addLocalPlayerSuccessPlayer, m_XRNS_Session->LocalSessionAddress, m_addLocalPlayerSuccessIndex) )
1011 {
1012 SendRoomSyncInfo();
1013 m_listener->HandlePlayerJoined(m_addLocalPlayerSuccessPlayer); // This is notifying local players joining, when online (host only)
1014 }
1015 }
1016 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_IDLE;
1017 }
1018 else if( m_addLocalPlayerState == DNM_ADD_PLAYER_STATE_COMPLETE_FAIL )
1019 {
1020 m_listener->HandleAddLocalPlayerFailed(m_addLocalPlayerFailedIndex, false);
1021 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_IDLE;
1022 }
1023 else if( m_addLocalPlayerState == DNM_ADD_PLAYER_STATE_COMPLETE_FAIL_FULL )
1024 {
1025 m_listener->HandleAddLocalPlayerFailed(m_addLocalPlayerFailedIndex, true);
1026 m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_IDLE;
1027 }
1028
1029 // Similarly for removing local players - avoiding having callbacks from the async task threads, so this aspect of the process is handled here
1030
1031 if( m_removeLocalPlayerState == DNM_REMOVE_PLAYER_STATE_COMPLETE_SUCCESS || m_removeLocalPlayerState == DNM_REMOVE_PLAYER_STATE_COMPLETE_FAIL )
1032 {
1033 // Note: we now remove the player from the room sync data even if remove from session/party failed,
1034 // either way we need to clean up
1035
1036 // On host, directly remove from the player sync data. On client, send a message to the host which will do this
1037 if( m_isHosting)
1038 {
1039 DQRNetworkPlayer* pPlayer = GetLocalPlayerByUserIndex( m_removeLocalPlayerIndex );
1040 RemoveRoomSyncPlayer( pPlayer );
1041 SendRoomSyncInfo();
1042 }
1043 else
1044 {
1045 // Check if this player actually exists yet on this machine. If it is, then we need to send a message to the host to unassign it which
1046 // ultimately will end up with this player being removed when the host syncs back with us. If it hasn't then there isn't anything to
1047 // unassign with the host
1048 DQRNetworkPlayer* pPlayer = GetLocalPlayerByUserIndex( m_removeLocalPlayerIndex );
1049 if( pPlayer )
1050 {
1051 SendUnassignSmallId(m_removeLocalPlayerIndex);
1052 }
1053 }
1054 m_removeLocalPlayerState = DNM_REMOVE_PLAYER_STATE_IDLE;
1055 }
1056}
1057
1058void DQRNetworkManager::Tick_ResolveGamertags()
1059{
1060 // Host only - if there are any player display names which have been resolved (or failed to resolve), then this is the last stage in the player becoming active on the host's side and so do a few things here
1061 EnterCriticalSection(&m_csHostGamertagResolveResults);
1062 while( !m_hostGamertagResolveResults.empty() )
1063 {
1064 HostGamertagResolveDetails *details = m_hostGamertagResolveResults.front();
1065
1066 details->m_pPlayer->SetName(details->m_name.c_str());
1067
1068 LogComment("Adding a player");
1069 if( AddRoomSyncPlayer(details->m_pPlayer, details->m_sessionAddress, details->m_channel ) )
1070 {
1071 LogComment("Adding a player - success");
1072 m_listener->HandlePlayerJoined(details->m_pPlayer); // This is for notifying of remote players joining, when online (when we are the host), as we have resolved their names
1073 // The last name to be resolve in any one atomic set (ie that comes in from a single DQR_INTERNAL_ASSIGN_SMALL_IDS message) will have this flag set, so we know this is the point
1074 // to synchronise out to the clients
1075 if( details->m_sync )
1076 {
1077 LogComment("Synchronising players with clients");
1078 SendRoomSyncInfo();
1079 }
1080 }
1081 else
1082 {
1083 LogComment("Adding a player - failed");
1084 delete details->m_pPlayer;
1085
1086 // TODO - what to do if adding a player failed here?
1087 assert(false);
1088 }
1089
1090 delete details;
1091 m_hostGamertagResolveResults.pop();
1092 }
1093 LeaveCriticalSection(&m_csHostGamertagResolveResults);
1094}
1095
1096void DQRNetworkManager::Tick_PartyProcess()
1097{
1098 // On starting up the game, there's 3 options of what we need to do...
1099 // (1) Attempt to join a game session that was passed in on activation (this will have happened if we were started from a game ready notification)
1100 // (2) Attempt to join whatever game the party is associated with (this will happen if we were started in response to a party invite)
1101 switch( m_partyProcess )
1102 {
1103 case DNM_PARTY_PROCESS_NONE:
1104 break;
1105 case DNM_PARTY_PROCESS_JOIN_PARTY:
1106 if( GetBestPartyUserIndex() )
1107 {
1108 m_listener->HandleInviteReceived(0, new SessionInfo());
1109 }
1110 break;
1111 case DNM_PARTY_PROCESS_JOIN_SPECIFIED:
1112 m_listener->HandleInviteReceived(m_bootUserIndex, new SessionInfo(m_bootSessionName, m_bootServiceConfig, m_bootSessionTemplate));
1113 break;
1114 }
1115 m_partyProcess = DNM_PARTY_PROCESS_NONE;
1116}
1117
1118void DQRNetworkManager::Tick_StateMachine()
1119{
1120 switch( m_state )
1121 {
1122 case DNM_INT_STATE_JOINING_GET_SDA:
1123 {
1124 SetState(DNM_INT_STATE_JOINING_WAITING_FOR_SDA);
1125 auto asyncOp = m_associationTemplate->CreateAssociationAsync(WXN::SecureDeviceAddress::FromBase64String(m_secureDeviceAddressBase64), WXN::CreateSecureDeviceAssociationBehavior::Reevaluate);
1126 create_task(asyncOp).then([this](task<WXN::SecureDeviceAssociation^> t)
1127 {
1128 m_sda = nullptr;
1129 try
1130 {
1131 m_sda = t.get();
1132 }
1133 catch (Platform::COMException^ ex)
1134 {
1135 LogCommentWithError( L"CreateAssociationAsync failed", ex->HResult );
1136 }
1137 // If this succeeded, then make a store of all the things we'll need to initiate the network communication endpoint for this machine (our local address, remove address, secure device association etc.)
1138 if( m_sda)
1139 {
1140 m_remoteSocketAddress = ref new Platform::Array<BYTE>(sizeof(SOCKADDR_STORAGE));
1141 m_sda->GetRemoteSocketAddressBytes(m_remoteSocketAddress);
1142 SetState(DNM_INT_STATE_JOINING_CREATE_SESSION);
1143 }
1144 else
1145 {
1146 SetState(DNM_INT_STATE_JOINING_FAILED);
1147 }
1148 });
1149 }
1150 break;
1151 case DNM_INT_STATE_JOINING_CREATE_SESSION:
1152 RTS_StartCient();
1153 SetState(DNM_INT_STATE_JOINING_WAITING_FOR_ACTIVE_SESSION);
1154 break;
1155 case DNM_INT_STATE_JOINING_SENDING_UNRELIABLE:
1156 {
1157 __int64 timeNow = System::currentTimeMillis();
1158 // m_firstUnreliableSendTime of 0 indicates that we haven't tried sending an unreliable packet yet so need to send one and initialise things
1159 if( m_firstUnreliableSendTime == 0 )
1160 {
1161 m_firstUnreliableSendTime = timeNow;
1162 m_lastUnreliableSendTime = timeNow;
1163
1164 SendSmallId(false, m_joinSmallIdMask);
1165 }
1166 else
1167 {
1168 // Timeout if we've exceeded the threshold for this
1169 if( (timeNow - m_firstUnreliableSendTime) > JOIN_PACKET_RESEND_TIMEOUT_MS )
1170 {
1171 app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED unreliable resend timeout\n");
1172 SetState(DNM_INT_STATE_JOINING_FAILED);
1173 }
1174 else
1175 {
1176 // Possibly send another packet if it has been long enough
1177 if( (timeNow - m_lastUnreliableSendTime ) > JOIN_PACKET_RESEND_DELAY_MS )
1178 {
1179 LogComment("Resending unreliable packet\n");
1180 m_lastUnreliableSendTime = timeNow;
1181 SendSmallId(false, m_joinSmallIdMask);
1182 }
1183 }
1184 }
1185 }
1186 break;
1187 case DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS:
1188 {
1189 // Timeout if we've been waiting for reserved slots for our joining players for too long. This is most likely because the host doesn't have room for all the slots we wanted, and we weren't able to determine this
1190 // when we went to join the game (ie someone else was joining at the same time). At this point we need to remove any local players that did already join, from both the session and the party.
1191 __int64 timeNow = System::currentTimeMillis();
1192 if( ( timeNow - m_startedWaitingForReservationsTime ) > JOIN_RESERVATION_WAIT_TIME )
1193 {
1194 SetState(DNM_INT_STATE_JOINING_FAILED_TIDY_UP);
1195 TidyUpFailedJoin();
1196 }
1197 }
1198 break;
1199 case DNM_INT_STATE_ENDING:
1200 SetState(DNM_INT_STATE_IDLE);
1201 break;
1202 case DNM_INT_STATE_HOSTING_WAITING_TO_PLAY:
1203 delete m_CreateSessionThread;
1204 m_CreateSessionThread = NULL;
1205 // If the game is offline we can transition straight to playing
1206 if (m_isOfflineGame) StartGame();
1207 break;
1208 case DNM_INT_STATE_JOINING_FAILED:
1209 SetState(DNM_INT_STATE_JOINING_FAILED_TIDY_UP);
1210 TidyUpFailedJoin();
1211 break;
1212 case DNM_INT_STATE_HOSTING_FAILED:
1213 m_multiplayerSession = nullptr;
1214 LogComment("Error DNM_INT_STATE_HOSTING_FAILED\n");
1215 SetState(DNM_INT_STATE_IDLE);
1216 break;
1217 case DNM_INT_STATE_LEAVING_FAILED:
1218 m_multiplayerSession = nullptr;
1219 LogComment("Error DNM_INT_STATE_LEAVING_FAILED\n");
1220 SetState(DNM_INT_STATE_IDLE);
1221 break;
1222 }
1223
1224 EnterCriticalSection(&m_csStateChangeQueue);
1225 while(m_stateChangeQueue.size() > 0 )
1226 {
1227 if( m_listener )
1228 {
1229 m_listener->HandleStateChange(m_stateChangeQueue.front().m_oldState, m_stateChangeQueue.front().m_newState);
1230 if( m_stateChangeQueue.front().m_newState == DNM_STATE_IDLE )
1231 {
1232 m_isInSession = false;
1233 }
1234 }
1235 m_stateExternal = m_stateChangeQueue.front().m_newState;
1236 m_stateChangeQueue.pop();
1237 }
1238 LeaveCriticalSection(&m_csStateChangeQueue);
1239}
1240
1241void DQRNetworkManager::Tick_CheckInviteParty()
1242{
1243 if( m_inviteReceived )
1244 {
1245 if( m_CheckPartyInviteThread )
1246 {
1247 if( !m_CheckPartyInviteThread->isRunning() )
1248 {
1249 delete m_CheckPartyInviteThread;
1250 m_CheckPartyInviteThread = NULL;
1251 }
1252 }
1253 if( m_CheckPartyInviteThread == NULL )
1254 {
1255 m_inviteReceived = false;
1256 m_CheckPartyInviteThread = new C4JThread(&DQRNetworkManager::_CheckInviteThreadProc, this, "Check invite thread");
1257 m_CheckPartyInviteThread->Run();
1258 }
1259 }
1260}
1261
1262bool DQRNetworkManager::ShouldMessageForFullSession()
1263{
1264 bool retval = m_notifyForFullParty;
1265 m_notifyForFullParty = false;
1266 return retval;
1267}
1268
1269void DQRNetworkManager::FlagInvitedToFullSession()
1270{
1271 m_notifyForFullParty = true;
1272}
1273
1274void DQRNetworkManager::UnflagInvitedToFullSession()
1275{
1276 m_notifyForFullParty = false;
1277}
1278
1279void DQRNetworkManager::AddPlayerFailed(Platform::String ^xuid)
1280{
1281 // A request to add a player (via the party) has been rejected by the host. If this is a player that we were waiting to join, then we can now:
1282 // (1) stop waiting
1283 // (2) remove from the party
1284 // (3) inform the game of the failure
1285 LogCommentFormat(L"AddPlayerFailed received, for XUID %s", xuid->Data());
1286 for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ )
1287 {
1288 if( m_joinSessionUserMask & ( 1 << i ) )
1289 {
1290 if( m_joinSessionXUIDs[i] == xuid )
1291 {
1292 LogCommentFormat(L"AddPlayerFailed received, XUID matched with joining player so handling (index %d)",i);
1293 m_joinSessionUserMask &= ~( 1 << i );
1294 m_joinSessionXUIDs[i] = nullptr;
1295 m_partyController->RemoveLocalUsersFromParty(m_primaryUser, 1 << i, m_multiplayerSession->SessionReference );
1296 m_listener->HandleAddLocalPlayerFailed(i, true);
1297 }
1298 }
1299 }
1300}
1301
1302// Utility method to remove the braces at the start and end of a GUID and return the remaining string
1303Platform::String^ DQRNetworkManager::RemoveBracesFromGuidString(__in Platform::String^ guid )
1304{
1305 std::wstring strGuid = guid->ToString()->Data();
1306
1307 if(strGuid.length() > 0 && strGuid[0] == L'{')
1308 {
1309 // Remove the {
1310 strGuid.erase(0, 1);
1311 }
1312
1313 if(strGuid.length() > 0 && strGuid[strGuid.length() - 1] == L'}')
1314 {
1315 // Remove the }
1316 strGuid.erase(strGuid.end() - 1, strGuid.end());
1317 }
1318
1319 return ref new Platform::String(strGuid.c_str());
1320}
1321
1322void DQRNetworkManager::HandleSessionChange(MXSM::MultiplayerSession^ multiplayerSession)
1323{
1324 // 4J-JEV: Fix for Durango #152208 - [CRASH] Code: Gameplay: Title crashes during loading screen after signing in when prompted.
1325 if (multiplayerSession != nullptr)
1326 {
1327 // 4J-JEV: This id is needed to link stats together.
1328 // I thought setting the value from here would be less intrusive than adding an accessor.
1329 ((DurangoStats*)GenericStats::getInstance())->setMultiplayerCorrelationId(multiplayerSession->MultiplayerCorrelationId);
1330 }
1331 else
1332 {
1333 ((DurangoStats*)GenericStats::getInstance())->setMultiplayerCorrelationId( nullptr );
1334 }
1335
1336 m_multiplayerSession = multiplayerSession;
1337}
1338
1339// Utility method to update a session on the server, synchronously.
1340MXSM::MultiplayerSession^ DQRNetworkManager::WriteSessionHelper( MXS::XboxLiveContext^ xboxLiveContext, MXSM::MultiplayerSession^ multiplayerSession, MXSM::MultiplayerSessionWriteMode writeMode, HRESULT& hr )
1341{
1342 if (multiplayerSession == nullptr)
1343 {
1344 return nullptr;
1345 }
1346
1347 auto asyncOpWriteSessionAsync = xboxLiveContext->MultiplayerService->WriteSessionAsync( multiplayerSession, writeMode );
1348
1349 MXSM::MultiplayerSession^ outputMultiplayerSession = nullptr;
1350
1351 create_task(asyncOpWriteSessionAsync)
1352 .then([&outputMultiplayerSession, &hr](task<MXSM::MultiplayerSession^> t)
1353 {
1354 try
1355 {
1356 outputMultiplayerSession = t.get(); // if t.get() didn't throw, it succeeded
1357 }
1358 catch ( Platform::COMException^ ex )
1359 {
1360 hr = ex->HResult;
1361 }
1362 })
1363 .wait();
1364
1365 if( outputMultiplayerSession != nullptr &&
1366 outputMultiplayerSession->SessionReference != nullptr )
1367 {
1368 app.DebugPrintf( "Session written OK\n" );
1369 }
1370
1371 return outputMultiplayerSession;
1372}
1373
1374MXSM::MultiplayerSessionMember^ DQRNetworkManager::GetUserMemberInSession( Windows::Xbox::System::User^ user, MXSM::MultiplayerSession^ session)
1375{
1376 for each (MXSM::MultiplayerSessionMember^ member in session->Members)
1377 {
1378 if( _wcsicmp(member->XboxUserId->Data(), user->XboxUserId->Data()) == 0)
1379 {
1380 return member;
1381 }
1382 }
1383
1384 return nullptr;
1385}
1386
1387bool DQRNetworkManager::IsPlayerInSession( Platform::String^ xboxUserId, MXSM::MultiplayerSession^ session, int *smallId )
1388{
1389 if( session == nullptr )
1390 {
1391 LogComment(L"IsPlayerInSession: invalid session.");
1392 return false;
1393 }
1394
1395 for each (MXSM::MultiplayerSessionMember^ member in session->Members)
1396 {
1397 if( _wcsicmp(xboxUserId->Data(), member->XboxUserId->Data() ) == 0 )
1398 {
1399 if( smallId )
1400 {
1401 // Get small Id for this member
1402 try
1403 {
1404 Windows::Data::Json::JsonObject^ customConstant = Windows::Data::Json::JsonObject::Parse(member->MemberCustomConstantsJson);
1405 Windows::Data::Json::JsonValue^ customValue = customConstant->GetNamedValue(L"smallId");
1406 *smallId = (int)(customValue->GetNumber()) & 255;
1407 }
1408 catch (Platform::COMException^ ex)
1409 {
1410 LogCommentWithError( L"Custom constant Parse/GetNamedValue failed", ex->HResult );
1411 }
1412 }
1413
1414 return true;
1415 }
1416 }
1417
1418 return false;
1419}
1420
1421WXM::MultiplayerSessionReference^ DQRNetworkManager::ConvertToWindowsXboxMultiplayerSessionReference(MXSM::MultiplayerSessionReference^ sessionRef )
1422{
1423 return ref new WXM::MultiplayerSessionReference(
1424 sessionRef->SessionName,
1425 sessionRef->ServiceConfigurationId,
1426 sessionRef->SessionTemplateName
1427 );
1428}
1429
1430MXSM::MultiplayerSessionReference^ DQRNetworkManager::ConvertToMicrosoftXboxServicesMultiplayerSessionReference(WXM::MultiplayerSessionReference^ sessionRef )
1431{
1432 return ref new MXSM::MultiplayerSessionReference(
1433 sessionRef->ServiceConfigurationId,
1434 sessionRef->SessionTemplateName,
1435 sessionRef->SessionName
1436 );
1437}
1438
1439// This is called on the client, when new room sync data is received. By comparing this with the existing room sync data,
1440// this method is able to work out who has been added or removed from the game session, and notify the game of these events.
1441void DQRNetworkManager::UpdateRoomSyncPlayers(RoomSyncData *pNewSyncData)
1442{
1443 vector<DQRNetworkPlayer *> tempPlayers;
1444 vector<DQRNetworkPlayer *> newPlayers;
1445
1446 EnterCriticalSection(&m_csRoomSyncData);
1447
1448 // Make temporary vector with all the current players in
1449 for( int j = 0; j < m_roomSyncData.playerCount; j++ )
1450 {
1451 tempPlayers.push_back(m_players[j]);
1452 m_players[j] = NULL;
1453 }
1454
1455 // For each new player, it's either:
1456 // (1) In the temp player array already, so we already knew about it.
1457 // (2) Not in the temp player array already, so its a new player. Need to inform the game.
1458 // And when we are done, anything left in the temporary vector must be a player that left
1459 for( int i = 0; i < pNewSyncData->playerCount; i++ )
1460 {
1461 PlayerSyncData *pNewPlayer = &pNewSyncData->players[i];
1462 bool bAlreadyExisted = false;
1463 for( AUTO_VAR(it, tempPlayers.begin()); it != tempPlayers.end(); it++ )
1464 {
1465 if( pNewPlayer->m_smallId == (*it)->GetSmallId() )
1466 {
1467 m_players[i] = (*it);
1468 it = tempPlayers.erase(it);
1469 bAlreadyExisted = true;
1470 break;
1471 }
1472 }
1473 if( !bAlreadyExisted )
1474 {
1475 // Create the new player, and tell the game etc. about it - the game is now free to send data via this player since it is active
1476 if( i == 0 )
1477 {
1478 // Player 0 is always the host
1479 m_players[i] = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_HOST, false, pNewPlayer->m_channel, pNewPlayer->m_sessionAddress);
1480 }
1481 else
1482 {
1483 if( pNewPlayer->m_sessionAddress == m_XRNS_LocalAddress )
1484 {
1485 m_players[i] = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_LOCAL, false, pNewPlayer->m_channel, pNewPlayer->m_sessionAddress);
1486 }
1487 else
1488 {
1489 m_players[i] = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_REMOTE, false, pNewPlayer->m_channel, pNewPlayer->m_sessionAddress);
1490 }
1491 }
1492
1493 LogCommentFormat(L"Adding new player, index %d - type %d, small Id %d, name %s, xuid %s\n",i,m_players[i]->m_type,pNewPlayer->m_smallId,pNewPlayer->m_name,pNewPlayer->m_XUID);
1494
1495 m_players[i]->SetSmallId(pNewPlayer->m_smallId);
1496 m_players[i]->SetName(pNewPlayer->m_name);
1497 m_players[i]->SetUID(PlayerUID(pNewPlayer->m_XUID));
1498 if (m_players[i]->IsLocal())
1499 {
1500 m_players[i]->SetDisplayName(ProfileManager.GetDisplayName(i));
1501 }
1502
1503 newPlayers.push_back( m_players[i] );
1504 }
1505 }
1506 for( int i = 0; i < m_roomSyncData.playerCount; i++ )
1507 {
1508 delete [] m_roomSyncData.players[i].m_XUID;
1509 }
1510 memcpy(&m_roomSyncData, pNewSyncData, sizeof(m_roomSyncData));
1511
1512 for( int i = 0; i < tempPlayers.size(); i++ )
1513 {
1514 m_listener->HandlePlayerLeaving(tempPlayers[i]);
1515 delete tempPlayers[i];
1516 }
1517 for( int i = 0; i < newPlayers.size(); i++ )
1518 {
1519 m_listener->HandlePlayerJoined(newPlayers[i]); // For clients, this is where we get notified of local and remote players joining
1520 }
1521 LeaveCriticalSection(&m_csRoomSyncData);
1522}
1523
1524// This is called from the host, to add a new player into the room sync data that is then sent out to the clients.
1525bool DQRNetworkManager::AddRoomSyncPlayer(DQRNetworkPlayer *pPlayer, unsigned int sessionAddress, int channel)
1526{
1527 if( m_roomSyncData.playerCount == MAX_ONLINE_PLAYER_COUNT ) return false;
1528
1529 EnterCriticalSection(&m_csRoomSyncData);
1530 // Find the first entry that isn't us, to decide what to sync before. Don't consider entry #0 as this is reserved to indicate the host.
1531 int insertAtIdx = m_roomSyncData.playerCount;
1532 for( int i = 1; i < m_roomSyncData.playerCount; i++ )
1533 {
1534 if( m_roomSyncData.players[i].m_sessionAddress != sessionAddress )
1535 {
1536 insertAtIdx = i;
1537 break;
1538 }
1539 else
1540 {
1541 // Don't add the same local index twice
1542 if( m_roomSyncData.players[i].m_channel == channel )
1543 {
1544 LeaveCriticalSection(&m_csRoomSyncData);
1545 return false;
1546 }
1547 }
1548 }
1549
1550 // Make room for a new entry...
1551 for( int i = m_roomSyncData.playerCount; i > insertAtIdx; i-- )
1552 {
1553 m_roomSyncData.players[i] = m_roomSyncData.players[i-1];
1554 m_players[i] = m_players[i - 1];
1555 }
1556 m_roomSyncData.players[insertAtIdx].m_channel = channel;
1557 m_roomSyncData.players[insertAtIdx].m_sessionAddress = sessionAddress;
1558 int xuidLength = pPlayer->GetUID().toString().length() + 1; // +1 for terminator
1559 m_roomSyncData.players[insertAtIdx].m_XUID = new wchar_t [xuidLength];
1560 wcsncpy(m_roomSyncData.players[insertAtIdx].m_XUID, pPlayer->GetUID().toString().c_str(), xuidLength);
1561 m_roomSyncData.players[insertAtIdx].m_smallId = pPlayer->GetSmallId();
1562 wcscpy_s(m_roomSyncData.players[insertAtIdx].m_name, pPlayer->GetName());
1563 m_players[insertAtIdx] = pPlayer;
1564
1565
1566 m_roomSyncData.playerCount++;
1567
1568 LeaveCriticalSection(&m_csRoomSyncData);
1569 return true;
1570}
1571
1572// This is called from the host to remove players from the room sync data that is sent out to the clients.
1573// This method removes any players sharing a session address, and is used when one machine leaves the network game.
1574void DQRNetworkManager::RemoveRoomSyncPlayersWithSessionAddress(unsigned int sessionAddress)
1575{
1576 EnterCriticalSection(&m_csRoomSyncData);
1577 vector<DQRNetworkPlayer *> removedPlayers;
1578 int iWriteIdx = 0;
1579 for( int i = 0; i < m_roomSyncData.playerCount; i++ )
1580 {
1581 if( m_roomSyncData.players[i].m_sessionAddress == sessionAddress )
1582 {
1583 removedPlayers.push_back(m_players[i]);
1584 delete [] m_roomSyncData.players[i].m_XUID;
1585 }
1586 else
1587 {
1588 m_roomSyncData.players[iWriteIdx] = m_roomSyncData.players[i];
1589 m_players[iWriteIdx] = m_players[i];
1590 iWriteIdx++;
1591 }
1592 }
1593 m_roomSyncData.playerCount = iWriteIdx;
1594
1595 for( int i = 0; i < removedPlayers.size(); i++ )
1596 {
1597 m_listener->HandlePlayerLeaving(removedPlayers[i]);
1598 delete removedPlayers[i];
1599 memset(&m_roomSyncData.players[m_roomSyncData.playerCount + i], 0, sizeof(PlayerSyncData));
1600 m_players[m_roomSyncData.playerCount + i] = NULL;
1601 }
1602 LeaveCriticalSection(&m_csRoomSyncData);
1603}
1604
1605// This is called from the host a remove player from the room sync data that is sent out to the clients.
1606void DQRNetworkManager::RemoveRoomSyncPlayer(DQRNetworkPlayer *pPlayer)
1607{
1608 vector<DQRNetworkPlayer *> removedPlayers;
1609 int iWriteIdx = 0;
1610 for( int i = 0; i < m_roomSyncData.playerCount; i++ )
1611 {
1612 if( m_players[i] == pPlayer )
1613 {
1614 removedPlayers.push_back(m_players[i]);
1615 delete [] m_roomSyncData.players[i].m_XUID;
1616 }
1617 else
1618 {
1619 m_roomSyncData.players[iWriteIdx] = m_roomSyncData.players[i];
1620 m_players[iWriteIdx] = m_players[i];
1621 iWriteIdx++;
1622 }
1623 }
1624 m_roomSyncData.playerCount = iWriteIdx;
1625
1626 for( int i = 0; i < removedPlayers.size(); i++ )
1627 {
1628 m_listener->HandlePlayerLeaving(removedPlayers[i]);
1629 delete removedPlayers[i];
1630 memset(&m_roomSyncData.players[m_roomSyncData.playerCount + i], 0, sizeof(PlayerSyncData));
1631 m_players[m_roomSyncData.playerCount + i] = NULL;
1632 }
1633}
1634
1635// Broadcast RoomSyncData to all clients (host only)
1636void DQRNetworkManager::SendRoomSyncInfo()
1637{
1638 if( m_isOfflineGame ) return;
1639
1640 EnterCriticalSection(&m_csRoomSyncData);
1641 // Calculate the size of data we are going to be sending. This is composed of:
1642 // (1) 2 byte general header
1643 // (2) A single byte internal data tag
1644 // (3) An unsigned int encoding the size of the combined size of all the strings in stage (5)
1645 // (4) The RoomSyncData structure itself
1646 // (5) A wchar NULL terminated string for every active player to encode the XUID
1647 unsigned int xuidBytes = 0;
1648 for( int i = 0 ; i < m_roomSyncData.playerCount; i++ )
1649 {
1650 LogCommentFormat(L"Sending room sync info for player with XUID %s",m_roomSyncData.players[i].m_XUID);
1651 xuidBytes += ( wcslen(m_roomSyncData.players[i].m_XUID) + 1 ) * sizeof(wchar_t); // 2 bytes per character, including 0 terminator
1652 }
1653
1654 unsigned int internalBytes = 1 + 4 + sizeof(RoomSyncData) + xuidBytes;
1655 unsigned int totalBytes = 2 + internalBytes;
1656
1657 unsigned char *data = new unsigned char[totalBytes];
1658
1659 uint32_t sizeHigh = internalBytes >> 8;
1660 uint32_t sizeLow = internalBytes & 0xff;
1661
1662 data[0] = 0x80 | sizeHigh; // Header - flag as internal data (0x80), sending
1663 data[1] = sizeLow; // Data following has the a single byte to say what it is, followed by the room sync data itself
1664 data[2] = DQR_INTERNAL_PLAYER_TABLE;
1665
1666 memcpy(data + 3, &xuidBytes, 4);
1667 memcpy(data + 7, &m_roomSyncData, sizeof(RoomSyncData));
1668 unsigned char *pucCurr = data + 7 + sizeof(RoomSyncData);
1669
1670 for( int i = 0 ; i < m_roomSyncData.playerCount; i++ )
1671 {
1672 unsigned int thisBytes = ( wcslen(m_roomSyncData.players[i].m_XUID) + 1 ) * sizeof(wchar_t);
1673 memcpy(pucCurr, m_roomSyncData.players[i].m_XUID, thisBytes);
1674 pucCurr += thisBytes;
1675 }
1676
1677 SendBytesRaw(-1, data, totalBytes, true);
1678
1679 delete [] data;
1680 LeaveCriticalSection(&m_csRoomSyncData);
1681}
1682
1683// Broadcast the fact that joining a particular XUID has failed (host only)
1684void DQRNetworkManager::SendAddPlayerFailed(Platform::String^ xuid)
1685{
1686 if( m_isOfflineGame ) return;
1687
1688 // Calculate the size of data we are going to be sending. This is composed of:
1689 // (1) 2 byte general header
1690 // (2) A single byte internal data tag
1691 // (3) An unsigned int encoding the size size of the string
1692 // (5) A wchar NULL terminated string storing the xuid of the player which has failed to join
1693
1694 unsigned int xuidBytes = sizeof(wchar_t) * ( xuid->Length() + 1 );
1695
1696 unsigned int internalBytes = 1 + 4 + xuidBytes;
1697 unsigned int totalBytes = 2 + internalBytes;
1698
1699 unsigned char *data = new unsigned char[totalBytes];
1700
1701 uint32_t sizeHigh = internalBytes >> 8;
1702 uint32_t sizeLow = internalBytes & 0xff;
1703
1704 data[0] = 0x80 | sizeHigh; // Header - flag as internal data (0x80), sending
1705 data[1] = sizeLow; // Data following has the a single byte to say what it is, followed by the room sync data itself
1706 data[2] = DQR_INTERNAL_ADD_PLAYER_FAILED;
1707
1708 memcpy(data + 3, &xuidBytes, 4);
1709 memcpy(data + 7, xuid->Data(), xuidBytes);
1710
1711 SendBytesRaw(-1, data, totalBytes, true);
1712
1713 delete [] data;
1714}
1715
1716// This method is used by the client to send a small Id to the host. When the host receives this on a particular communication channel,
1717// it then knows the association between communication channel & small Id, and that a player is actitve.
1718void DQRNetworkManager::SendSmallId(bool reliableAndSequential, int playerMask)
1719{
1720 // Send data with small Id setting mode - see full comment in DQRNetworkManagerEventHandlers::DataReceivedHandler
1721 BYTE data[8];
1722 data[0] = 0x80; // Send 6 bytes, internal mode. Send on channel 0 but this is ignored.
1723 data[1] = 6;
1724 data[2] = DQR_INTERNAL_ASSIGN_SMALL_IDS; // Internal data type
1725 data[3] = playerMask; // Actual id
1726 data[4] = 0;
1727 data[5] = 0;
1728 data[6] = 0;
1729 data[7] = 0;
1730
1731 bool bError = false;
1732 for( int j = 0; j < MAX_LOCAL_PLAYER_COUNT; j++ )
1733 {
1734 if( playerMask & ( 1 << j ) )
1735 {
1736 bool bFound = false;
1737 for( unsigned int i = 0; i < m_multiplayerSession->Members->Size; i++ )
1738 {
1739 MXSM::MultiplayerSessionMember^ member = m_multiplayerSession->Members->GetAt(i);
1740
1741 if( member->XboxUserId == m_joinSessionXUIDs[j] )
1742 {
1743 BYTE smallId = 0;
1744 try
1745 {
1746 Windows::Data::Json::JsonObject^ customConstant = Windows::Data::Json::JsonObject::Parse(member->MemberCustomConstantsJson);
1747 Windows::Data::Json::JsonValue^ customValue = customConstant->GetNamedValue(L"smallId");
1748 smallId = (BYTE)(customValue->GetNumber());
1749 bFound = true;
1750 }
1751 catch (Platform::COMException^ ex)
1752 {
1753 bError = true;
1754 LogCommentWithError( L"Custom constant Parse/GetNamedValue failed", ex->HResult );
1755 }
1756
1757 m_channelFromSmallId[smallId] = j;
1758
1759 data[ 4 + j ] = smallId;
1760 m_connectionInfoClient.m_smallId[ j ] = smallId;
1761 LogCommentFormat( L"SendSmallId, channel %d, id %d\n", m_channelFromSmallId[smallId], smallId);
1762 }
1763 }
1764 if( !bFound )
1765 {
1766 bError = true;
1767 }
1768 }
1769 }
1770
1771 assert(bError == false );
1772
1773 SendBytesRaw(0, data, 8, reliableAndSequential);
1774}
1775
1776// This is sent by the client to the host, acting to undo a previous SendSmallId call
1777void DQRNetworkManager::SendUnassignSmallId(int playerIndex)
1778{
1779 LogCommentFormat( L"SendUnassignSmallId, channel %d\n", playerIndex);
1780 // Send data with small Id setting mode - see full comment in DQRNetworkManagerEventHandlers::DataReceivedHandler
1781 BYTE data[4];
1782 data[0] = 0x80 | ( playerIndex << 5 ); // Send 1 byte, internal mode
1783 data[1] = 1;
1784 data[2] = DQR_INTERNAL_UNASSIGN_SMALL_ID; // Internal data type
1785
1786 SendBytesRaw(0, data, 3, true);
1787}
1788
1789// This method gets the player index within the session document for a particular small Id.
1790int DQRNetworkManager::GetSessionIndexForSmallId(unsigned char smallId)
1791{
1792 for( unsigned int i = 0; i < m_multiplayerSession->Members->Size; i++ )
1793 {
1794 MXSM::MultiplayerSessionMember^ member = m_multiplayerSession->Members->GetAt(i);
1795 BYTE smallIdMember = 0;
1796 try
1797 {
1798 Windows::Data::Json::JsonObject^ customConstant = Windows::Data::Json::JsonObject::Parse(member->MemberCustomConstantsJson);
1799 Windows::Data::Json::JsonValue^ customValue = customConstant->GetNamedValue(L"smallId");
1800 smallIdMember = (BYTE)(customValue->GetNumber());
1801 }
1802 catch (Platform::COMException^ ex)
1803 {
1804 LogCommentWithError( L"Custom constant Parse/GetNamedValue failed", ex->HResult );
1805 }
1806 if( smallIdMember == smallId )
1807 {
1808 return i;
1809 }
1810 }
1811 return -1;
1812}
1813
1814// This method gets the player index and small id for the host, which is sent with a small id that has 256 added to it
1815int DQRNetworkManager::GetSessionIndexAndSmallIdForHost(unsigned char *smallId)
1816{
1817 for( unsigned int i = 0; i < m_multiplayerSession->Members->Size; i++ )
1818 {
1819 MXSM::MultiplayerSessionMember^ member = m_multiplayerSession->Members->GetAt(i);
1820 int smallIdMember = 0;
1821 try
1822 {
1823 Windows::Data::Json::JsonObject^ customConstant = Windows::Data::Json::JsonObject::Parse(member->MemberCustomConstantsJson);
1824 Windows::Data::Json::JsonValue^ customValue = customConstant->GetNamedValue(L"smallId");
1825 smallIdMember = customValue->GetNumber();
1826 }
1827 catch (Platform::COMException^ ex)
1828 {
1829 LogCommentWithError( L"Custom constant Parse/GetNamedValue failed", ex->HResult );
1830 }
1831 if( smallIdMember > 255 )
1832 {
1833 *smallId = (BYTE)(smallIdMember);
1834 return i;
1835 }
1836 }
1837 return -1;
1838}
1839
1840// Connection info is used to store the current state of a communcation endpoint, on both host & client
1841DQRConnectionInfo::DQRConnectionInfo()
1842{
1843 Reset();
1844}
1845
1846void DQRConnectionInfo::Reset()
1847{
1848 m_currentChannel = 0;
1849 m_internalFlag = false;
1850 m_bytesRemaining = 0;
1851 m_internalDataState = ConnectionState_InternalHeaderByte;
1852 for( int i = 0; i < 4; i++ )
1853 {
1854 m_smallId[i] = 0;
1855 m_channelActive[i] = false;
1856 }
1857 m_state = ConnectionState_HeaderByte0;
1858 m_initialPacketReceived = false;
1859}
1860
1861// This method allocates the next available small id, returning it as a json formatted named value so that it can be inserted as custom data in the session document
1862Platform::String^ DQRNetworkManager::GetNextSmallIdAsJsonString()
1863{
1864 Windows::Data::Json::JsonObject^ customConstant = ref new Windows::Data::Json::JsonObject();
1865
1866 // The host sends it small Id with 256 added to it, so we can determine which player is the host easily at the client side
1867 int smallIdToSend = m_currentSmallId;
1868 if( smallIdToSend == m_hostSmallId )
1869 {
1870 smallIdToSend += 256;
1871 }
1872 customConstant->Insert(L"smallId", Windows::Data::Json::JsonValue::CreateNumberValue( smallIdToSend ));
1873 m_sessionAddressFromSmallId[m_currentSmallId++] = 0;
1874
1875 return customConstant->Stringify();
1876}
1877
1878int DQRNetworkManager::_HostGameThreadProc( void* lpParameter )
1879{
1880 DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter;
1881 return pDQR->HostGameThreadProc();
1882}
1883
1884// This is the main asynchronous method that is called when hosting a game (initiated by calling ::createAndJoinSession). This is called for
1885// both online & offline games, and it must:
1886// (1) Create a new multiplayer session document, with active players for all local players starting the game
1887// (2) Create a new game party, with matching players in it (possibly clearing any existing party)
1888// (3) Get a device token for the host & assign to the session
1889// (4) Initialise the room sync data, and inform the game of all local players joining at this stage
1890
1891int DQRNetworkManager::HostGameThreadProc()
1892{
1893 Platform::String^ sessionName;
1894
1895 // Get primary user that we are going to create the session with, and create an xbox live context from it
1896
1897 WXS::User^ primaryUser = ProfileManager.GetUser(0);
1898 m_primaryUser = primaryUser;
1899 m_primaryUserXboxLiveContext = nullptr;
1900 if( primaryUser == nullptr )
1901 {
1902 SetState(DNM_INT_STATE_HOSTING_FAILED);
1903 return 0;
1904 }
1905
1906 int localSessionAddress = 0;
1907
1908 if( !m_isOfflineGame )
1909 {
1910 MXS::XboxLiveContext^ primaryUserXBLContext = ref new MXS::XboxLiveContext(primaryUser);
1911 m_primaryUserXboxLiveContext = primaryUserXBLContext;
1912 if( primaryUserXBLContext == nullptr )
1913 {
1914 SetState(DNM_INT_STATE_HOSTING_FAILED);
1915 return 0;
1916 }
1917
1918 EnableDebugXBLContext(m_primaryUserXboxLiveContext);
1919
1920 // Get a globally unique identifier to use as our session name that we are going to create
1921 GUID sessionNameGUID;
1922 CoCreateGuid( &sessionNameGUID );
1923 Platform::Guid sessionGuidName = Platform::Guid( sessionNameGUID );
1924 sessionName = RemoveBracesFromGuidString( sessionGuidName.ToString() );
1925
1926 MXSM::MultiplayerSession^ session = nullptr;
1927 // Actually create the session (locally), using the primary player's context
1928 try
1929 {
1930 session = ref new MXSM::MultiplayerSession( primaryUserXBLContext,
1931 ref new MXSM::MultiplayerSessionReference( SERVICE_CONFIG_ID, MATCH_SESSION_TEMPLATE_NAME, sessionName ),
1932 0, // this means that it will use the maxMembers specified in the session template.
1933 false,
1934 MXSM::MultiplayerSessionVisibility::Open,
1935 nullptr,
1936 nullptr
1937 );
1938 }
1939 catch (Platform::COMException^ ex)
1940 {
1941 SetState(DNM_INT_STATE_HOSTING_FAILED);
1942 return 0;
1943 }
1944
1945 // Set custom property with the game session data
1946 session->SetSessionCustomPropertyJson( L"GameSessionData", Windows::Data::Json::JsonValue::CreateStringValue( base64_encode(m_customSessionData, m_customSessionDataSize ) )->Stringify() );
1947
1948 // More custom data, for the XUIDs of the players to match our room sync data. This isn't itself set up at t:his point but we know what is going in it.
1949 Windows::Data::Json::JsonArray ^currentXuidArray = ref new Windows::Data::Json::JsonArray();
1950 for( int i = 0 ; i < MAX_LOCAL_PLAYER_COUNT; i++ )
1951 {
1952 if( m_currentUserMask & ( 1 << i ) )
1953 {
1954 WXS::User^ newUser = ProfileManager.GetUser(i);
1955 if( newUser != nullptr )
1956 {
1957 currentXuidArray->Append( Windows::Data::Json::JsonValue::CreateStringValue( newUser->XboxUserId ) );
1958 }
1959 else
1960 {
1961 SetState(DNM_INT_STATE_HOSTING_FAILED);
1962 return 0;
1963 }
1964 }
1965 }
1966 session->SetSessionCustomPropertyJson( L"RoomSyncData", currentXuidArray->Stringify() );
1967
1968 // Join session with the primary user for whom the session was created (via their xbox live context)
1969 // user to store the small Id
1970 m_hostSmallId = m_currentSmallId;
1971 m_sessionAddressFromSmallId[m_hostSmallId] = 0;
1972
1973 session->Join( GetNextSmallIdAsJsonString(), true );
1974 session->SetCurrentUserStatus( MXSM::MultiplayerSessionMemberStatus::Active );
1975
1976 // Get device ID for current user & set in the session
1977 Platform::String^ secureDeviceAddress = WXN::SecureDeviceAddress::GetLocal()->GetBase64String();
1978 session->SetCurrentUserSecureDeviceAddressBase64( secureDeviceAddress );
1979 LogComment(L"Setting host secure device: " + secureDeviceAddress + L"\n");
1980
1981 // If there is a party currently, then remove all our local players from it so that we can start our own one
1982 m_partyController->RefreshPartyView();
1983 WXM::PartyView^ partyView = m_partyController->GetPartyView();
1984
1985 if( partyView != nullptr )
1986 {
1987 m_partyController->RemoveLocalUsersFromParty(primaryUser);
1988 }
1989
1990 m_partyController->AddLocalUsersToParty(m_currentUserMask, primaryUser);
1991 partyView = m_partyController->GetPartyView();
1992
1993 // If there is no party by this stage, then we can't proceed.
1994 if( partyView == nullptr )
1995 {
1996 SetState(DNM_INT_STATE_HOSTING_FAILED);
1997 return 0;
1998 }
1999 m_partyController->SetJoinability(m_listener->IsSessionJoinable());
2000
2001 // Add reservations for anyone in the party, who isn't the primary player. Just adding local players for now, but perhaps this should add
2002 // other party members at this stage?
2003 for ( WXM::PartyMember^ member : partyView->Members )
2004 {
2005 if( member->IsLocal )
2006 {
2007 if ( _wcsicmp(primaryUser->XboxUserId->Data(), member->XboxUserId->Data()) != 0)
2008 {
2009 session->AddMemberReservation( member->XboxUserId, GetNextSmallIdAsJsonString(), true );
2010 LogCommentFormat( L"Added %s to session\n", member->XboxUserId->Data());
2011 }
2012 }
2013 }
2014
2015 // This is everything now set up locally as we want to start the session. Now attempt to write the session data to the server - will return a valid new session object if we succeeeded.
2016 HRESULT hr = S_OK;
2017 session = WriteSessionHelper( primaryUserXBLContext, session, MXSM::MultiplayerSessionWriteMode::UpdateOrCreateNew, hr );
2018
2019 // It is important to set the session as soon as we have written it, so that if we get any incoming events (such as the game session ready one) then we will be aware that we are actually already in the session
2020 HandleSessionChange(session);
2021
2022 // If this was successful, then do further set up of the session
2023 if( session != nullptr )
2024 {
2025 // Get reference to the current user within the session
2026 MXSM::MultiplayerSessionMember^ hostMember = GetUserMemberInSession(primaryUser, session);
2027
2028 // Set the device token of the host from this user (since we're hosting)
2029 session->SetHostDeviceToken( hostMember->DeviceToken );
2030
2031 m_partyController->RegisterGamePlayersChangedEventHandler();
2032
2033 // Update session on the server
2034 HRESULT hr = S_OK;
2035 session = WriteSessionHelper( primaryUserXBLContext, session, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr );
2036
2037 if ( FAILED(hr) )
2038 {
2039 app.DebugPrintf("Failed setting host device token");
2040
2041 SetState(DNM_INT_STATE_HOSTING_FAILED);
2042 return 0;
2043 }
2044
2045 // Convert the session reference (in Microsoft::Xbox::Services::Multiplayer namespace) to Windows::Xbox::Multiplayer names space so we can use in the party system
2046 WXM::MultiplayerSessionReference^ winSessionRef = ConvertToWindowsXboxMultiplayerSessionReference(session->SessionReference);
2047
2048 // Register this multiplayer session as the current session for the party
2049 auto registerSessionAsync = WXM::Party::RegisterGameSessionAsync(primaryUser, winSessionRef);
2050 create_task(registerSessionAsync).then([this](task<void> t)
2051 {
2052 try
2053 {
2054 t.get(); // if t.get() didn't throw, it succeeded
2055 }
2056 catch (Platform::COMException^ ex)
2057 {
2058 LogCommentWithError( L"RegisterGameSessionAsync failed", ex->HResult );
2059
2060 SetState(DNM_INT_STATE_HOSTING_FAILED);
2061 }
2062 })
2063 .wait();
2064 if( m_state == DNM_INT_STATE_HOSTING_FAILED) return 0;
2065
2066 m_partyController->RefreshPartyView();
2067
2068 // We've now created the session with our local player in it, and reserved slots for everyone else in the party. We've also set the party to say that
2069 // the current game the party are playing is this session. We can now request that the reserved player's be pulled into the game. For people not currently running the
2070 // game, this will ask them if they want to start playing, and for people already in the title it will send an even to say that the game is ready.
2071 auto pullPlayersAsync = WXM::Party::PullReservedPlayersAsync(primaryUser, winSessionRef);
2072 create_task(pullPlayersAsync).then([this](task<void> t)
2073 {
2074 try
2075 {
2076 t.get(); // if t.get() didn't throw, it succeeded
2077 }
2078 catch (Platform::COMException^ ex)
2079 {
2080 LogCommentWithError( L"PullReservedPlayersAsync failed", ex->HResult );
2081
2082 // SetState(DNM_INT_STATE_HOSTING_FAILED); // This does seem to fail (harmlessly?) with a code of 0x87cc0008 sometimes
2083 }
2084 })
2085 .wait();
2086 if( m_state == DNM_INT_STATE_HOSTING_FAILED) return 0;
2087
2088 sockaddr_in6 localSocketAddressStorage;
2089
2090 ZeroMemory(&localSocketAddressStorage, sizeof(localSocketAddressStorage));
2091
2092 localSocketAddressStorage.sin6_family = AF_INET6;
2093 localSocketAddressStorage.sin6_port = htons(m_associationTemplate->AcceptorSocketDescription->BoundPortRangeLower);
2094
2095 memcpy(&localSocketAddressStorage.sin6_addr, &in6addr_any, sizeof(in6addr_any));
2096
2097 m_localSocketAddress = Platform::ArrayReference<BYTE>(reinterpret_cast<BYTE*>(&localSocketAddressStorage), sizeof(localSocketAddressStorage));
2098
2099 // This shouldn't ever happen, but seems worth checking that we don't have a pre-existing session in case there's any way to get here with one already running
2100 if( m_XRNS_Session )
2101 {
2102 RTS_Terminate();
2103 do
2104 {
2105 Sleep(20);
2106 } while ( m_XRNS_Session != nullptr );
2107 }
2108
2109 // Start a new session (this actually happens in the RTS processing thread), and wait until it exists
2110 RTS_StartHost();
2111 do
2112 {
2113 Sleep(20);
2114 } while ( m_XRNS_Session == nullptr );
2115
2116 // Set any other local players that we added as reserved in the session, to be active. This must be done by getting and writing the multiplayer session
2117 // document from each player's xbox live context in turn
2118
2119 for( int i = 1; i < 4; i++ )
2120 {
2121 if( m_currentUserMask & ( 1 << i ) )
2122 {
2123 WXS::User^ extraUser = ProfileManager.GetUser(i);
2124 if( extraUser == nullptr )
2125 {
2126 SetState(DNM_INT_STATE_HOSTING_FAILED);
2127 return 0;
2128 }
2129 MXS::XboxLiveContext^ extraUserXBLContext = ref new MXS::XboxLiveContext(extraUser);
2130 if( extraUserXBLContext == nullptr )
2131 {
2132 SetState(DNM_INT_STATE_HOSTING_FAILED);
2133 return 0;
2134 }
2135
2136 auto asyncOp = extraUserXBLContext->MultiplayerService->GetCurrentSessionAsync( session->SessionReference );
2137 create_task(asyncOp).then([this, extraUserXBLContext, &session] (task<MXSM::MultiplayerSession^> t)
2138 {
2139 try
2140 {
2141 MXSM::MultiplayerSession^ currentSession = t.get();
2142
2143 currentSession->SetCurrentUserStatus(MXSM::MultiplayerSessionMemberStatus::Active);
2144 HRESULT hr = S_OK;
2145 session = WriteSessionHelper(extraUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr);
2146 }
2147 catch ( Platform::COMException^ ex )
2148 {
2149 }
2150 }).wait();
2151 }
2152 }
2153
2154 HandleSessionChange(session);
2155 }
2156 else
2157 {
2158 app.DebugPrintf("Error creating session");
2159 SetState(DNM_INT_STATE_HOSTING_FAILED);
2160 return 0;
2161 }
2162 m_XRNS_LocalAddress = m_XRNS_Session->LocalSessionAddress;
2163 m_XRNS_OldestAddress = m_XRNS_Session->OldestSessionAddress;
2164 localSessionAddress = m_XRNS_Session->LocalSessionAddress;
2165 }
2166 else
2167 {
2168 m_hostSmallId = m_currentSmallId;
2169 // Offline game - get small id incremented to the same value that it would have ended up with
2170 for( int i = 0; i < 4; i++ )
2171 {
2172 if( m_currentUserMask & ( 1 << i ) )
2173 {
2174 m_currentSmallId++;
2175 }
2176 }
2177 }
2178
2179 // At this stage, set the local players up. We know we'll have created these with smallIds from m_hostSmallId (for the host) to a max of m_hostSmallId+3 for the other local players
2180 int smallId = m_hostSmallId;
2181 for( int i = 0; i < 4; i++ )
2182 {
2183 // If user is present in mask and currently signed in
2184 if( m_currentUserMask & ( 1 << i ) && ProfileManager.IsSignedIn(i))
2185 {
2186 auto user = ProfileManager.GetUser(i);
2187 wstring displayName = ProfileManager.GetDisplayName(i);
2188
2189 DQRNetworkPlayer* pPlayer = new DQRNetworkPlayer(this, ( ( smallId == m_hostSmallId ) ? DQRNetworkPlayer::DNP_TYPE_HOST : DQRNetworkPlayer::DNP_TYPE_LOCAL ), true, i, localSessionAddress);
2190 pPlayer->SetSmallId(smallId);
2191 pPlayer->SetName(user->DisplayInfo->Gamertag->Data());
2192 pPlayer->SetDisplayName(displayName);
2193 pPlayer->SetUID(PlayerUID(user->XboxUserId->Data()));
2194
2195 AddRoomSyncPlayer( pPlayer, localSessionAddress, i);
2196
2197 m_listener->HandlePlayerJoined(pPlayer);
2198
2199 smallId++;
2200 }
2201 }
2202
2203 SetState(DNM_INT_STATE_HOSTING_WAITING_TO_PLAY);
2204
2205 return 0;
2206}
2207
2208int DQRNetworkManager::_LeaveRoomThreadProc( void* lpParameter )
2209{
2210
2211 DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter;
2212 return pDQR->LeaveRoomThreadProc();
2213}
2214
2215int DQRNetworkManager::LeaveRoomThreadProc()
2216{
2217 WXS::User^ primaryUser = ProfileManager.GetUser(0, true);
2218
2219 LogComment(L"LeaveRoomThreadProc");
2220
2221 if( !m_isOfflineGame && m_multiplayerSession != nullptr )
2222 {
2223 // If we are hosting, then we need to disassociate the gaming session from the party. We also need to make sure that we don't subscribe to gameplayer events anymore
2224 // or else if we subsequently become a client in another game, things will get confused
2225 if( m_isHosting )
2226 {
2227 m_partyController->DisassociateSessionFromParty( m_multiplayerSession->SessionReference );
2228
2229 m_partyController->UnregisterGamePlayersEventHandler();
2230 }
2231
2232 // Remove local players from the party
2233 m_partyController->RemoveLocalUsersFromParty(primaryUser, m_currentUserMask, m_multiplayerSession->SessionReference);
2234
2235 // Request RTS to be terminated
2236 RTS_Terminate();
2237
2238 // Now leave the game session. We need to do this for each player in turn, writing each time
2239 bool bError = false;
2240 for( int i = 0; i < 4; i++ )
2241 {
2242 if( m_currentUserMask & ( 1 << i ) )
2243 {
2244 LogCommentFormat(L"DQRNetworkManager::LeaveRoomThreadProc: Attempting to remove player %d from session",i);
2245 WXS::User^ leavingUser = ProfileManager.GetUser(i);
2246 if( leavingUser == nullptr )
2247 {
2248 bError = true;
2249 continue;
2250 }
2251 if( m_chat )
2252 {
2253 m_chat->RemoveLocalUser(leavingUser);
2254 }
2255
2256 MXS::XboxLiveContext^ leavingUserXBLContext = ref new MXS::XboxLiveContext(leavingUser);
2257 if( leavingUserXBLContext == nullptr )
2258 {
2259 bError = true;
2260 continue;
2261 }
2262
2263 auto asyncOp = leavingUserXBLContext->MultiplayerService->GetCurrentSessionAsync( m_multiplayerSession->SessionReference );
2264 create_task(asyncOp).then([this, leavingUserXBLContext,&bError] (task<MXSM::MultiplayerSession^> t)
2265 {
2266 try
2267 {
2268 MXSM::MultiplayerSession^ currentSession = t.get();
2269
2270 if (currentSession != nullptr)
2271 {
2272 currentSession->Leave();
2273 HRESULT hr = S_OK;
2274 WriteSessionHelper(leavingUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr);
2275 }
2276 else
2277 {
2278 // Specific error case where session is gone, this generally happens if all players have left (e.g. party of one and player signs out)
2279 app.DebugPrintf("DQRNetworkManager::LeaveRoomThreadProc: Error removing a player from the session, session was null (user probably signed out)");
2280 }
2281 }
2282 catch ( Platform::COMException^ ex )
2283 {
2284 bError = true;
2285 }
2286 }).wait();
2287 }
2288 }
2289
2290 if ( bError )
2291 {
2292 app.DebugPrintf("DQRNetworkManager::LeaveRoomThreadProc: Error removing a player from the session");
2293 assert(true);
2294 }
2295 }
2296
2297 ProfileManager.CompleteDeferredSignouts();
2298 app.DebugPrintf("DQRNetworkManager::LeaveRoomThreadProc: Completing deferred sign out");
2299 ProfileManager.SetDeferredSignoutEnabled(false);
2300
2301 m_multiplayerSession = nullptr;
2302 m_currentUserMask = 0;
2303 m_joinSessionUserMask = 0;
2304 UnflagInvitedToFullSession();
2305
2306 SetState(DNM_INT_STATE_ENDING);
2307
2308 return 0;
2309}
2310
2311int DQRNetworkManager::_TidyUpJoinThreadProc( void* lpParameter )
2312{
2313
2314 DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter;
2315 return pDQR->TidyUpJoinThreadProc();
2316}
2317
2318// This method is called when joining the game fails in some situations, ie when we attempted to join with a
2319// set of players and not all managed to get into the session. Tidies things up by removing any players that
2320// Did get into the session, and removing any players we added to the party.
2321int DQRNetworkManager::TidyUpJoinThreadProc()
2322{
2323 WXS::User^ primaryUser = ProfileManager.GetUser(0);
2324
2325 LogComment(L"TidyUpJoinThreadProc");
2326
2327 if( primaryUser != nullptr )
2328 {
2329 // Remove any local users at all who are in the party - but first get a session reference from the party whilst we still have it
2330 m_partyController->RefreshPartyView();
2331 MXSM::MultiplayerSessionReference ^sessionRef = m_partyController->GetGamePartySessionReference();
2332 m_partyController->RemoveLocalUsersFromParty(primaryUser);
2333
2334 if( sessionRef != nullptr )
2335 {
2336 // Now leave the game session. We need to do this for each player in turn, writing each time. Consider that any of the joining
2337 // members *may* have a slot (reserved or active) depending on how far progressed the joining got.
2338 bool bError = false;
2339
2340 // We can fail to join at various points, and in at least one case (if it is down to RUDP unreliable packets timing out) then we don't have m_joinSessionUserMask bits set any more,
2341 // but we Do have m_currentUserMask set. Any of these should be considered users we should be attempting to remove from the session.
2342 int removeSessionMask = m_joinSessionUserMask | m_currentUserMask;
2343 for( int i = 0; i < 4; i++ )
2344 {
2345 if( removeSessionMask & ( 1 << i ) )
2346 {
2347 LogCommentFormat(L"Attempting to remove player %d from session",i);
2348 WXS::User^ leavingUser = ProfileManager.GetUser(i);
2349 if( leavingUser == nullptr )
2350 {
2351 bError = true;
2352 continue;
2353 }
2354 MXS::XboxLiveContext^ leavingUserXBLContext = ref new MXS::XboxLiveContext(leavingUser);
2355 if( leavingUserXBLContext == nullptr )
2356 {
2357 bError = true;
2358 continue;
2359 }
2360
2361 EnableDebugXBLContext(leavingUserXBLContext);
2362
2363 auto asyncOp = leavingUserXBLContext->MultiplayerService->GetCurrentSessionAsync( sessionRef );
2364 create_task(asyncOp).then([this, leavingUser, leavingUserXBLContext,&bError] (task<MXSM::MultiplayerSession^> t)
2365 {
2366 try
2367 {
2368 MXSM::MultiplayerSession^ currentSession = t.get();
2369
2370 if( currentSession )
2371 {
2372 bool bFound = false;
2373 for( int i = 0; i < currentSession->Members->Size; i++ )
2374 {
2375 if( currentSession->Members->GetAt(i)->XboxUserId == leavingUser->XboxUserId )
2376 {
2377 bFound = true;
2378 break;
2379 }
2380 }
2381
2382 if( bFound )
2383 {
2384 currentSession->Leave();
2385 HRESULT hr = S_OK;
2386 WriteSessionHelper(leavingUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr);
2387 }
2388 }
2389 }
2390 catch ( Platform::COMException^ ex )
2391 {
2392 bError = true;
2393 }
2394 }).wait();
2395 }
2396 }
2397
2398 if ( bError )
2399 {
2400 app.DebugPrintf("Error removing a player from the session");
2401 assert(true);
2402 }
2403 }
2404 }
2405
2406 m_multiplayerSession = nullptr;
2407 m_currentUserMask = 0;
2408 m_joinSessionUserMask = 0;
2409
2410 // Fixing up things from joining needs to go straight from joining -> idle to be properly detected as a failed join by the higher-level networking systems
2411 SetState(DNM_INT_STATE_IDLE);
2412
2413 return 0;
2414}
2415
2416int DQRNetworkManager::_UpdateCustomSessionDataThreadProc( void* lpParameter )
2417{
2418
2419 DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter;
2420 return pDQR->UpdateCustomSessionDataThreadProc();
2421}
2422
2423// This method is called to updat the custom session data associated with the multiplayer session. This is done only on the host. The custom data is specified
2424// when we create the game session (when calling CreateAndJoinSession) as a region of memory so that this layer doesn't have to be concerned with what it
2425// represents, however we use it to synchronise a GameSessionData structure containing details of the current game session. This data is base 64 encoded
2426// and then put in the session document as a custom value as a json string name/value pair.
2427int DQRNetworkManager::UpdateCustomSessionDataThreadProc()
2428{
2429 LogComment(L"Starting thread to update custom data");
2430 WXS::User^ primaryUser = ProfileManager.GetUser(0);
2431
2432 if( primaryUser == nullptr )
2433 {
2434 return 0;
2435 }
2436 MXS::XboxLiveContext^ primaryUserXBLContext = ref new MXS::XboxLiveContext(primaryUser);
2437 if( primaryUserXBLContext == nullptr )
2438 {
2439 return 0;
2440 }
2441
2442 MXSM::MultiplayerSession^ session = nullptr;
2443
2444 auto multiplayerSessionAsync = primaryUserXBLContext->MultiplayerService->GetCurrentSessionAsync( m_multiplayerSession->SessionReference );
2445 create_task(multiplayerSessionAsync).then([&session,this](task<MXSM::MultiplayerSession^> t)
2446 {
2447 try
2448 {
2449 session = t.get(); // if t.get() didn't throw, it succeeded
2450 }
2451 catch (Platform::COMException^ ex)
2452 {
2453 LogCommentWithError( L"MultiplayerSession failed", ex->HResult );
2454 }
2455 })
2456 .wait();
2457
2458 if( session != nullptr )
2459 {
2460 // Set custom property with the game session data
2461 session->SetSessionCustomPropertyJson( L"GameSessionData", Windows::Data::Json::JsonValue::CreateStringValue( base64_encode(m_customSessionData, m_customSessionDataSize ) )->Stringify() );
2462
2463 // Update XUIDs from room sync data which we also have as custom data
2464 EnterCriticalSection(&m_csRoomSyncData);
2465 Windows::Data::Json::JsonArray ^currentXuidArray = ref new Windows::Data::Json::JsonArray();
2466 for( int i = 0 ; i < m_roomSyncData.playerCount; i++ )
2467 {
2468 currentXuidArray->Append( Windows::Data::Json::JsonValue::CreateStringValue( ref new Platform::String(m_roomSyncData.players[i].m_XUID ) ) );
2469 }
2470 session->SetSessionCustomPropertyJson( L"RoomSyncData", currentXuidArray->Stringify() );
2471 LeaveCriticalSection(&m_csRoomSyncData);
2472
2473 HRESULT hr = S_OK;
2474 WriteSessionHelper( primaryUserXBLContext, session, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr );
2475
2476 // If we didn't succeed, then retry later
2477 if( hr != S_OK )
2478 {
2479 if( m_customDataDirtyUpdateTicks == 0 )
2480 {
2481 LogCommentFormat(L"Error updating custom data 0x%x, will retry", hr);
2482 m_customDataDirtyUpdateTicks = 20;
2483 }
2484 }
2485 }
2486
2487 LogComment(L"Ending thread to update custom data");
2488
2489 return 0;
2490}
2491
2492int DQRNetworkManager::_CheckInviteThreadProc(void* lpParameter)
2493{
2494 DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter;
2495 return pDQR->CheckInviteThreadProc();
2496}
2497
2498int DQRNetworkManager::CheckInviteThreadProc()
2499{
2500 for( int i = 4; i < 12; i++ )
2501 {
2502 WXS::User^ anyUser = InputManager.GetUserForGamepad(i);
2503 if( anyUser )
2504 {
2505 m_partyController->CheckPartySessionFull(anyUser);
2506 return 0;
2507 }
2508 }
2509 return 0;
2510}
2511
2512// This method is called by the the party controller if a new party is found for a local player. This will happen after the party system
2513// has called HandlePlayerRemovedFromParty, if the player is being removed from one party only to be placed in another. If Only
2514// HandlePlayerRemovedFromParty is called (with no following HandleNewPartyFoundForPlayer), then we must assume that the player was
2515// just removed from a party, and Isn't moving to another party. We try to differentiate between these two events by allowing a window of time
2516// to pass after a user is removed from a party before processing it.
2517void DQRNetworkManager::HandleNewPartyFoundForPlayer()
2518{
2519 LogCommentFormat(L"HandleNewPartyFoundForPlayer called after %d ms\n", System::currentTimeMillis() - m_playersLeftPartyTime);
2520 m_playersLeftParty = 0;
2521}
2522
2523void DQRNetworkManager::HandlePlayerRemovedFromParty(int playerMask)
2524{
2525 if( m_state == DNM_INT_STATE_PLAYING )
2526 {
2527 if( m_isHosting )
2528 {
2529 // We should check that they're still here, they might already have left
2530 for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ )
2531 {
2532 if( playerMask & ( 1 << i ) )
2533 {
2534 if (!ProfileManager.IsSignedIn(i))
2535 {
2536 // Player is already gone so remove them from the mask
2537 playerMask &= ~(1 << i);
2538 }
2539 }
2540 }
2541
2542 LogCommentFormat(L"HandlePlayerRemovedFromParty called mask %d\n", playerMask);
2543 m_playersLeftParty |= playerMask;
2544 m_playersLeftPartyTime = System::currentTimeMillis();
2545
2546 // Check for forced sign out
2547 CheckForcedSignOut();
2548 }
2549 else
2550 {
2551 // As a client, we don't have any messy changing to offline game or saving etc. to do, so we can respond immediately to leaving the party
2552 if( playerMask & 1 )
2553 {
2554 DQRNetworkManager::LogComment(L"Primary player on this system has left the party - leaving game\n");
2555 app.SetDisconnectReason(DisconnectPacket::eDisconnect_ExitedGame);
2556 LeaveRoom();
2557 }
2558 else
2559 {
2560 // Secondary player(s) leaving, just remove as if they had chosen to exit themselves from the game
2561 for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ )
2562 {
2563 if( playerMask & ( 1 << i ) )
2564 {
2565 RemoveLocalPlayerByUserIndex(i);
2566 }
2567 }
2568 }
2569 }
2570 }
2571 else
2572 {
2573 LogComment(L"HandlePlayerRemovedFromParty called, ignoring as not in state DNM_INT_STATE_PLAYING\n");
2574 }
2575}
2576
2577// Method called by the DQRNetworkManager when we need to inform the chat system that a new player has been added to the game. We don't do anything
2578// directly here, as this ends up getting called whilst we are in a handler for receiving network data, and telling the chat system that a player
2579// has joined causes it to direct attempt to send data on the network, causing us to lock up. This is processed in the tick instead.
2580void DQRNetworkManager::ChatPlayerJoined(int idx)
2581{
2582 EnterCriticalSection(&m_csVecChatPlayers);
2583 m_vecChatPlayersJoined.push_back(idx);
2584 LeaveCriticalSection(&m_csVecChatPlayers);
2585}
2586
2587bool DQRNetworkManager::IsReadyToPlayOrIdle()
2588{
2589 return (( m_state == DNM_INT_STATE_HOSTING_WAITING_TO_PLAY ) || ( m_state == DNM_INT_STATE_PLAYING ) || ( m_state == DNM_INT_STATE_IDLE ) );
2590}
2591
2592void DQRNetworkManager::StartGame()
2593{
2594 SetState( DNM_INT_STATE_STARTING);
2595 SetState( DNM_INT_STATE_PLAYING);
2596}
2597
2598// Removes all local players from a network game (aysnchronously) and leaves the game
2599void DQRNetworkManager::LeaveRoom()
2600{
2601 m_playersLeftParty = 0;
2602 if( ( m_state == DNM_INT_STATE_HOSTING_WAITING_TO_PLAY ) ||
2603 ( m_state == DNM_INT_STATE_STARTING ) ||
2604 ( m_state == DNM_INT_STATE_PLAYING ) )
2605 {
2606 SetState(DNM_INT_STATE_LEAVING);
2607
2608 // Empty out the room of players & notify the game (needs separate loops for each as HandlePlayerLeaving checks the players)
2609 for( int i = 0; i < m_roomSyncData.playerCount; i++ )
2610 {
2611 m_listener->HandlePlayerLeaving(m_players[i]);
2612 }
2613
2614 for( int i = 0; i < m_roomSyncData.playerCount; i++ )
2615 {
2616 delete m_players[i];
2617 m_players[i] = NULL;
2618 }
2619 memset(&m_roomSyncData, 0, sizeof(m_roomSyncData));
2620 m_displayNames.clear();
2621
2622 m_LeaveRoomThread = new C4JThread(&DQRNetworkManager::_LeaveRoomThreadProc, this, "Leave room");
2623 m_LeaveRoomThread->Run();
2624 }
2625 else
2626 {
2627 SetState(DNM_INT_STATE_IDLE);
2628 }
2629}
2630
2631void DQRNetworkManager::TidyUpFailedJoin()
2632{
2633 m_TidyUpJoinThread = new C4JThread(&DQRNetworkManager::_TidyUpJoinThreadProc, this, "Tidying up failed join");
2634 m_TidyUpJoinThread->Run();
2635}
2636
2637// This is called when we get notification that the user has accepted an invite toast - this is a good point to check the session that the party is associated with,
2638// and see if it is full of people that aren't us
2639void DQRNetworkManager::SetInviteReceivedFlag()
2640{
2641 m_inviteReceived = true;
2642}
2643
2644// The "Party process" is picked up in the tick and used to join games from invites etc. This method is static so it can be called from bits of the game that aren't really tied in with the network libs.
2645void DQRNetworkManager::SetPartyProcessJoinParty()
2646{
2647 m_partyProcess = DQRNetworkManager::DNM_PARTY_PROCESS_JOIN_PARTY;
2648}
2649
2650// The "Party process" is picked up in the tick and used to join games from invites etc. This method is static so it can be called from bits of the game that aren't really tied in with the network libs.
2651void DQRNetworkManager::SetPartyProcessJoinSession(int bootUserIndex, Platform::String^ bootSessionName, Platform::String^ bootServiceConfig, Platform::String^ bootSessionTemplate)
2652{
2653 if( s_pDQRManager )
2654 {
2655 // Don't do anything if we are already in this session
2656 if( s_pDQRManager->m_multiplayerSession )
2657 {
2658 if( s_pDQRManager->m_multiplayerSession->SessionReference->SessionName == bootSessionName )
2659 {
2660 return;
2661 }
2662 }
2663 }
2664 m_bootUserIndex = bootUserIndex;
2665 m_bootSessionName = bootSessionName->Data();
2666 m_bootServiceConfig = bootServiceConfig->Data();
2667 m_bootSessionTemplate = bootSessionTemplate->Data();
2668 m_partyProcess = DQRNetworkManager::DNM_PARTY_PROCESS_JOIN_SPECIFIED;
2669}
2670
2671// Brings up the system GUI so that invites can be sent to invite friends to join the current game party
2672void DQRNetworkManager::SendInviteGUI(int quadrant)
2673{
2674 Windows::Xbox::UI::SystemUI::ShowSendInvitesAsync(ProfileManager.GetUser(quadrant));
2675}
2676
2677bool DQRNetworkManager::IsAddingPlayer()
2678{
2679 return ( m_addLocalPlayerState != DNM_ADD_PLAYER_STATE_IDLE );
2680}
2681
2682// Attempt to join a party that we have found, for a given set of local players. This adds the specified users to the
2683// party, and then we have to wait for the host to detect this change, and (hopefully) reserve slots for us in the game session. When our
2684// slots are reserved, we will receive a game session ready notification through the party manager & can take things from there.
2685bool DQRNetworkManager::JoinPartyFromSearchResult(SessionSearchResult *searchResult, int playerMask)
2686{
2687 // Assume that primary player is always player 0
2688 if( ( playerMask & 1 ) == 0 )
2689 {
2690 return false;
2691 }
2692
2693 // Gather set of XUIDs for the players that we are joining with
2694 for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ )
2695 {
2696 if( playerMask & ( 1 << i ) )
2697 {
2698 WXS::User^ user = ProfileManager.GetUser(i);
2699 if( user == nullptr )
2700 {
2701 return false;
2702 }
2703 m_joinSessionXUIDs[i] = user->XboxUserId;
2704 }
2705 else
2706 {
2707 m_joinSessionXUIDs[i] = nullptr;
2708 }
2709 }
2710
2711 // Set up primary user & context to be used generally by other things once we are in the game
2712 m_primaryUser = ProfileManager.GetUser(0);
2713 if( m_primaryUser == nullptr )
2714 {
2715 return false;
2716 }
2717
2718 m_primaryUserXboxLiveContext = ref new MXS::XboxLiveContext(m_primaryUser);
2719 if( m_primaryUserXboxLiveContext == nullptr )
2720 {
2721 return false;
2722 }
2723
2724 m_currentUserMask = 0;
2725 m_joinSessionUserMask = playerMask;
2726 m_isInSession = true;
2727 m_isOfflineGame = false;
2728
2729 m_startedWaitingForReservationsTime = System::currentTimeMillis();
2730 SetState(DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS);
2731
2732 // There is a small window that we currently allow cancelling
2733 m_cancelJoinFromSearchResult = false;
2734
2735 // Before we join the party, check if any of the joining players are in the session already, and remove. Joining the game cleanly depends
2736 // on the host detecting us joining the party and then adding us (as reserved) to the session so it can pull us in, which it can't do
2737 // if we are already in the session
2738
2739 MXSM::MultiplayerSessionReference ^sessionRef = ref new MXSM::MultiplayerSessionReference(SERVICE_CONFIG_ID, MATCH_SESSION_TEMPLATE_NAME, ref new Platform::String(searchResult->m_sessionName.c_str()));
2740
2741 bool shownCancelScreen = false;
2742 if( sessionRef != nullptr )
2743 {
2744 // Allow 2 seconds before we let the player cancel
2745 __int64 allowCancelTime = System::currentTimeMillis() + (1000 * 2);
2746
2747 // Now leave the game session. We need to do this for each player in turn, writing each time. Consider that any of the joining
2748 // members *may* have a slot (reserved or active) depending on how far progressed the joining got.
2749 bool bError = false;
2750 for( int i = 0; i < 4; i++ )
2751 {
2752 if( playerMask & ( 1 << i ) )
2753 {
2754 LogCommentFormat(L"Attempting to remove player %d from session",i);
2755 WXS::User^ leavingUser = ProfileManager.GetUser(i);
2756 if( leavingUser == nullptr )
2757 {
2758 bError = true;
2759 continue;
2760 }
2761 MXS::XboxLiveContext^ leavingUserXBLContext = ref new MXS::XboxLiveContext(leavingUser);
2762 if( leavingUserXBLContext == nullptr )
2763 {
2764 bError = true;
2765 continue;
2766 }
2767
2768 EnableDebugXBLContext(leavingUserXBLContext);
2769
2770 auto asyncOp = leavingUserXBLContext->MultiplayerService->GetCurrentSessionAsync( sessionRef );
2771 auto ccTask = create_task(asyncOp).then([this, leavingUser, leavingUserXBLContext,&bError] (task<MXSM::MultiplayerSession^> t)
2772 {
2773 try
2774 {
2775 MXSM::MultiplayerSession^ currentSession = t.get();
2776
2777 if( currentSession == nullptr )
2778 {
2779 bError = true;
2780 }
2781 else
2782 {
2783 bool bFound = false;
2784 for( int i = 0; i < currentSession->Members->Size; i++ )
2785 {
2786 if( currentSession->Members->GetAt(i)->XboxUserId == leavingUser->XboxUserId )
2787 {
2788 bFound = true;
2789 break;
2790 }
2791 }
2792
2793 if( bFound )
2794 {
2795 currentSession->Leave();
2796 HRESULT hr = S_OK;
2797 WriteSessionHelper(leavingUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr);
2798 }
2799 }
2800 }
2801 catch ( Platform::COMException^ ex )
2802 {
2803 bError = true;
2804 }
2805 });
2806
2807 while(!ccTask.is_done())
2808 {
2809 // Check for being disconnected from the network
2810 if(!ProfileManager.IsSignedInLive(i) || m_cancelJoinFromSearchResult)
2811 {
2812 asyncOp->Cancel();
2813 bError = true;
2814 break;
2815 }
2816
2817 __int64 currentTime = System::currentTimeMillis();
2818 if( currentTime > allowCancelTime)
2819 {
2820 shownCancelScreen = true;
2821
2822 ConnectionProgressParams *param = new ConnectionProgressParams();
2823 param->iPad = 0;
2824 param->stringId = -1;
2825 param->showTooltips = true;
2826 param->cancelFunc = &g_NetworkManager.CancelJoinGame;
2827 param->cancelFuncParam = &g_NetworkManager;
2828
2829 // Show a progress spinner so that we can cancel the join.
2830 ui.NavigateToScene(0, eUIScene_ConnectingProgress, param);
2831
2832 allowCancelTime = LONGLONG_MAX;
2833 }
2834
2835 // Tick some simple things
2836 ProfileManager.Tick();
2837 StorageManager.Tick();
2838 InputManager.Tick();
2839 RenderManager.Tick();
2840 ui.tick();
2841 ui.render();
2842 RenderManager.Present();
2843 }
2844 // Check for being disconnected from the network
2845 if(!ProfileManager.IsSignedInLive(i))
2846 {
2847 bError = true;
2848 break;
2849 }
2850 }
2851 }
2852
2853 if ( bError && !m_cancelJoinFromSearchResult )
2854 {
2855 app.DebugPrintf("Error removing a player from the session");
2856 assert(true);
2857 }
2858 }
2859
2860 if(shownCancelScreen)
2861 {
2862 ui.NavigateToScene(0,eUIScene_Timer);
2863 }
2864
2865 if(m_cancelJoinFromSearchResult)
2866 {
2867 SetState(DNM_INT_STATE_IDLE);
2868 m_cancelJoinFromSearchResult= false;
2869
2870 return false;
2871 }
2872
2873 m_cancelJoinFromSearchResult = false;
2874
2875
2876 // Now we join the party with each of joining players, and then wait to be informed of our reserved slots
2877 for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ )
2878 {
2879 if( playerMask & ( 1 << i ) )
2880 {
2881 WXS::User^ user = ProfileManager.GetUser(i);
2882
2883 auto joinPartyAsync = WXM::Party::JoinPartyByIdAsync(user, ref new Platform::String(searchResult->m_partyId.c_str()));
2884 auto ccTask = create_task(joinPartyAsync).then([this](task<void> t)
2885 {
2886 try
2887 {
2888 t.get(); // if t.get() didn't throw, it succeeded
2889 }
2890 catch (Platform::COMException^ ex)
2891 {
2892 LogCommentWithError( L"JoinPartyByIdAsync failed", ex->HResult );
2893 SetState(DNM_INT_STATE_JOINING_FAILED);
2894 }
2895 });
2896
2897
2898 while(!ccTask.is_done())
2899 {
2900 // Check for being disconnected from the network
2901 if(!ProfileManager.IsSignedInLive(i))
2902 {
2903 joinPartyAsync->Cancel();
2904 break;
2905 }
2906
2907 // Tick some simple things
2908 ProfileManager.Tick();
2909 StorageManager.Tick();
2910 InputManager.Tick();
2911 RenderManager.Tick();
2912 ui.tick();
2913 ui.render();
2914 RenderManager.Present();
2915 }
2916 // Check for being disconnected from the network
2917 if(!ProfileManager.IsSignedInLive(i))
2918 {
2919 break;
2920 }
2921 }
2922 }
2923
2924 return ( m_state == DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS );
2925}
2926
2927void DQRNetworkManager::CancelJoinPartyFromSearchResult()
2928{
2929 m_cancelJoinFromSearchResult = true;
2930}
2931
2932// This method attempts to find the local player within a party that would be most appropriate to act as our primary player when joining the game.
2933bool DQRNetworkManager::GetBestPartyUserIndex()
2934{
2935 bool playerFound = false;
2936
2937 // Use context from any user at all for this
2938 WXS::User ^anyUser = nullptr;
2939 if( WXS::User::Users->Size > 0 )
2940 {
2941 anyUser = WXS::User::Users->GetAt(0);
2942 }
2943 if( anyUser == nullptr ) return 0;
2944 MXS::XboxLiveContext^ xboxLiveContext = ref new MXS::XboxLiveContext(anyUser);
2945
2946 m_partyController->RefreshPartyView();
2947 MXSM::MultiplayerSessionReference ^sessionRef = m_partyController->GetGamePartySessionReference();
2948 if( sessionRef != nullptr )
2949 {
2950 auto asyncOp = xboxLiveContext->MultiplayerService->GetCurrentSessionAsync( sessionRef );
2951 create_task(asyncOp)
2952 .then([this,&playerFound] (task<Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^> t)
2953 {
2954 try
2955 {
2956 Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ session = t.get();
2957
2958 int playerIdx = -1;
2959 for( int i = 0; i < session->Members->Size; i++ ) // First pass through to see if we've a signed in user that is in the session we've been added to
2960 {
2961 MXSM::MultiplayerSessionMember^ member = session->Members->GetAt(i);
2962 for( int j = 0; j < MAX_LOCAL_PLAYERS; j++ )
2963 {
2964 WXS::User ^user = ProfileManager.GetUser(j);
2965 if( user != nullptr )
2966 {
2967 if( user->XboxUserId == member->XboxUserId )
2968 {
2969 DQRNetworkManager::LogCommentFormat(L"Found already signed in player %s to act as invite recipient",member->XboxUserId->Data());
2970 playerIdx = j;
2971 playerFound = true;
2972 break;
2973 }
2974 }
2975 }
2976 }
2977 if( playerIdx == -1 ) // If nothing found, second pass through to attempt to find a controller that matches
2978 {
2979 for( int i = 0; i < session->Members->Size; i++ )
2980 {
2981 MXSM::MultiplayerSessionMember^ member = session->Members->GetAt(i);
2982 for( int j = 4; j < 12; j++ )
2983 {
2984 WXS::User ^user = InputManager.GetUserForGamepad(j);
2985 if( user != nullptr )
2986 {
2987 if( user->XboxUserId == member->XboxUserId )
2988 {
2989 DQRNetworkManager::LogCommentFormat(L"Found controller %d for %s (session idx %d) to act as invite recipient (%s)",j,member->XboxUserId->Data(),i, user->XboxUserId->Data());
2990 playerIdx = ProfileManager.AddGamepadToGame(j);
2991 if( playerIdx != -1 )
2992 {
2993 playerFound = true;
2994 DQRNetworkManager::LogCommentFormat(L"Assigned controller to user index %d",playerIdx);
2995 break;
2996 }
2997 }
2998 }
2999 }
3000 }
3001 }
3002 }
3003 catch ( Platform::COMException^ ex )
3004 {
3005 }
3006 }).wait();
3007 }
3008 return playerFound;
3009}
3010
3011// Request the GameDisplayName for this player asynchronously
3012void DQRNetworkManager::RequestDisplayName(DQRNetworkPlayer *player)
3013{
3014 if (player->IsLocal())
3015 {
3016 // Player is local so we can just ask profile manager
3017 SetDisplayName(player->GetUID(), ProfileManager.GetDisplayName(player->GetLocalPlayerIndex()));
3018 }
3019 else
3020 {
3021 // Player is remote so we need to do an async request
3022 PlayerUID xuid = player->GetUID();
3023 ProfileManager.GetProfile(xuid, &GetProfileCallback, this);
3024 }
3025}
3026
3027void DQRNetworkManager::GetProfileCallback(LPVOID pParam, Microsoft::Xbox::Services::Social::XboxUserProfile^ profile)
3028{
3029 DQRNetworkManager *dqnm = (DQRNetworkManager *)pParam;
3030 dqnm->SetDisplayName(PlayerUID(profile->XboxUserId->Data()), profile->GameDisplayName->Data());
3031}
3032
3033// Set player display name
3034void DQRNetworkManager::SetDisplayName(PlayerUID xuid, wstring displayName)
3035{
3036 EnterCriticalSection(&m_csRoomSyncData);
3037 for (int i = 0; i < m_roomSyncData.playerCount; i++)
3038 {
3039 if( m_players[i] )
3040 {
3041 if (m_players[i]->GetUID() == xuid)
3042 {
3043 // Set player display name
3044 m_players[i]->SetDisplayName(displayName);
3045 // Add display name to map
3046 m_displayNames.insert(std::make_pair(m_players[i]->m_name, m_players[i]->m_displayName));
3047 }
3048 }
3049 }
3050 LeaveCriticalSection(&m_csRoomSyncData);
3051}
3052
3053void DQRNetworkManager::CheckForcedSignOut()
3054{
3055 auto asyncOp = m_primaryUserXboxLiveContext->MultiplayerService->GetCurrentSessionAsync(m_multiplayerSession->SessionReference);
3056 create_task(asyncOp)
3057 .then([this] (task<Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^> t)
3058 {
3059 try
3060 {
3061 t.get();
3062 }
3063 catch (Platform::COMException^ ex)
3064 {
3065 if (ex->HResult == 0x8015DC16)
3066 {
3067 m_handleForcedSignOut = true;
3068 }
3069 }
3070 });
3071}
3072
3073void DQRNetworkManager::HandleForcedSignOut()
3074{
3075 // Bin the session (we can't use it anymore)
3076 m_multiplayerSession = nullptr;
3077
3078 // Bin the party
3079 m_partyController->SetPartyView(nullptr);
3080
3081 // Forced sign out destroyed the party so time to go
3082 app.DebugPrintf("DQRNetworkManager::HandleForcedSignOut: Forced sign out destroyed the party, aborting game\n");
3083
3084 if (IsInSession() && !g_NetworkManager.IsLeavingGame())
3085 {
3086 // Exit world
3087 app.SetAction(0, eAppAction_ExitWorld);
3088 }
3089 else
3090 {
3091 app.DebugPrintf("DQRNetworkManager::HandleForcedSignOut: Already leaving the game, skipping abort\n");
3092 }
3093}