the game where you go into mines and start crafting! but for consoles (forked directly from smartcmd's github)
at main 3093 lines 107 kB view raw
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}