From b691c43c44ff180d10e7d4a9afc83b98551ff586 Mon Sep 17 00:00:00 2001 From: daoge_cmd <3523206925@qq.com> Date: Sun, 1 Mar 2026 12:16:08 +0800 Subject: Initial commit --- .../Durango/Network/ChatIntegrationLayer.cpp | 805 +++++ .../Durango/Network/ChatIntegrationLayer.h | 244 ++ .../Durango/Network/DQRNetworkManager.cpp | 3093 ++++++++++++++++++++ .../Durango/Network/DQRNetworkManager.h | 582 ++++ .../Network/DQRNetworkManager_FriendSessions.cpp | 591 ++++ .../Durango/Network/DQRNetworkManager_Log.cpp | 303 ++ .../Network/DQRNetworkManager_SendReceive.cpp | 409 +++ .../Network/DQRNetworkManager_XRNSEvent.cpp | 651 ++++ .../Durango/Network/DQRNetworkPlayer.cpp | 204 ++ .../Durango/Network/DQRNetworkPlayer.h | 65 + .../Durango/Network/NetworkPlayerDurango.cpp | 113 + .../Durango/Network/NetworkPlayerDurango.h | 39 + .../Durango/Network/PartyController.cpp | 1205 ++++++++ Minecraft.Client/Durango/Network/PartyController.h | 74 + .../Network/PlatformNetworkManagerDurango.cpp | 940 ++++++ .../Network/PlatformNetworkManagerDurango.h | 168 ++ .../Windows.Xbox.Networking.RealtimeSession.dll | Bin 0 -> 235520 bytes Minecraft.Client/Durango/Network/base64.cpp | 156 + Minecraft.Client/Durango/Network/base64.h | 6 + .../windows.xbox.networking.realtimesession.pdb | Bin 0 -> 4952064 bytes .../windows.xbox.networking.realtimesession.winmd | Bin 0 -> 18944 bytes 21 files changed, 9648 insertions(+) create mode 100644 Minecraft.Client/Durango/Network/ChatIntegrationLayer.cpp create mode 100644 Minecraft.Client/Durango/Network/ChatIntegrationLayer.h create mode 100644 Minecraft.Client/Durango/Network/DQRNetworkManager.cpp create mode 100644 Minecraft.Client/Durango/Network/DQRNetworkManager.h create mode 100644 Minecraft.Client/Durango/Network/DQRNetworkManager_FriendSessions.cpp create mode 100644 Minecraft.Client/Durango/Network/DQRNetworkManager_Log.cpp create mode 100644 Minecraft.Client/Durango/Network/DQRNetworkManager_SendReceive.cpp create mode 100644 Minecraft.Client/Durango/Network/DQRNetworkManager_XRNSEvent.cpp create mode 100644 Minecraft.Client/Durango/Network/DQRNetworkPlayer.cpp create mode 100644 Minecraft.Client/Durango/Network/DQRNetworkPlayer.h create mode 100644 Minecraft.Client/Durango/Network/NetworkPlayerDurango.cpp create mode 100644 Minecraft.Client/Durango/Network/NetworkPlayerDurango.h create mode 100644 Minecraft.Client/Durango/Network/PartyController.cpp create mode 100644 Minecraft.Client/Durango/Network/PartyController.h create mode 100644 Minecraft.Client/Durango/Network/PlatformNetworkManagerDurango.cpp create mode 100644 Minecraft.Client/Durango/Network/PlatformNetworkManagerDurango.h create mode 100644 Minecraft.Client/Durango/Network/Windows.Xbox.Networking.RealtimeSession.dll create mode 100644 Minecraft.Client/Durango/Network/base64.cpp create mode 100644 Minecraft.Client/Durango/Network/base64.h create mode 100644 Minecraft.Client/Durango/Network/windows.xbox.networking.realtimesession.pdb create mode 100644 Minecraft.Client/Durango/Network/windows.xbox.networking.realtimesession.winmd (limited to 'Minecraft.Client/Durango/Network') diff --git a/Minecraft.Client/Durango/Network/ChatIntegrationLayer.cpp b/Minecraft.Client/Durango/Network/ChatIntegrationLayer.cpp new file mode 100644 index 00000000..232b4dd3 --- /dev/null +++ b/Minecraft.Client/Durango/Network/ChatIntegrationLayer.cpp @@ -0,0 +1,805 @@ +//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +//// PARTICULAR PURPOSE. +//// +//// Copyright (c) Microsoft Corporation. All rights reserved +#include "stdafx.h" +#include "ChatIntegrationLayer.h" +#include "DQRNetworkManager.h" +#include + +using namespace Windows::Foundation; +using namespace Windows::Xbox::System; + +// To integrate the Chat DLL in your game, you can use this ChatIntegrationLayer class with modifications, +// or create your own design your own class using the code in this file a guide. +std::shared_ptr GetChatIntegrationLayer() +{ + static std::shared_ptr chatIntegrationLayerInstance; + if (chatIntegrationLayerInstance == nullptr) + { + chatIntegrationLayerInstance.reset( new ChatIntegrationLayer() ); + } + + return chatIntegrationLayerInstance; +} + +ChatIntegrationLayer::ChatIntegrationLayer() +{ + ZeroMemory( m_chatVoicePacketsStatistic, sizeof(m_chatVoicePacketsStatistic) ); +} + +void ChatIntegrationLayer::InitializeChatManager( + __in bool combineCaptureBuffersIntoSinglePacket, + __in bool useKinectAsCaptureSource, + __in bool applySoundEffectsToCapturedAudio, + __in bool applySoundEffectsToChatRenderedAudio, + DQRNetworkManager *pDQRNet + ) +{ + m_pDQRNet = pDQRNet; + { + Concurrency::critical_section::scoped_lock lock(m_chatPacketStatsLock); + ZeroMemory( m_chatVoicePacketsStatistic, sizeof(m_chatVoicePacketsStatistic) ); + } + + m_chatManager = ref new Microsoft::Xbox::GameChat::ChatManager(); + m_chatManager->ChatSettings->DiagnosticsTraceLevel = Microsoft::Xbox::GameChat::GameChatDiagnosticsTraceLevel::Verbose; + + // Optionally, change the default settings below as desired by commenting out and editing any of the following lines + // Otherwise these defaults are used. + // + // m_chatManager = ref new Microsoft::Xbox::GameChat::ChatManager( ChatSessionPeriod::ChatPeriodOf40Milliseconds ); + // m_chatManager->ChatSettings->AudioThreadPeriodInMilliseconds = 40; + // m_chatManager->ChatSettings->AudioThreadAffinityMask = XAUDIO2_DEFAULT_PROCESSOR; // <- this means is core 5, same as the default XAudio2 core + // m_chatManager->ChatSettings->AudioThreadPriority = THREAD_PRIORITY_TIME_CRITICAL; + // m_chatManager->ChatSettings->AudioEncodingQuality = Windows::Xbox::Chat::EncodingQuality::Normal; + // m_chatManager->ChatSettings->DiagnosticsTraceLevel = Microsoft::Xbox::GameChat::GameChatDiagnosticsTraceLevel::Verbose; + m_chatManager->ChatSettings->CombineCaptureBuffersIntoSinglePacket = combineCaptureBuffersIntoSinglePacket; // if unset, it defaults to TRUE + m_chatManager->ChatSettings->UseKinectAsCaptureSource = useKinectAsCaptureSource; // if unset, it defaults to FALSE + m_chatManager->ChatSettings->PreEncodeCallbackEnabled = applySoundEffectsToCapturedAudio; // if unset, it defaults to FALSE + m_chatManager->ChatSettings->PostDecodeCallbackEnabled = applySoundEffectsToChatRenderedAudio; // if unset, it defaults to FALSE + + InitializeCriticalSection(&m_csAddedUsers); + + std::weak_ptr weakPtrToThis = shared_from_this(); + +#ifdef PROFILE + m_chatManager->ChatSettings->PerformanceCountersEnabled = true; +#endif + + // Upon enter constrained mode, mute everyone. + // Upon leaving constrained mode, unmute everyone who was previously muted. + m_tokenResourceAvailabilityChanged = Windows::ApplicationModel::Core::CoreApplication::ResourceAvailabilityChanged += + ref new EventHandler< Platform::Object^ >( [weakPtrToThis] (Platform::Object^, Platform::Object^ ) + { + // Using a std::weak_ptr instead of 'this' to avoid dangling pointer if caller class is released. + // Simply unregistering the callback in the destructor isn't enough to prevent a dangling pointer + std::shared_ptr sharedPtrToThis(weakPtrToThis.lock()); + if( sharedPtrToThis != nullptr ) + { + if (Windows::ApplicationModel::Core::CoreApplication::ResourceAvailability == Windows::ApplicationModel::Core::ResourceAvailability::Constrained) + { + if( sharedPtrToThis->m_chatManager != nullptr ) + { + sharedPtrToThis->m_chatManager->MuteAllUsersFromAllChannels(); + } + } + else if(Windows::ApplicationModel::Core::CoreApplication::ResourceAvailability == Windows::ApplicationModel::Core::ResourceAvailability::Full) + { + if( sharedPtrToThis->m_chatManager != nullptr ) + { + sharedPtrToThis->m_chatManager->UnmuteAllUsersFromAllChannels(); + + // The title should remember who was muted so when the Resume even occurs + // to avoid unmuting users who has been previously muted. Simply re-mute them here + } + } + } + }); + + m_tokenOnDebugMessage = m_chatManager->OnDebugMessage += + ref new Windows::Foundation::EventHandler( + [weakPtrToThis] ( Platform::Object^, Microsoft::Xbox::GameChat::DebugMessageEventArgs^ args ) + { + // Using a std::weak_ptr instead of 'this' to avoid dangling pointer if caller class is released. + // Simply unregistering the callback in the destructor isn't enough to prevent a dangling pointer + std::shared_ptr sharedPtrToThis(weakPtrToThis.lock()); + if( sharedPtrToThis != nullptr ) + { + sharedPtrToThis->OnDebugMessageReceived(args); + } + }); + + m_tokenOnOutgoingChatPacketReady = m_chatManager->OnOutgoingChatPacketReady += + ref new Windows::Foundation::EventHandler( + [weakPtrToThis] ( Platform::Object^, Microsoft::Xbox::GameChat::ChatPacketEventArgs^ args ) + { + // Using a std::weak_ptr instead of 'this' to avoid dangling pointer if caller class is released. + // Simply unregistering the callback in the destructor isn't enough to prevent a dangling pointer + std::shared_ptr sharedPtrToThis(weakPtrToThis.lock()); + if( sharedPtrToThis != nullptr ) + { + sharedPtrToThis->OnOutgoingChatPacketReady(args); + } + }); + + m_tokenOnCompareUniqueConsoleIdentifiers = m_chatManager->OnCompareUniqueConsoleIdentifiers += + ref new Microsoft::Xbox::GameChat::CompareUniqueConsoleIdentifiersHandler( + [weakPtrToThis] ( Platform::Object^ obj1, Platform::Object^ obj2 ) + { + // Using a std::weak_ptr instead of 'this' to avoid dangling pointer if caller class is released. + // Simply unregistering the callback in the destructor isn't enough to prevent a dangling pointer + std::shared_ptr sharedPtrToThis(weakPtrToThis.lock()); + if( sharedPtrToThis != nullptr ) + { + return sharedPtrToThis->CompareUniqueConsoleIdentifiers(obj1, obj2); + } + else + { + return false; + } + }); + + m_tokenUserAudioDeviceAdded = WXS::User::AudioDeviceAdded += + ref new Windows::Foundation::EventHandler( + [weakPtrToThis] ( Platform::Object^, WXS::AudioDeviceAddedEventArgs^ value ) + { + std::shared_ptr sharedPtrToThis(weakPtrToThis.lock()); + if( sharedPtrToThis != nullptr ) + { + sharedPtrToThis->EvaluateDevicesForUser(value->User); + } + }); + + m_tokenUserAudioDeviceRemoved = WXS::User::AudioDeviceRemoved += + ref new Windows::Foundation::EventHandler( + [weakPtrToThis] ( Platform::Object^, WXS::AudioDeviceRemovedEventArgs^ value ) + { + std::shared_ptr sharedPtrToThis(weakPtrToThis.lock()); + if( sharedPtrToThis != nullptr ) + { + sharedPtrToThis->EvaluateDevicesForUser(value->User); + } + }); + + m_tokenUserAudioDeviceChanged = WXS::User::AudioDeviceChanged += + ref new Windows::Foundation::EventHandler( + [weakPtrToThis] ( Platform::Object^, WXS::AudioDeviceChangedEventArgs^ value ) + { + std::shared_ptr sharedPtrToThis(weakPtrToThis.lock()); + if( sharedPtrToThis != nullptr ) + { + sharedPtrToThis->EvaluateDevicesForUser(value->User); + } + }); + + //m_tokenSuspending = Windows::ApplicationModel::Core::CoreApplication::Suspending += + // ref new EventHandler< Windows::ApplicationModel::SuspendingEventArgs^ >( [weakPtrToThis] (Platform::Object^, Windows::ApplicationModel::SuspendingEventArgs^ args) + //{ + // // Upon Suspending, nothing needs to be done + //}); + + //m_tokenResuming = Windows::ApplicationModel::Core::CoreApplication::Resuming += + // ref new EventHandler< Platform::Object^ >( [weakPtrToThis] (Platform::Object^, Windows::ApplicationModel::SuspendingEventArgs^ args) + //{ + // // Upon Resuming, re-initialize the network, and reinitialize the chat session + //}); +} + + +void ChatIntegrationLayer::Shutdown() +{ + if( m_chatManager != nullptr ) + { + m_chatManager->OnDebugMessage -= m_tokenOnDebugMessage; + m_chatManager->OnOutgoingChatPacketReady -= m_tokenOnOutgoingChatPacketReady; + m_chatManager->OnCompareUniqueConsoleIdentifiers -= m_tokenOnCompareUniqueConsoleIdentifiers; + Windows::ApplicationModel::Core::CoreApplication::ResourceAvailabilityChanged -= m_tokenResourceAvailabilityChanged; + if( m_chatManager->ChatSettings->PreEncodeCallbackEnabled ) + { + m_chatManager->OnPreEncodeAudioBuffer -= m_tokenOnPreEncodeAudioBuffer; + } + if( m_chatManager->ChatSettings->PostDecodeCallbackEnabled ) + { + m_chatManager->OnPostDecodeAudioBuffer -= m_tokenOnPostDecodeAudioBuffer; + } + WXS::User::AudioDeviceAdded -= m_tokenUserAudioDeviceAdded; + WXS::User::AudioDeviceRemoved -= m_tokenUserAudioDeviceRemoved; + WXS::User::AudioDeviceChanged -= m_tokenUserAudioDeviceChanged; + + DeleteCriticalSection(&m_csAddedUsers); + + m_chatManager = nullptr; + } +} + +void ChatIntegrationLayer::OnDebugMessageReceived( + __in Microsoft::Xbox::GameChat::DebugMessageEventArgs^ args + ) +{ + // To integrate the Chat DLL in your game, + // change this to false and remove the LogComment calls, + // or integrate with your game's own UI/debug message logging system + bool outputToUI = false; + + if( outputToUI ) + { + if (args->ErrorCode == S_OK ) + { + m_pDQRNet->LogComment(L"GameChat: " + args->Message); + } + else + { + m_pDQRNet->LogCommentWithError(L"GameChat: " + args->Message, args->ErrorCode); + } + } + else + { + // The string appear in the Visual Studio Output window +#ifndef _CONTENT_PACKAGE + OutputDebugString( args->Message->Data() ); +#endif + } +} + +void ChatIntegrationLayer::GameUI_RecordPacketStatistic( + __in Microsoft::Xbox::GameChat::ChatMessageType messageType, + __in ChatPacketType chatPacketType + ) +{ + uint32 messageTypeInt = static_cast(messageType); + if( messageType > Microsoft::Xbox::GameChat::ChatMessageType::InvalidMessage ) + { + return; + } + + { + Concurrency::critical_section::scoped_lock lock(m_chatPacketStatsLock); + m_chatVoicePacketsStatistic[static_cast(chatPacketType)][messageTypeInt]++; + } +} + +int ChatIntegrationLayer::GameUI_GetPacketStatistic( + __in Microsoft::Xbox::GameChat::ChatMessageType messageType, + __in ChatPacketType chatPacketType + ) +{ + uint32 messageTypeInt = static_cast(messageType); + if( messageType > Microsoft::Xbox::GameChat::ChatMessageType::InvalidMessage ) + { + return 0; + } + + { + Concurrency::critical_section::scoped_lock lock(m_chatPacketStatsLock); + return m_chatVoicePacketsStatistic[static_cast(chatPacketType)][messageTypeInt]; + } +} + +void ChatIntegrationLayer::OnOutgoingChatPacketReady( + __in Microsoft::Xbox::GameChat::ChatPacketEventArgs^ args + ) +{ + byte *bytes; + int byteCount; + + GetBufferBytes(args->PacketBuffer, &bytes); + byteCount = args->PacketBuffer->Length; + unsigned int address = 0; + if( !args->SendPacketToAllConnectedConsoles ) + { + address = safe_cast(args->UniqueTargetConsoleIdentifier); + } + m_pDQRNet->SendBytesChat(address, bytes, byteCount, args->SendReliable, args->SendInOrder, args->SendPacketToAllConnectedConsoles); + + GameUI_RecordPacketStatistic( args->ChatMessageType, ChatPacketType::OutgoingPacket ); + +} + +void ChatIntegrationLayer::OnIncomingChatMessage( + unsigned int sessionAddress, + Platform::Array^ message + ) +{ + // To integrate the Chat DLL in your game, change the following code to use your game's network layer. + // Ignore the OnChatMessageReceived event as that is specific to this sample's simple network layer. + // Instead your title should upon receiving a packet, extract the chat message from it, and then call m_chatManager->ProcessIncomingChatMessage as shown below + // You will need to isolate chat messages to be unique from the rest of you game's other message types. + + // uniqueRemoteConsoleIdentifier is a Platform::Object^ and can be cast or unboxed to most types. + // What exactly you use doesn't matter, but optimally it would be something that uniquely identifies a console on in the session. + // A Windows::Xbox::Networking::SecureDeviceAssociation^ is perfect to use if you have access to it. + + // This is how you would convert from byte array to a IBuffer^ + // + // Windows::Storage::Streams::IBuffer^ destBuffer = ref new Windows::Storage::Streams::Buffer( sourceByteBufferSize ); + // byte* destBufferBytes = nullptr; + // GetBufferBytes( destBuffer, &destBufferBytes ); + // errno_t err = memcpy_s( destBufferBytes, destBuffer->Capacity, sourceByteBuffer, sourceByteBufferSize ); + // THROW_HR_IF(err != 0, E_FAIL); + // destBuffer->Length = sourceByteBufferSize; + + // This is how you would convert from an int to a Platform::Object^ + // Platform::Object obj = IntToPlatformObject(5); + + Windows::Storage::Streams::IBuffer^ chatMessage = ArrayToBuffer(message); + Platform::Object^ uniqueRemoteConsoleIdentifier = (Platform::Object^)sessionAddress; + + if( m_chatManager != nullptr ) + { + Microsoft::Xbox::GameChat::ChatMessageType chatMessageType = m_chatManager->ProcessIncomingChatMessage(chatMessage, uniqueRemoteConsoleIdentifier); + + GameUI_RecordPacketStatistic( chatMessageType, ChatPacketType::IncomingPacket ); + } +} + +// Only add people who intend to play. +void ChatIntegrationLayer::AddAllLocallySignedInUsersToChatClient( + __in uint8 channelIndex, + __in Windows::Foundation::Collections::IVectorView^ locallySignedInUsers + ) +{ + // To integrate the Chat DLL in your game, + // add all locally signed in users to the chat client + for each( Windows::Xbox::System::User^ user in locallySignedInUsers ) + { + if( user != nullptr ) + { +// LogComment(L"Adding Local User to Chat Client"); + AddLocalUserToChatChannel( channelIndex, user ); + } + } +} + +ChatIntegrationLayer::AddedUser::AddedUser(Windows::Xbox::System::IUser^ user, bool canCaptureAudio) +{ + m_user = user; + m_canCaptureAudio = canCaptureAudio; +} + + +void ChatIntegrationLayer::AddLocalUser( __in Windows::Xbox::System::IUser^ user ) +{ + // Check we haven't added already + for( int i = 0; i < m_addedUsers.size(); i++ ) + { + if( m_addedUsers[i]->m_user->XboxUserId == user->XboxUserId ) + { + return; + } + } + + bool kinectAvailable = false; + Windows::Kinect::KinectSensor^ sensor = Windows::Kinect::KinectSensor::GetDefault(); + if( sensor ) + { + sensor->Open(); + if( sensor->IsAvailable ) + { + kinectAvailable = true; + m_pDQRNet->LogComment(L"Evaluated that kinect is available\n"); + } + sensor->Close(); + } + + EnterCriticalSection(&m_csAddedUsers); + // First establish whether we have an appropriate audio device at this time + bool canCaptureAudio = false; + for each( WXS::IAudioDeviceInfo^ audioDevice in user->AudioDevices ) + { + m_pDQRNet->LogComment(L"Evaluating device " + audioDevice->DeviceCategory.ToString() + L" " + + audioDevice->DeviceType.ToString() + L" " + + audioDevice->Id + L" " + + audioDevice->Sharing.ToString() + L" " + + audioDevice->IsMicrophoneMuted.ToString() + L"\n"); + + // Consider shared devices only if kinect is actually available - every machine seems to claim a shared device whether kinect is attached or not + if( ( audioDevice->DeviceType == WXS::AudioDeviceType::Capture ) && ( kinectAvailable || ( audioDevice->Sharing != WXS::AudioDeviceSharing::Shared) ) ) + { + canCaptureAudio = true; + } + } + + // If we can capture audio initially, then register with the chat session. Otherwise we'll reevaluate this situation when audio devices change + if( canCaptureAudio ) + { + AddLocalUserToChatChannel( 0 , user ); + } + + // Add to vector of users that we are tracking in the chat system + m_addedUsers.push_back(new AddedUser(user, canCaptureAudio)); + LeaveCriticalSection(&m_csAddedUsers); +} + +// Remove from our list of tracked users, if the user is already there +void ChatIntegrationLayer::RemoveLocalUser( __in Windows::Xbox::System::IUser^ user ) +{ + EnterCriticalSection(&m_csAddedUsers); + for( auto it = m_addedUsers.begin(); it != m_addedUsers.end(); it++ ) + { + if( (*it)->m_user->XboxUserId == user->XboxUserId ) + { + delete (*it); + m_addedUsers.erase(it); + LeaveCriticalSection(&m_csAddedUsers); + return; + } + } + LeaveCriticalSection(&m_csAddedUsers); +} + +// This is called when the audio devices for a user change in any way, and establishes whether we can now capture audio. Any change in this status from before will cause the user to be added/removed from the chat session. +void ChatIntegrationLayer::EvaluateDevicesForUser(__in Windows::Xbox::System::IUser^ user ) +{ + bool kinectAvailable = false; + Windows::Kinect::KinectSensor^ sensor = Windows::Kinect::KinectSensor::GetDefault(); + if( sensor ) + { + sensor->Open(); + if( sensor->IsAvailable ) + { + kinectAvailable = true; + m_pDQRNet->LogComment(L"Evaluated that kinect is available\n"); + } + sensor->Close(); + } + + EnterCriticalSection(&m_csAddedUsers); + for( int i = 0; i < m_addedUsers.size(); i++ ) + { + AddedUser *addedUser = m_addedUsers[i]; + if( addedUser->m_user->XboxUserId == user->XboxUserId ) + { + bool canCaptureAudio = false; + for each( WXS::IAudioDeviceInfo^ audioDevice in addedUser->m_user->AudioDevices ) + { + // Consider shared devices only if kinect is actually available - every machine seems to claim a shared device whether kinect is attached or not + if( ( audioDevice->DeviceType == WXS::AudioDeviceType::Capture ) && ( kinectAvailable || ( audioDevice->Sharing != WXS::AudioDeviceSharing::Shared) ) ) + { + canCaptureAudio = true; + break; + } + } + if( canCaptureAudio != addedUser->m_canCaptureAudio ) + { + if( canCaptureAudio ) + { + AddLocalUserToChatChannel(0, addedUser->m_user ); + } + else + { + RemoveUserFromChatChannel(0, addedUser->m_user ); + } + addedUser->m_canCaptureAudio = canCaptureAudio; + LeaveCriticalSection(&m_csAddedUsers); + return; + } + } + } + LeaveCriticalSection(&m_csAddedUsers); +} + +void ChatIntegrationLayer::AddLocalUserToChatChannel( + __in uint8 channelIndex, + __in Windows::Xbox::System::IUser^ user + ) +{ + // Adds a local user to a specific channel. + + // This is helper function waits for the task to cm_chatManageromplete so shouldn't be called from the UI thread + // Remove the .wait() and return the result of concurrency::create_task() if you want to call it from the UI thread + // and chain PPL tasks together + + m_pDQRNet->LogComment( L">>>>>>>>>>>>> AddLocalUserToChatChannel" ); + if( m_chatManager != nullptr ) + { + auto asyncOp = m_chatManager->AddLocalUserToChatChannelAsync( channelIndex, user ); + concurrency::create_task( asyncOp ) + .then( [this] ( concurrency::task t ) + { + // Error handling + try + { + t.get(); + } + catch ( Platform::Exception^ ex ) + { + m_pDQRNet->LogCommentWithError( L"AddLocalUserToChatChannelAsync failed", ex->HResult ); + } + }) + .wait(); + } +} + +void ChatIntegrationLayer::RemoveRemoteConsole( + unsigned int address + ) +{ + // uniqueConsoleIdentifier is a Platform::Object^ and can be cast or unboxed to most types. + // What exactly you use doesn't matter, but optimally it would be something that uniquely identifies a console on in the session. + // A Windows::Xbox::Networking::SecureDeviceAssociation^ is perfect to use if you have access to it. + + // This is how you would convert from an int to a Platform::Object^ + // Platform::Object obj = IntToPlatformObject(5); + + // This is helper function waits for the task to complete so shouldn't be called from the UI thread + // Remove the .wait() and return the result of concurrency::create_task() if you want to call it from the UI thread + // and chain PPL tasks together + + Platform::Object^ uniqueRemoteConsoleIdentifier = (Platform::Object^)address; + + if( m_chatManager != nullptr ) + { + auto asyncOp = m_chatManager->RemoveRemoteConsoleAsync( uniqueRemoteConsoleIdentifier ); + concurrency::create_task( asyncOp ).then( [this] ( concurrency::task t ) + { + // Error handling + try + { + t.get(); + } + catch ( Platform::Exception^ ex ) + { + m_pDQRNet->LogCommentWithError( L"RemoveRemoteConsoleAsync failed", ex->HResult ); + } + }) + .wait(); + } +} + +void ChatIntegrationLayer::RemoveUserFromChatChannel( + __in uint8 channelIndex, + __in Windows::Xbox::System::IUser^ user + ) +{ + if( m_chatManager != nullptr ) + { + // This is helper function waits for the task to complete so shouldn't be called from the UI thread + // Remove the .wait() and return the result of concurrency::create_task() if you want to call it from the UI thread + // and chain PPL tasks together + + auto asyncOp = m_chatManager->RemoveLocalUserFromChatChannelAsync( channelIndex, user ); + concurrency::create_task( asyncOp ).then( [this] ( concurrency::task t ) + { + // Error handling + try + { + t.get(); + } + catch ( Platform::Exception^ ex ) + { + m_pDQRNet->LogCommentWithError( L"RemoveLocalUserFromChatChannelAsync failed", ex->HResult ); + } + }) + .wait(); + } +} + +void ChatIntegrationLayer::OnNewSessionAddressAdded( + __in unsigned int address + ) +{ + m_pDQRNet->LogCommentFormat( L">>>>>>>>>>>>> OnNewSessionAddressAdded (%d)",address ); + Platform::Object^ uniqueConsoleIdentifier = (Platform::Object^)address; + /// Call this when a new console connects. + /// This adds this console to the chat layer + if( m_chatManager != nullptr ) + { + m_chatManager->HandleNewRemoteConsole(uniqueConsoleIdentifier ); + } +} + +Windows::Foundation::Collections::IVectorView^ ChatIntegrationLayer::GetChatUsers() +{ + if( m_chatManager != nullptr ) + { + return m_chatManager->GetChatUsers(); + } + + return nullptr; +} + +bool ChatIntegrationLayer::HasMicFocus() +{ + if( m_chatManager != nullptr ) + { + return m_chatManager->HasMicFocus; + } + + return false; +} + +Platform::Object^ ChatIntegrationLayer::IntToPlatformObject( + __in int val + ) +{ + return (Platform::Object^)val; + + // You can also do the same using a PropertyValue. + //return Windows::Foundation::PropertyValue::CreateInt32(val); +} + +int ChatIntegrationLayer::PlatformObjectToInt( + __in Platform::Object^ uniqueRemoteConsoleIdentifier + ) +{ + return safe_cast( uniqueRemoteConsoleIdentifier ); + + // You can also do the same using a PropertyValue. + //return safe_cast(uniqueRemoteConsoleIdentifier)->GetInt32(); +} + +void ChatIntegrationLayer::HandleChatChannelChanged( + __in uint8 oldChatChannelIndex, + __in uint8 newChatChannelIndex, + __in Microsoft::Xbox::GameChat::ChatUser^ chatUser + ) +{ + // We remember if the local user was currently muted from all channels. And when we switch channels, + // we ensure that the state persists. For remote users, title should implement this themselves + // based on title game design if they want to persist the muting state. + + bool wasUserMuted = false; + IUser^ userBeingRemoved = nullptr; + + if (chatUser != nullptr && chatUser->IsLocal) + { + wasUserMuted = chatUser->IsMuted; + userBeingRemoved = chatUser->User; + if (userBeingRemoved != nullptr) + { + RemoveUserFromChatChannel(oldChatChannelIndex, userBeingRemoved); + AddLocalUserToChatChannel(newChatChannelIndex, userBeingRemoved); + } + } + + // If the local user was muted earlier, get the latest chat users and mute him again on the newly added channel. + if (wasUserMuted && userBeingRemoved != nullptr) + { + auto chatUsers = GetChatUsers(); + if (chatUsers != nullptr ) + { + for (UINT chatUserIndex = 0; chatUserIndex < chatUsers->Size; chatUserIndex++) + { + Microsoft::Xbox::GameChat::ChatUser^ chatUser = chatUsers->GetAt(chatUserIndex); + if( chatUser != nullptr && (chatUser->XboxUserId == userBeingRemoved->XboxUserId) ) + { + m_chatManager->MuteUserFromAllChannels(chatUser); + break; + } + } + } + } +} + +void ChatIntegrationLayer::ChangeChatUserMuteState( + __in Microsoft::Xbox::GameChat::ChatUser^ chatUser + ) +{ + /// Helper function to swap the mute state of a specific chat user + if( m_chatManager != nullptr && chatUser != nullptr) + { + if (chatUser->IsMuted) + { + m_chatManager->UnmuteUserFromAllChannels(chatUser); + } + else + { + m_chatManager->MuteUserFromAllChannels(chatUser); + } + } +} + +Microsoft::Xbox::GameChat::ChatUser^ ChatIntegrationLayer::GetChatUserByXboxUserId( + __in Platform::String^ xboxUserId + ) +{ + Windows::Foundation::Collections::IVectorView^ chatUsers = GetChatUsers(); + for each (Microsoft::Xbox::GameChat::ChatUser^ chatUser in chatUsers) + { + if (chatUser != nullptr && ( chatUser->XboxUserId == xboxUserId ) ) + { + return chatUser; + } + } + + return nullptr; +} + + +bool ChatIntegrationLayer::CompareUniqueConsoleIdentifiers( + __in Platform::Object^ uniqueRemoteConsoleIdentifier1, + __in Platform::Object^ uniqueRemoteConsoleIdentifier2 + ) +{ + if (uniqueRemoteConsoleIdentifier1 == nullptr || uniqueRemoteConsoleIdentifier2 == nullptr) + { + return false; + } + + // uniqueRemoteConsoleIdentifier is a Platform::Object^ and can be cast or unboxed to most types. + // We're using XRNS addresses, which are unsigned ints + unsigned int address1 = safe_cast(uniqueRemoteConsoleIdentifier1); + unsigned int address2 = safe_cast(uniqueRemoteConsoleIdentifier2); + return address1 == address2; +} + +Microsoft::Xbox::GameChat::ChatPerformanceCounters^ ChatIntegrationLayer::GetChatPerformanceCounters() +{ + if( m_chatManager != nullptr && + m_chatManager->ChatSettings != nullptr && + m_chatManager->ChatSettings->PerformanceCountersEnabled ) + { + return m_chatManager->ChatPerformanceCounters; + } + + return nullptr; +} + +void ChatIntegrationLayer::OnControllerPairingChanged( Windows::Xbox::Input::ControllerPairingChangedEventArgs^ args ) +{ +#if 0 + auto controller = args->Controller; + if ( controller ) + { + if ( controller->Type == L"Windows.Xbox.Input.Gamepad" ) + { + // Either add the user or sign one in + User^ user = args->User; + if ( user != nullptr ) + { + g_sampleInstance->GetLoggingUI()->LogCommentToUI("OnControllerPairingChanged: " + user->DisplayInfo->Gamertag); + AddLocalUserToChatChannel( + g_sampleInstance->GetUISelectionChatChannelIndex(), + user + ); + } + } + } +#endif +} + +void ChatIntegrationLayer::ToggleRenderTargetVolume() +{ + // Simple toggle logic to just show usage of LocalRenderTargetVolume property + static bool makeRenderTargetVolumeQuiet = false; + makeRenderTargetVolumeQuiet = !makeRenderTargetVolumeQuiet; + + auto chatUsers = GetChatUsers(); + if (chatUsers != nullptr ) + { + for (UINT chatUserIndex = 0; chatUserIndex < chatUsers->Size; chatUserIndex++) + { + Microsoft::Xbox::GameChat::ChatUser^ chatUser = chatUsers->GetAt(chatUserIndex); + if( chatUser != nullptr && chatUser->IsLocal ) + { + chatUser->LocalRenderTargetVolume = ( makeRenderTargetVolumeQuiet ) ? 0.1f : 1.0f; + } + } + } +} + + +void ChatIntegrationLayer::GetBufferBytes( __in Windows::Storage::Streams::IBuffer^ buffer, __out byte** ppOut ) +{ + if ( ppOut == nullptr || buffer == nullptr ) + { + throw ref new Platform::InvalidArgumentException(); + } + + *ppOut = nullptr; + + Microsoft::WRL::ComPtr srcBufferInspectable(reinterpret_cast( buffer )); + Microsoft::WRL::ComPtr srcBufferByteAccess; + srcBufferInspectable.As(&srcBufferByteAccess); + srcBufferByteAccess->Buffer(ppOut); +} + +Windows::Storage::Streams::IBuffer^ ChatIntegrationLayer::ArrayToBuffer( __in Platform::Array^ array ) +{ + Windows::Storage::Streams::DataWriter^ writer = ref new Windows::Storage::Streams::DataWriter(); + writer->WriteBytes(array); + return writer->DetachBuffer(); +} \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/ChatIntegrationLayer.h b/Minecraft.Client/Durango/Network/ChatIntegrationLayer.h new file mode 100644 index 00000000..80b4e10a --- /dev/null +++ b/Minecraft.Client/Durango/Network/ChatIntegrationLayer.h @@ -0,0 +1,244 @@ +//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +//// PARTICULAR PURPOSE. +//// +//// Copyright (c) Microsoft Corporation. All rights reserved +#pragma once + +//#include "UserController.h" + +class DQRNetworkManager; + +enum ChatPacketType +{ + IncomingPacket = 0, + OutgoingPacket = 1 +}; + +class ChatIntegrationLayer + : public std::enable_shared_from_this // shared_from_this is needed to use a weak ref to 'this' when handling delegate callbacks +{ +public: + ChatIntegrationLayer(); + + DQRNetworkManager *m_pDQRNet; + /// + /// Initializes the chat manager + /// + void InitializeChatManager( + __in bool combineCaptureBuffersIntoSinglePacketEnabled, + __in bool useKinectAsCaptureSource, + __in bool applySoundEffectToCapture, + __in bool applySoundEffectToRender, + DQRNetworkManager *pDQRNet + ); + + /// + /// Shuts down the chat manager + /// + void Shutdown(); + + class AddedUser + { + public: + AddedUser(Windows::Xbox::System::IUser^ user, bool canCaptureAudio); + Windows::Xbox::System::IUser^ m_user; + bool m_canCaptureAudio; + }; + + void AddLocalUser( __in Windows::Xbox::System::IUser^ user ); + void RemoveLocalUser( __in Windows::Xbox::System::IUser^ user ); + void EvaluateDevicesForUser(__in Windows::Xbox::System::IUser^ user ); + + vector m_addedUsers; + CRITICAL_SECTION m_csAddedUsers; + +private: + /// + /// Adds a local user to a specific channel + /// This is helper function waits for the task to complete so shouldn't be called from the UI thread + /// + /// The channel to add the user to + /// The local user to add + void AddLocalUserToChatChannel( + __in uint8 channelIndex, + __in Windows::Xbox::System::IUser^ user + ); + + /// + /// Removes a local user from a specific channel + /// This is helper function waits for the task to complete so shouldn't be called from the UI thread + /// + /// The channel to remove the user from + /// The local user to remove + void RemoveUserFromChatChannel( + __in uint8 channelIndex, + __in Windows::Xbox::System::IUser^ user + ); +public: + + /// + /// Removes a remote console from chat + /// This is helper function waits for the task to complete so shouldn't be called from the UI thread + /// + /// A unique ID for the remote console + void RemoveRemoteConsole( + unsigned int address + ); + + /// + /// Handles incoming chat messages from the game's network layer + /// + /// A buffer containing the chat message + /// A unique ID for the remote console + void OnIncomingChatMessage( + unsigned int sessionAddress, + Platform::Array^ message + ); + + /// + /// Returns a list of chat users in the chat session + /// + Windows::Foundation::Collections::IVectorView^ GetChatUsers(); + + /// + /// Returns true if the game has mic focus. Otherwise another app has mic focus + /// + bool HasMicFocus(); + + /// + /// Helper function to swap the mute state of a specific chat user + /// + void ChangeChatUserMuteState( + __in Microsoft::Xbox::GameChat::ChatUser^ chatUser + ); + + /// + /// Helper function to change the channel of a specific chat user + /// + void HandleChatChannelChanged( + __in uint8 oldChatChannelIndex, + __in uint8 newChatChannelIndex, + __in Microsoft::Xbox::GameChat::ChatUser^ chatUser + ); + + /// + /// Call this when a new console connects. + /// This adds this console to the chat layer + /// + void OnNewSessionAddressAdded( + __in unsigned int address + ); + + /// + /// Adds a list of locally signed in users that have intent to play to the chat session on a specific channel index. + /// Avoid adding any user who is signed in that doesn't have intent to play otherwise users who are biometrically + /// signed in automatically will be added to the chat session + /// + /// The channel to add the users to + /// A list of locally signed in users that have intent to play + void AddAllLocallySignedInUsersToChatClient( + __in uint8 channelIndex, + __in Windows::Foundation::Collections::IVectorView^ locallySignedInUsers + ); + + /// + /// Handles when a debug message is received. Send this to the UI and OutputDebugString. Games should integrate with their existing log system. + /// + /// Contains the debug message to log + void OnDebugMessageReceived( + __in Microsoft::Xbox::GameChat::DebugMessageEventArgs^ args + ); + + /// + /// Send the chat packet to all connected consoles + /// + /// To integrate the Chat DLL in your game, change the following code to use your game's network layer. + /// You will need to isolate chat messages to be unique from the rest of you game's other message types. + /// When args->SendPacketToAllConnectedConsoles is true, your game should send the chat message to each connected console using the game's network layer. + /// It should send the chat message with Reliable UDP if args->SendReliable is true. + /// It should send the chat message in order (if that feature is available) if args->SendInOrder is true + /// + /// Describes the packet to send + void OnOutgoingChatPacketReady( + __in Microsoft::Xbox::GameChat::ChatPacketEventArgs^ args + ); + + /// + /// Example of how to cast an int to a Platform::Object^ + /// + Platform::Object^ IntToPlatformObject( + __in int val + ); + + /// + /// Example of how to cast an Platform::Object^ to an int + /// + int PlatformObjectToInt( + __in Platform::Object^ obj + ); + + /// + /// Helper function to get specific ChatUser by xboxUserId + /// + Microsoft::Xbox::GameChat::ChatUser^ GetChatUserByXboxUserId( + __in Platform::String^ xboxUserId + ); + + /// + /// Helper function to get specific ChatUser by xboxUserId + /// + bool ChatIntegrationLayer::CompareUniqueConsoleIdentifiers( + __in Platform::Object^ uniqueRemoteConsoleIdentifier1, + __in Platform::Object^ uniqueRemoteConsoleIdentifier2 + ); + + /// + /// Helper function to return the ChatPerformanceCounters^ from the ChatManager so perf numbers can be shown on the UI + /// These numbers will only be valid if m_chatManager->ChatSettings->PerformanceCountersEnabled is set to true. + /// + Microsoft::Xbox::GameChat::ChatPerformanceCounters^ GetChatPerformanceCounters(); + + /// + /// Returns a count of the number of chat packets of a specific type that have been either sent or received. + /// It is useful to monitor this number in the UI / logs to debug network issues. + /// + int GameUI_GetPacketStatistic( + __in Microsoft::Xbox::GameChat::ChatMessageType messageType, + __in ChatPacketType chatPacketType + ); + + void OnControllerPairingChanged( + __in Windows::Xbox::Input::ControllerPairingChangedEventArgs^ args + ); + + void ToggleRenderTargetVolume(); + +private: + void GetBufferBytes( __in Windows::Storage::Streams::IBuffer^ buffer, __out byte** ppOut ); + Windows::Storage::Streams::IBuffer^ ArrayToBuffer( __in Platform::Array^ array ); + + Concurrency::critical_section m_lock; + Microsoft::Xbox::GameChat::ChatManager^ m_chatManager; + Windows::Foundation::EventRegistrationToken m_tokenOnDebugMessage; + Windows::Foundation::EventRegistrationToken m_tokenOnOutgoingChatPacketReady; + Windows::Foundation::EventRegistrationToken m_tokenOnCompareUniqueConsoleIdentifiers; + Windows::Foundation::EventRegistrationToken m_tokenResourceAvailabilityChanged; + Windows::Foundation::EventRegistrationToken m_tokenOnPreEncodeAudioBuffer; + Windows::Foundation::EventRegistrationToken m_tokenOnPostDecodeAudioBuffer; + Windows::Foundation::EventRegistrationToken m_tokenUserAudioDeviceAdded; + Windows::Foundation::EventRegistrationToken m_tokenUserAudioDeviceRemoved; + Windows::Foundation::EventRegistrationToken m_tokenUserAudioDeviceChanged; + + // Debug stats for chat packets. Use Debug_GetPacketStatistic() to get the values. + /// It is useful to monitor this number in the UI / logs to debug network issues. + void GameUI_RecordPacketStatistic( + __in Microsoft::Xbox::GameChat::ChatMessageType messageType, + __in ChatPacketType chatPacketType + ); + Concurrency::critical_section m_chatPacketStatsLock; + int m_chatVoicePacketsStatistic[2][(int)Microsoft::Xbox::GameChat::ChatMessageType::InvalidMessage+1]; +}; + +std::shared_ptr GetChatIntegrationLayer(); \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/DQRNetworkManager.cpp b/Minecraft.Client/Durango/Network/DQRNetworkManager.cpp new file mode 100644 index 00000000..f13cc4aa --- /dev/null +++ b/Minecraft.Client/Durango/Network/DQRNetworkManager.cpp @@ -0,0 +1,3093 @@ +#include "stdafx.h" + +#include "DQRNetworkManager.h" +#include "PartyController.h" +#include +#include +#include +#include "..\Minecraft.World\StringHelpers.h" +#include "base64.h" + +#ifdef _DURANGO +#include "..\Minecraft.World\DurangoStats.h" +#endif + +#include "ChatIntegrationLayer.h" + + +using namespace Concurrency; +using namespace Windows::Foundation::Collections; + +DQRNetworkManager::ePartyProcessType DQRNetworkManager::m_partyProcess = DQRNetworkManager::DNM_PARTY_PROCESS_NONE; + +bool DQRNetworkManager::m_inviteReceived = false; +int DQRNetworkManager::m_bootUserIndex; +wstring DQRNetworkManager::m_bootSessionName; +wstring DQRNetworkManager::m_bootServiceConfig; +wstring DQRNetworkManager::m_bootSessionTemplate; +DQRNetworkManager * DQRNetworkManager::s_pDQRManager = NULL; + +//using namespace Windows::Xbox::Networking; + +DQRNetworkManager::SessionInfo::SessionInfo(wstring& sessionName, wstring& serviceConfig, wstring& sessionTemplate) +{ + m_detailsValid = true; + m_sessionName = sessionName; + m_serviceConfig = serviceConfig; + m_sessionTemplate = sessionTemplate; +} + +DQRNetworkManager::SessionInfo::SessionInfo() +{ + m_detailsValid = false; +} + +// This maps internal to extern states, and needs to match element-by-element the eSQRNetworkManagerInternalState enumerated type +const DQRNetworkManager::eDQRNetworkManagerState DQRNetworkManager::m_INTtoEXTStateMappings[DQRNetworkManager::DNM_INT_STATE_COUNT] = +{ + DNM_STATE_INITIALISING, // DNM_INT_STATE_INITIALISING + DNM_STATE_INITIALISE_FAILED, // DNM_INT_STATE_INITIALISE_FAILED + DNM_STATE_IDLE, // DNM_INT_STATE_IDLE + DNM_STATE_HOSTING, // DNM_INT_STATE_HOSTING + DNM_STATE_HOSTING, // DNM_INT_STATE_HOSTING_WAITING_TO_PLAY + DNM_STATE_HOSTING, // DNM_INT_STATE_HOSTING_FAILED + DNM_STATE_JOINING, // DNM_INT_STATE_JOINING + DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS + DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_GET_SDA + DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_WAITING_FOR_SDA + DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_CREATE_SESSION + DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_WAITING_FOR_ACTIVE_SESSION + DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_SENDING_UNRELIABLE + DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_FAILED_TIDY_UP + DNM_STATE_JOINING, // DNM_INT_STATE_JOINING_FAILED + DNM_STATE_STARTING, // DNM_INT_STATE_STARTING + DNM_STATE_PLAYING, // DNM_INT_STATE_PLAYING + DNM_STATE_LEAVING, // DNM_INT_STATE_LEAVING + DNM_STATE_LEAVING, // DNM_INT_STATE_LEAVING_FAILED + DNM_STATE_ENDING, // DNM_INT_STATE_ENDING +}; + +DQRNetworkManager::DQRNetworkManager(IDQRNetworkManagerListener *listener) +{ + s_pDQRManager = this; + m_listener = listener; + m_eventHandlers = ref new DQRNetworkManagerEventHandlers(this); + m_XRNS_Session = nullptr; + m_multiplayerSession = nullptr; + m_sda = nullptr; + m_currentSmallId = 0; + m_hostSmallId = 0; + m_isHosting = false; + m_isInSession = false; + m_partyController = new PartyController(this); + m_partyController->RegisterEventHandlers(); + memset(m_sessionAddressFromSmallId,0,sizeof(m_sessionAddressFromSmallId)); + memset(m_channelFromSmallId,0,sizeof(m_channelFromSmallId)); + + memset(&m_roomSyncData, 0, sizeof(m_roomSyncData)); + memset(m_players, 0, sizeof(m_players)); + + m_CreateSessionThread = NULL; + m_GetFriendPartyThread = NULL; + m_UpdateCustomSessionDataThread = NULL; + + m_CheckPartyInviteThread = NULL; + m_notifyForFullParty = false; + + m_customDataDirtyUpdateTicks = 0; + m_sessionResultCount = 0; + m_sessionSearchResults = NULL; + m_joinSessionUserMask = 0; + m_cancelJoinFromSearchResult = false; + + InitializeCriticalSection(&m_csStateChangeQueue); + InitializeCriticalSection(&m_csHostGamertagResolveResults); + InitializeCriticalSection(&m_csRTSMessageQueueIncoming); + InitializeCriticalSection(&m_csRTSMessageQueueOutgoing); + InitializeCriticalSection(&m_csSendBytes); + InitializeCriticalSection(&m_csVecChatPlayers); + InitializeCriticalSection(&m_csRoomSyncData); + InitializeCriticalSection(&m_csPartyViewVector); + + m_joinSessionXUIDs = ref new Platform::Array(4); + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_IDLE; + m_removeLocalPlayerState = DNM_REMOVE_PLAYER_STATE_IDLE; + + m_playersLeftParty = 0; + + m_handleForcedSignOut = false; + + m_RTS_Stat_totalBytes = 0; + m_RTS_Stat_totalSends = 0; + + m_RTS_DoWorkThread = new C4JThread(DQRNetworkManager::_RTSDoWorkThread, this, "Realtimesession processing"); + m_RTS_DoWorkThread->SetProcessor(CPU_CORE_DQR_REALTIMESESSION); + m_RTS_DoWorkThread->SetPriority(THREAD_PRIORITY_ABOVE_NORMAL); + m_RTS_DoWorkThread->Run(); +} + +void DQRNetworkManager::Initialise() +{ + m_associationTemplate = WXN::SecureDeviceAssociationTemplate::GetTemplateByName( L"MultiplayerUdp" ); + + m_state = DNM_INT_STATE_IDLE; + m_stateExternal = DNM_STATE_IDLE; + + m_chat = GetChatIntegrationLayer(); + m_chat->InitializeChatManager(true, true, false, false, this); +} + +// 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 +void DQRNetworkManager::EnableDebugXBLContext(MXS::XboxLiveContext^ XBLContext) +{ +#ifndef _CONTENT_PACKAGE + // Turn on debug logging to Output debug window for Xbox Services + XBLContext->Settings->DiagnosticsTraceLevel = MXS::XboxServicesDiagnosticsTraceLevel::Verbose; + + // Show service calls from Xbox Services on the UI for easy debugging + XBLContext->Settings->EnableServiceCallRoutedEvents = true; + XBLContext->Settings->ServiceCallRouted += ref new Windows::Foundation::EventHandler( + [=]( Platform::Object^, Microsoft::Xbox::Services::XboxServiceCallRoutedEventArgs^ args ) + { + //if( args->HttpStatus != 200 ) + { + LogComment(L"[URL]: " + args->HttpMethod + " " + args->Url->AbsoluteUri); + if( !args->RequestBody->IsEmpty() ) + { + LogComment(L"[RequestBody]: " + args->RequestBody ); + } + LogComment(L""); + LogComment(L"[Response]: " + args->HttpStatus.ToString() + " " + args->ResponseBody); + LogComment(L""); + } + }); +#endif +} + +// 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 +void DQRNetworkManager::CreateAndJoinSession(int usersMask, unsigned char *customSessionData, unsigned int customSessionDataSize, bool offline) +{ + m_isHosting = true; + m_isInSession = true; + m_isOfflineGame = offline; + m_currentUserMask = usersMask; + SetState(DNM_INT_STATE_HOSTING); + m_customSessionData = customSessionData; + m_customSessionDataSize = customSessionDataSize; + + m_CreateSessionThread = new C4JThread(&DQRNetworkManager::_HostGameThreadProc, this, "Create session"); + m_CreateSessionThread->Run(); +} + +// 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 +// update. Instead the custom data is flagged flagged as dirty here, and it will be considered for updated when next appropriate during a tick. +void DQRNetworkManager::UpdateCustomSessionData() +{ + if( !m_isHosting) return; + if( m_isOfflineGame ) return; + + // Update data on next tick + m_customDataDirtyUpdateTicks = 1; +} + +// This is the main method for finishing joining a game session itself, by any method. +// By the point this is called, we should already have a reserved slot in the game session, by virtue +// 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. +// At this point we need to: +// (1) Set out players state in the session to active, so that they won't timeout & be removed +// (2) Get the network details of the host that we need to connect to +// (3) Set state up so that in the next tick we'll attempt to set up the network communications for this endpoint +// 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 +// 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. +void DQRNetworkManager::JoinSession(int playerMask) +{ + // 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 + m_primaryUser = ProfileManager.GetUser(0); + if( m_primaryUser == nullptr ) + { + app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting primary user\n"); + SetState(DNM_INT_STATE_JOINING_FAILED); + return; + } + + m_primaryUserXboxLiveContext = ref new MXS::XboxLiveContext(m_primaryUser); + if( m_primaryUserXboxLiveContext == nullptr ) + { + app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting primary context\n"); + SetState(DNM_INT_STATE_JOINING_FAILED); + return; + } + EnableDebugXBLContext(m_primaryUserXboxLiveContext); + + SetState(DNM_INT_STATE_JOINING); + + m_partyController->RefreshPartyView(); + m_isInSession = true; + m_isOfflineGame = false; + + for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ ) + { + // 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 + if( playerMask & ( 1 << i ) ) + { + MXSM::MultiplayerSession^ session = nullptr; + + // Get user & xbox live context for this specific local user that we are attempting to join + WXS::User^ joiningUser = ProfileManager.GetUser(i); + if( joiningUser == nullptr ) + { + app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting joining user\n"); + SetState(DNM_INT_STATE_JOINING_FAILED); + return; + } + + MXS::XboxLiveContext^ joiningUserXBLContext = ref new MXS::XboxLiveContext(joiningUser); + if( joiningUserXBLContext == nullptr ) + { + app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting joining context\n"); + SetState(DNM_INT_STATE_JOINING_FAILED); + return; + } + + if( m_partyController->GetPartyView() == nullptr ) + { + app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting party view\n"); + SetState(DNM_INT_STATE_JOINING_FAILED); + return; + } + + // Get a copy of the session document, for this user + auto multiplayerSessionAsync = joiningUserXBLContext->MultiplayerService->GetCurrentSessionAsync( ConvertToMicrosoftXboxServicesMultiplayerSessionReference(m_partyController->GetPartyView()->GameSession)); + create_task(multiplayerSessionAsync).then([&session,this](task t) + { + try + { + session = t.get(); // if t.get() didn't throw, it succeeded + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"MultiplayerSession failed", ex->HResult ); + } + }) + .wait(); + + // 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. + if( session != nullptr ) + { + if(!IsPlayerInSession(joiningUser->XboxUserId, session, NULL) ) + { + app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED didn't find required player in session\n"); + SetState(DNM_INT_STATE_JOINING_FAILED); + return; + } + session->SetCurrentUserStatus(MXSM::MultiplayerSessionMemberStatus::Active); + HRESULT hr = S_OK; + session = WriteSessionHelper( joiningUserXBLContext, session, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr ); + HandleSessionChange(session); + } + else + { + app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED didn't find session\n"); + SetState(DNM_INT_STATE_JOINING_FAILED); + return; + } + } + } + + MXSM::MultiplayerSession^ session = m_multiplayerSession; + + if( session != nullptr ) + { + // Get the secure device address for the host player, and then attempt to create a association with it + int hostSessionIndex = GetSessionIndexAndSmallIdForHost(&m_hostSmallId); + + MXSM::MultiplayerSessionMember^ member = m_multiplayerSession->Members->GetAt(hostSessionIndex); + + m_secureDeviceAddressBase64 = member->SecureDeviceAddressBase64; + + m_isHosting = false; + + sockaddr_in6 localSocketAddressStorage; + + ZeroMemory(&localSocketAddressStorage, sizeof(localSocketAddressStorage)); + + localSocketAddressStorage.sin6_family = AF_INET6; + localSocketAddressStorage.sin6_port = htons(m_associationTemplate->AcceptorSocketDescription->BoundPortRangeLower); + + memcpy(&localSocketAddressStorage.sin6_addr, &in6addr_any, sizeof(in6addr_any)); + + m_localSocketAddress = Platform::ArrayReference(reinterpret_cast(&localSocketAddressStorage), sizeof(localSocketAddressStorage)); + + m_joinCreateSessionAttempts = 0; + + m_joinSmallIdMask = playerMask; + + SetState(DNM_INT_STATE_JOINING_GET_SDA); + } + else + { + app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED getting session\n"); + SetState(DNM_INT_STATE_JOINING_FAILED); + } +} + +void DQRNetworkManager::JoinSessionFromInviteInfo(int playerMask) +{ + // Gather set of XUIDs for the players that we are joining with + for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ ) + { + if( playerMask & ( 1 << i ) ) + { + WXS::User^ user = ProfileManager.GetUser(i); + if( user == nullptr ) + { + return; + } + m_joinSessionXUIDs[i] = user->XboxUserId; + } + else + { + m_joinSessionXUIDs[i] = nullptr; + } + } + + // 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. + // 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 + // set of local players into the game + + bool playerAdded = m_partyController->AddLocalUsersToParty( playerMask, ProfileManager.GetUser(0) ); + + if( playerAdded ) + { + app.DebugPrintf("Joining from invite, but extra non-party user(s) found so waiting for reservations\n"); + // Wait until we get notification via game session ready that our newly added party members have slots, then proceed to join session + m_isInSession = true; + m_startedWaitingForReservationsTime = System::currentTimeMillis(); + m_joinSessionUserMask = playerMask; + m_currentUserMask = 0; + m_isOfflineGame = false; + + SetState(DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS); + } + else + { + app.DebugPrintf("Joining from invite, no extra non-party users required\n"); + // No further players added - continue directly on with joining + JoinSession(playerMask); + } +} + + +// 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 +// 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 +// been detected by the host, and it has added the reserved slots in the sesion that we require. +// +// At this stage we need to: +// (1) Set the player's state in the session to active +// (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 +// host know that there is an active player on this communication channel (we are multiplexing 4 channels, one for each local player) + +bool DQRNetworkManager::AddUsersToSession(int playerMask, MXSM::MultiplayerSessionReference^ sessionRef ) +{ + if( m_isHosting ) + { + return false; + } + + bool bSuccess = true; + for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ ) + { + if( playerMask & ( 1 << i ) ) + { + // We need to get a MultiplayerSession for each player that is joining + + MXSM::MultiplayerSession^ session = nullptr; + + WXS::User^ newUser = ProfileManager.GetUser(i); + if( newUser == nullptr ) + { + bSuccess = false; + continue; + } + MXS::XboxLiveContext^ newUserXBLContext = ref new MXS::XboxLiveContext(newUser); + + auto multiplayerSessionAsync = newUserXBLContext->MultiplayerService->GetCurrentSessionAsync( sessionRef ); + create_task(multiplayerSessionAsync).then([&session,this](task t) + { + try + { + session = t.get(); // if t.get() didn't throw, it succeeded + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"MultiplayerSession failed", ex->HResult ); + } + }) + .wait(); + + // 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. + if( session != nullptr ) + { + int smallId = -1; + if(!IsPlayerInSession(newUser->XboxUserId, session, &smallId) ) + { + bSuccess = false; + continue; + } + session->SetCurrentUserStatus(MXSM::MultiplayerSessionMemberStatus::Active); + HRESULT hr = S_OK; + session = WriteSessionHelper( newUserXBLContext, session, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr ); + HandleSessionChange(session); + + SendSmallId(true, 1 << i); + } + } + } + return bSuccess; +} + +bool DQRNetworkManager::AddLocalPlayerByUserIndex(int userIndex) +{ + // 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 + // + // On the host machine, we: + // + // (1) Get a MPSD for the player that is being added + // (2) Call the join method + // (3) Write the MPSD + // (4) Update the player sync data, and broadcast out to all clients + + // On remote machines, we: + // + // (1) join the party + // (2) the host should (if a slot is available) put a reserved slot in the session and attempt to pull reserved players + // (3) the client will respond to the gamesessionready event that is then received to set the added player to be active + + // 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 + if( m_addLocalPlayerState != DNM_ADD_PLAYER_STATE_IDLE ) return false; + + if( m_isHosting ) + { + WXS::User^ newUser = ProfileManager.GetUser(userIndex); + if( newUser == nullptr ) + { + return false; + } + + if( !m_isOfflineGame ) + { + // This is going to involve some async processing + + MXS::XboxLiveContext^ newUserXBLContext = ref new MXS::XboxLiveContext(newUser); + if( newUserXBLContext == nullptr ) + { + return false; + } + + EnableDebugXBLContext( newUserXBLContext); + + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_PROCESSING; + + auto asyncOp = newUserXBLContext->MultiplayerService->GetCurrentSessionAsync( m_multiplayerSession->SessionReference ); + create_task(asyncOp) + .then([this,newUserXBLContext,userIndex] (task t) + { + try + { + Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ currentSession = t.get(); + + // Don't attempt to join a player if we've no slots left in the session (this will include reserved slots) + if( currentSession->Members->Size < currentSession->SessionConstants->MaxMembersInSession ) + { + int smallId = m_currentSmallId; + MXSM::MultiplayerSessionMember ^member = currentSession->Join(GetNextSmallIdAsJsonString(), false); + currentSession->SetCurrentUserStatus( MXSM::MultiplayerSessionMemberStatus::Active ); + m_currentUserMask |= (1 << userIndex ); + + // Get device ID for current user & set in the session + Platform::String^ secureDeviceAddress = WXN::SecureDeviceAddress::GetLocal()->GetBase64String(); + currentSession->SetCurrentUserSecureDeviceAddressBase64( secureDeviceAddress ); + + HRESULT result; + WriteSessionHelper(newUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, result); // ************ WAITING ************** + + DQRNetworkPlayer* pPlayer = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_LOCAL, true, userIndex, m_XRNS_Session->LocalSessionAddress); + pPlayer->SetSmallId(smallId); + pPlayer->SetName(ProfileManager.GetUser(userIndex)->DisplayInfo->Gamertag->Data()); + pPlayer->SetDisplayName(ProfileManager.GetDisplayName(userIndex)); + pPlayer->SetUID(PlayerUID(ProfileManager.GetUser(userIndex)->XboxUserId->Data())); + + // 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 + m_partyController->AddLocalUsersToParty(1 << userIndex, ProfileManager.GetUser(0)); + + m_addLocalPlayerSuccessPlayer = pPlayer; + m_addLocalPlayerSuccessIndex = userIndex; + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_SUCCESS; + } + else + { + m_addLocalPlayerFailedIndex = userIndex; + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL_FULL; + } + } + catch ( Platform::COMException^ ex ) + { + m_addLocalPlayerFailedIndex = userIndex; + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL; + } + catch ( Platform::Exception ^ex ) + { + m_addLocalPlayerFailedIndex = userIndex; + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL; + } + }); + + return true; + } + else + { + DQRNetworkPlayer* pPlayer = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_LOCAL, true, userIndex, 0); + pPlayer->SetSmallId(m_currentSmallId++); + pPlayer->SetName(ProfileManager.GetUser(userIndex)->DisplayInfo->Gamertag->Data()); + pPlayer->SetDisplayName(ProfileManager.GetDisplayName(userIndex)); + pPlayer->SetUID(PlayerUID(ProfileManager.GetUser(userIndex)->XboxUserId->Data())); + + // TODO - could this add fail? + if(AddRoomSyncPlayer( pPlayer, 0, userIndex)) + { + SendRoomSyncInfo(); + m_listener->HandlePlayerJoined(pPlayer); // This is for notifying of local players joining in an offline game + } + else + { + // Can fail (notably if m_roomSyncData contains players who've left) + assert(0); + return false; + } + } + return true; + } + else + { + // Check if there's any available slots before attempting to add the player to the party. We can still fail joining later if + // the host can't add a reserved slot for us for some reason but better checking on the client side before even attempting. + + WXS::User^ newUser = ProfileManager.GetUser(userIndex); + + MXS::XboxLiveContext^ newUserXBLContext = ref new MXS::XboxLiveContext(newUser); + if( newUserXBLContext == nullptr ) + { + return false; + } + + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_PROCESSING; + auto asyncOp = newUserXBLContext->MultiplayerService->GetCurrentSessionAsync( m_multiplayerSession->SessionReference ); + create_task(asyncOp) + .then([this,newUserXBLContext,userIndex] (task t) + { + try + { + Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ currentSession = t.get(); + + if( currentSession->Members->Size == currentSession->SessionConstants->MaxMembersInSession ) + { + m_addLocalPlayerFailedIndex = userIndex; + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL_FULL; + } + else + { + m_joinSessionUserMask |= (1 << userIndex); + m_joinSessionXUIDs[userIndex] = ProfileManager.GetUser(userIndex)->XboxUserId; + m_partyController->AddLocalUsersToParty(1 << userIndex, ProfileManager.GetUser(0)); + + m_addLocalPlayerSuccessPlayer = NULL; + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_SUCCESS; + } + } + catch( Platform::COMException^ ex ) + { + m_addLocalPlayerFailedIndex = userIndex; + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL; + } + catch ( Platform::Exception ^ex ) + { + m_addLocalPlayerFailedIndex = userIndex; + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_COMPLETE_FAIL; + } + }); + + return true; + } +} + +bool DQRNetworkManager::RemoveLocalPlayerByUserIndex(int userIndex) +{ + // We need to handle this differently for the host and other machines. + // + // On the host machine, we: + // + // (1) Get a MPSD for the player that is being removed + // (2) Call the leave method + // (3) Write the MPSD + // (4) Leave the party + // (5) Update the player sync data, and broadcast out to all clients + + // On remote machines, we: + // + // (1) Get a MPSD for the player that is being removed + // (2) Call the leave method + // (3) Write the MPSD + // (4) Leave the party + // (5) send message to the host that this player has left + // (6) host should respond to this message by removing the player from the player sync data, and notifying all clients of updated players + // (7) the client should respond to the player leaving that will happen and this will actually notify the game that the player has left + + // 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 + + if( m_removeLocalPlayerState != DNM_REMOVE_PLAYER_STATE_IDLE ) return false; + + WXS::User^ leavingUser = ProfileManager.GetUser(userIndex, true); + if( leavingUser == nullptr ) + { + return false; + } + + if( !m_isOfflineGame ) + { + if( m_chat ) + { + m_chat->RemoveLocalUser(leavingUser); + } + MXS::XboxLiveContext^ leavingUserXBLContext = ref new MXS::XboxLiveContext(leavingUser); + if( leavingUserXBLContext == nullptr ) + { + return false; + } + EnableDebugXBLContext( leavingUserXBLContext); + m_removeLocalPlayerState = DNM_REMOVE_PLAYER_STATE_PROCESSING; + auto asyncOp = leavingUserXBLContext->MultiplayerService->GetCurrentSessionAsync( m_multiplayerSession->SessionReference ); + create_task(asyncOp) + .then([this,leavingUserXBLContext,userIndex,leavingUser] (task t) + { + try + { + Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ currentSession = t.get(); + + // Remove from the party + LogComment(L"Removing user from party"); + m_partyController->RemoveLocalUserFromParty(leavingUser); + LogComment(L"Removed user from party, now leaving session"); + + // Then leave & update the session + currentSession->Leave(); + HRESULT result; + WriteSessionHelper(leavingUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, result); + m_currentUserMask &= ~(1<HResult); + + m_removeLocalPlayerState = DNM_REMOVE_PLAYER_STATE_COMPLETE_FAIL; + m_removeLocalPlayerIndex = userIndex; + } + }); + } + else + { + DQRNetworkPlayer* pPlayer = GetLocalPlayerByUserIndex( userIndex ); + RemoveRoomSyncPlayer( pPlayer ); + } + return true; +} + +bool DQRNetworkManager::IsHost() +{ + return m_isHosting; +} + +// 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 +// 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 +// it also requires that it is informed of the state changes leading up to not being in the session, before this should report false. +bool DQRNetworkManager::IsInSession() +{ + return m_isInSession; +} + +// Get count of players currently in the session +int DQRNetworkManager::GetPlayerCount() +{ + return m_roomSyncData.playerCount; +} + +// Get count of players who are in the session, but not local to this machine +int DQRNetworkManager::GetOnlinePlayerCount() +{ + int count = 0; + for( int i = 0; i < MAX_ONLINE_PLAYER_COUNT; i++ ) + { + if( m_players[i] ) + { + if( !m_players[i]->IsLocal() ) + { + count++; + } + } + } + return count; +} + + +DQRNetworkPlayer *DQRNetworkManager::GetPlayerByIndex(int idx) +{ + return m_players[idx]; + +} + +DQRNetworkPlayer *DQRNetworkManager::GetPlayerBySmallId(int idx) +{ + for( int i = 0; i < MAX_ONLINE_PLAYER_COUNT; i++ ) + { + if( m_players[i] ) + { + if( m_players[i]->GetSmallId() == idx) + { + return m_players[i]; + } + } + } + return NULL; +} + +DQRNetworkPlayer *DQRNetworkManager::GetPlayerByXuid(PlayerUID xuid) +{ + for( int i = 0; i < MAX_ONLINE_PLAYER_COUNT; i++ ) + { + if( m_players[i] ) + { + if( m_players[i]->GetUID() == xuid) + { + return m_players[i]; + } + } + } + return NULL; +} + +// Retrieve player display name by gamertag +wstring DQRNetworkManager::GetDisplayNameByGamertag(wstring gamertag) +{ + if (m_displayNames.find(gamertag) != m_displayNames.end()) + { + return m_displayNames[gamertag]; + } + else + { + return gamertag; + } +} + +DQRNetworkPlayer *DQRNetworkManager::GetLocalPlayerByUserIndex(int idx) +{ + for( int i = 0; i < MAX_ONLINE_PLAYER_COUNT; i++ ) + { + if( m_players[i] ) + { + if( m_players[i]->IsLocal() ) + { + if( m_players[i]->GetLocalPlayerIndex() == idx ) + { + return m_players[i]; + } + } + } + } + return NULL; +} + +DQRNetworkPlayer *DQRNetworkManager::GetHostPlayer() +{ + return GetPlayerBySmallId(m_hostSmallId); +} + + +int DQRNetworkManager::GetSessionIndex(DQRNetworkPlayer *player) +{ + for( int i = 0; i < MAX_ONLINE_PLAYER_COUNT; i++ ) + { + if( m_players[i] == player ) + { + return i; + } + } + return 0; +} + +void DQRNetworkManager::SetState(DQRNetworkManager::eDQRNetworkManagerInternalState state) +{ + eDQRNetworkManagerState oldState = m_INTtoEXTStateMappings[m_state]; + eDQRNetworkManagerState newState = m_INTtoEXTStateMappings[state]; + m_state = state; + + // 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 + // 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. + if( newState != oldState ) + { + EnterCriticalSection(&m_csStateChangeQueue); + m_stateChangeQueue.push(StateChangeInfo(oldState,newState)); + LeaveCriticalSection(&m_csStateChangeQueue); + } +} + +DQRNetworkManager::eDQRNetworkManagerState DQRNetworkManager::GetState() +{ + return m_stateExternal;; +} + +void DQRNetworkManager::Tick() +{ + Tick_XRNS(); + Tick_VoiceChat(); + Tick_Party(); + Tick_CustomSessionData(); + Tick_AddAndRemoveLocalPlayers(); + Tick_ResolveGamertags(); + Tick_PartyProcess(); + Tick_StateMachine(); + Tick_CheckInviteParty(); +} + +void DQRNetworkManager::Tick_XRNS() +{ + ProcessRTSMessagesIncoming(); +} + +void DQRNetworkManager::Tick_VoiceChat() +{ +#if 0 + static int chatDumpCount = 0; + chatDumpCount++; + if( ( chatDumpCount % 40 ) == 0 ) + { + if( m_chat ) + { + LogCommentFormat(L"ChatManager: hasFocus:%d\n",m_chat->HasMicFocus()); + IVectorView^ chatUsers = m_chat->GetChatUsers(); + for( int i = 0; i < chatUsers->Size; i++ ) + { + Microsoft::Xbox::GameChat::ChatUser^ chatUser = chatUsers->GetAt(i); + LogCommentFormat(L"local: %d muted: %d type:%s restriction:%s mode:%s volume:%f [xuid:%s]\n", + chatUser->IsLocal,chatUser->IsMuted,chatUser->ParticipantType.ToString()->Data(),chatUser->RestrictionMode.ToString()->Data(), chatUser->TalkingMode.ToString()->Data(),chatUser->Volume, + chatUser->XboxUserId->Data()); + } + } + } +#endif + // If we have to inform the chat integration layer of any players that have joined, do that now + EnterCriticalSection(&m_csVecChatPlayers); + for( int i = 0; i < m_vecChatPlayersJoined.size(); i++ ) + { + int idx = m_vecChatPlayersJoined[i]; + if( m_chat ) + { + WXS::User^ user = ProfileManager.GetUser(idx); + if( user != nullptr ) + { + m_chat->AddLocalUser(user); + } + } + } + m_vecChatPlayersJoined.clear(); + LeaveCriticalSection(&m_csVecChatPlayers); +} + +void DQRNetworkManager::Tick_Party() +{ + // 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 + // 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. + if( m_playersLeftParty ) + { + if( ( System::currentTimeMillis() - m_playersLeftPartyTime ) > PRIMARY_PLAYER_LEAVING_PARTY_WAIT_MS ) + { + // We've waited long enough. User must (hopefully) have just left the party + // Previously we'd switch to offline but that causes a world of pain with forced sign-outs + if( m_playersLeftParty & 1 ) + { + // Before we switch to an offline game, check to see if there is currently a new party. If this is the case and + // we're here, then its because we were added to a party, but didn't receive a gamesessionready event. So if we have + // a party here that we've joined, and the number of players in the party (including us) is more than MAX_PLAYERS_IN_TEMPLATE, + // 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 + // user of this when converting to an offline game + + m_partyController->RefreshPartyView(); + WXM::PartyView^ partyView = m_partyController->GetPartyView(); + if( partyView ) + { + int partySize = partyView->Members->Size; + if( partySize > MAX_PLAYERS_IN_TEMPLATE ) + { + g_NetworkManager.SetFullSessionMessageOnNextSessionChange(); + } + } + + DQRNetworkManager::LogComment(L"Primary player on this system has left the party, switching to offline\n"); + app.SetAction(0, eAppAction_EthernetDisconnected); + } + else + { + // Secondary player(s) leaving, just remove as if they had chosen to exit themselves from the game + for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ ) + { + if( m_playersLeftParty & ( 1 << i ) ) + { + RemoveLocalPlayerByUserIndex(i); + } + } + } + + m_playersLeftParty = 0; + } + } + + // Forced sign out + if (m_handleForcedSignOut) + { + HandleForcedSignOut(); + m_handleForcedSignOut = false; + } +} + +void DQRNetworkManager::Tick_CustomSessionData() +{ + // If there was a thread updaing our custom session data, then clear it up if it is done + if( m_UpdateCustomSessionDataThread != NULL ) + { + if( !m_UpdateCustomSessionDataThread->isRunning() ) + { + delete m_UpdateCustomSessionDataThread; + m_UpdateCustomSessionDataThread = NULL; + } + } + + // If our custom data is dirty, and we aren't currently updating, then kick off a thread to update it + if( m_isHosting && ( !m_isOfflineGame ) ) + { + if( m_UpdateCustomSessionDataThread == NULL ) + { + if( m_customDataDirtyUpdateTicks ) + { + m_customDataDirtyUpdateTicks--; + if( m_customDataDirtyUpdateTicks == 0 ) + { + m_UpdateCustomSessionDataThread = new C4JThread(&DQRNetworkManager::_UpdateCustomSessionDataThreadProc, this, "Updating custom data"); + m_UpdateCustomSessionDataThread->Run(); + } + } + } + } + else + { + m_customDataDirtyUpdateTicks = 0; + } +} + +void DQRNetworkManager::Tick_AddAndRemoveLocalPlayers() +{ + // 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 + if( m_addLocalPlayerState == DNM_ADD_PLAYER_STATE_COMPLETE_SUCCESS ) + { + // 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 + // adding of the player happens as part of a longer process of the host creating us a reserved slot etc. etc. + if( m_addLocalPlayerSuccessPlayer ) + { + if( AddRoomSyncPlayer( m_addLocalPlayerSuccessPlayer, m_XRNS_Session->LocalSessionAddress, m_addLocalPlayerSuccessIndex) ) + { + SendRoomSyncInfo(); + m_listener->HandlePlayerJoined(m_addLocalPlayerSuccessPlayer); // This is notifying local players joining, when online (host only) + } + } + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_IDLE; + } + else if( m_addLocalPlayerState == DNM_ADD_PLAYER_STATE_COMPLETE_FAIL ) + { + m_listener->HandleAddLocalPlayerFailed(m_addLocalPlayerFailedIndex, false); + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_IDLE; + } + else if( m_addLocalPlayerState == DNM_ADD_PLAYER_STATE_COMPLETE_FAIL_FULL ) + { + m_listener->HandleAddLocalPlayerFailed(m_addLocalPlayerFailedIndex, true); + m_addLocalPlayerState = DNM_ADD_PLAYER_STATE_IDLE; + } + + // Similarly for removing local players - avoiding having callbacks from the async task threads, so this aspect of the process is handled here + + if( m_removeLocalPlayerState == DNM_REMOVE_PLAYER_STATE_COMPLETE_SUCCESS || m_removeLocalPlayerState == DNM_REMOVE_PLAYER_STATE_COMPLETE_FAIL ) + { + // Note: we now remove the player from the room sync data even if remove from session/party failed, + // either way we need to clean up + + // On host, directly remove from the player sync data. On client, send a message to the host which will do this + if( m_isHosting) + { + DQRNetworkPlayer* pPlayer = GetLocalPlayerByUserIndex( m_removeLocalPlayerIndex ); + RemoveRoomSyncPlayer( pPlayer ); + SendRoomSyncInfo(); + } + else + { + // 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 + // 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 + // unassign with the host + DQRNetworkPlayer* pPlayer = GetLocalPlayerByUserIndex( m_removeLocalPlayerIndex ); + if( pPlayer ) + { + SendUnassignSmallId(m_removeLocalPlayerIndex); + } + } + m_removeLocalPlayerState = DNM_REMOVE_PLAYER_STATE_IDLE; + } +} + +void DQRNetworkManager::Tick_ResolveGamertags() +{ + // 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 + EnterCriticalSection(&m_csHostGamertagResolveResults); + while( !m_hostGamertagResolveResults.empty() ) + { + HostGamertagResolveDetails *details = m_hostGamertagResolveResults.front(); + + details->m_pPlayer->SetName(details->m_name.c_str()); + + LogComment("Adding a player"); + if( AddRoomSyncPlayer(details->m_pPlayer, details->m_sessionAddress, details->m_channel ) ) + { + LogComment("Adding a player - success"); + 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 + // 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 + // to synchronise out to the clients + if( details->m_sync ) + { + LogComment("Synchronising players with clients"); + SendRoomSyncInfo(); + } + } + else + { + LogComment("Adding a player - failed"); + delete details->m_pPlayer; + + // TODO - what to do if adding a player failed here? + assert(false); + } + + delete details; + m_hostGamertagResolveResults.pop(); + } + LeaveCriticalSection(&m_csHostGamertagResolveResults); +} + +void DQRNetworkManager::Tick_PartyProcess() +{ + // On starting up the game, there's 3 options of what we need to do... + // (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) + // (2) Attempt to join whatever game the party is associated with (this will happen if we were started in response to a party invite) + switch( m_partyProcess ) + { + case DNM_PARTY_PROCESS_NONE: + break; + case DNM_PARTY_PROCESS_JOIN_PARTY: + if( GetBestPartyUserIndex() ) + { + m_listener->HandleInviteReceived(0, new SessionInfo()); + } + break; + case DNM_PARTY_PROCESS_JOIN_SPECIFIED: + m_listener->HandleInviteReceived(m_bootUserIndex, new SessionInfo(m_bootSessionName, m_bootServiceConfig, m_bootSessionTemplate)); + break; + } + m_partyProcess = DNM_PARTY_PROCESS_NONE; +} + +void DQRNetworkManager::Tick_StateMachine() +{ + switch( m_state ) + { + case DNM_INT_STATE_JOINING_GET_SDA: + { + SetState(DNM_INT_STATE_JOINING_WAITING_FOR_SDA); + auto asyncOp = m_associationTemplate->CreateAssociationAsync(WXN::SecureDeviceAddress::FromBase64String(m_secureDeviceAddressBase64), WXN::CreateSecureDeviceAssociationBehavior::Reevaluate); + create_task(asyncOp).then([this](task t) + { + m_sda = nullptr; + try + { + m_sda = t.get(); + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"CreateAssociationAsync failed", ex->HResult ); + } + // 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.) + if( m_sda) + { + m_remoteSocketAddress = ref new Platform::Array(sizeof(SOCKADDR_STORAGE)); + m_sda->GetRemoteSocketAddressBytes(m_remoteSocketAddress); + SetState(DNM_INT_STATE_JOINING_CREATE_SESSION); + } + else + { + SetState(DNM_INT_STATE_JOINING_FAILED); + } + }); + } + break; + case DNM_INT_STATE_JOINING_CREATE_SESSION: + RTS_StartCient(); + SetState(DNM_INT_STATE_JOINING_WAITING_FOR_ACTIVE_SESSION); + break; + case DNM_INT_STATE_JOINING_SENDING_UNRELIABLE: + { + __int64 timeNow = System::currentTimeMillis(); + // m_firstUnreliableSendTime of 0 indicates that we haven't tried sending an unreliable packet yet so need to send one and initialise things + if( m_firstUnreliableSendTime == 0 ) + { + m_firstUnreliableSendTime = timeNow; + m_lastUnreliableSendTime = timeNow; + + SendSmallId(false, m_joinSmallIdMask); + } + else + { + // Timeout if we've exceeded the threshold for this + if( (timeNow - m_firstUnreliableSendTime) > JOIN_PACKET_RESEND_TIMEOUT_MS ) + { + app.DebugPrintf("DNM_INT_STATE_JOINING_FAILED unreliable resend timeout\n"); + SetState(DNM_INT_STATE_JOINING_FAILED); + } + else + { + // Possibly send another packet if it has been long enough + if( (timeNow - m_lastUnreliableSendTime ) > JOIN_PACKET_RESEND_DELAY_MS ) + { + LogComment("Resending unreliable packet\n"); + m_lastUnreliableSendTime = timeNow; + SendSmallId(false, m_joinSmallIdMask); + } + } + } + } + break; + case DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS: + { + // 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 + // 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. + __int64 timeNow = System::currentTimeMillis(); + if( ( timeNow - m_startedWaitingForReservationsTime ) > JOIN_RESERVATION_WAIT_TIME ) + { + SetState(DNM_INT_STATE_JOINING_FAILED_TIDY_UP); + TidyUpFailedJoin(); + } + } + break; + case DNM_INT_STATE_ENDING: + SetState(DNM_INT_STATE_IDLE); + break; + case DNM_INT_STATE_HOSTING_WAITING_TO_PLAY: + delete m_CreateSessionThread; + m_CreateSessionThread = NULL; + // If the game is offline we can transition straight to playing + if (m_isOfflineGame) StartGame(); + break; + case DNM_INT_STATE_JOINING_FAILED: + SetState(DNM_INT_STATE_JOINING_FAILED_TIDY_UP); + TidyUpFailedJoin(); + break; + case DNM_INT_STATE_HOSTING_FAILED: + m_multiplayerSession = nullptr; + LogComment("Error DNM_INT_STATE_HOSTING_FAILED\n"); + SetState(DNM_INT_STATE_IDLE); + break; + case DNM_INT_STATE_LEAVING_FAILED: + m_multiplayerSession = nullptr; + LogComment("Error DNM_INT_STATE_LEAVING_FAILED\n"); + SetState(DNM_INT_STATE_IDLE); + break; + } + + EnterCriticalSection(&m_csStateChangeQueue); + while(m_stateChangeQueue.size() > 0 ) + { + if( m_listener ) + { + m_listener->HandleStateChange(m_stateChangeQueue.front().m_oldState, m_stateChangeQueue.front().m_newState); + if( m_stateChangeQueue.front().m_newState == DNM_STATE_IDLE ) + { + m_isInSession = false; + } + } + m_stateExternal = m_stateChangeQueue.front().m_newState; + m_stateChangeQueue.pop(); + } + LeaveCriticalSection(&m_csStateChangeQueue); +} + +void DQRNetworkManager::Tick_CheckInviteParty() +{ + if( m_inviteReceived ) + { + if( m_CheckPartyInviteThread ) + { + if( !m_CheckPartyInviteThread->isRunning() ) + { + delete m_CheckPartyInviteThread; + m_CheckPartyInviteThread = NULL; + } + } + if( m_CheckPartyInviteThread == NULL ) + { + m_inviteReceived = false; + m_CheckPartyInviteThread = new C4JThread(&DQRNetworkManager::_CheckInviteThreadProc, this, "Check invite thread"); + m_CheckPartyInviteThread->Run(); + } + } +} + +bool DQRNetworkManager::ShouldMessageForFullSession() +{ + bool retval = m_notifyForFullParty; + m_notifyForFullParty = false; + return retval; +} + +void DQRNetworkManager::FlagInvitedToFullSession() +{ + m_notifyForFullParty = true; +} + +void DQRNetworkManager::UnflagInvitedToFullSession() +{ + m_notifyForFullParty = false; +} + +void DQRNetworkManager::AddPlayerFailed(Platform::String ^xuid) +{ + // 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: + // (1) stop waiting + // (2) remove from the party + // (3) inform the game of the failure + LogCommentFormat(L"AddPlayerFailed received, for XUID %s", xuid->Data()); + for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ ) + { + if( m_joinSessionUserMask & ( 1 << i ) ) + { + if( m_joinSessionXUIDs[i] == xuid ) + { + LogCommentFormat(L"AddPlayerFailed received, XUID matched with joining player so handling (index %d)",i); + m_joinSessionUserMask &= ~( 1 << i ); + m_joinSessionXUIDs[i] = nullptr; + m_partyController->RemoveLocalUsersFromParty(m_primaryUser, 1 << i, m_multiplayerSession->SessionReference ); + m_listener->HandleAddLocalPlayerFailed(i, true); + } + } + } +} + +// Utility method to remove the braces at the start and end of a GUID and return the remaining string +Platform::String^ DQRNetworkManager::RemoveBracesFromGuidString(__in Platform::String^ guid ) +{ + std::wstring strGuid = guid->ToString()->Data(); + + if(strGuid.length() > 0 && strGuid[0] == L'{') + { + // Remove the { + strGuid.erase(0, 1); + } + + if(strGuid.length() > 0 && strGuid[strGuid.length() - 1] == L'}') + { + // Remove the } + strGuid.erase(strGuid.end() - 1, strGuid.end()); + } + + return ref new Platform::String(strGuid.c_str()); +} + +void DQRNetworkManager::HandleSessionChange(MXSM::MultiplayerSession^ multiplayerSession) +{ + // 4J-JEV: Fix for Durango #152208 - [CRASH] Code: Gameplay: Title crashes during loading screen after signing in when prompted. + if (multiplayerSession != nullptr) + { + // 4J-JEV: This id is needed to link stats together. + // I thought setting the value from here would be less intrusive than adding an accessor. + ((DurangoStats*)GenericStats::getInstance())->setMultiplayerCorrelationId(multiplayerSession->MultiplayerCorrelationId); + } + else + { + ((DurangoStats*)GenericStats::getInstance())->setMultiplayerCorrelationId( nullptr ); + } + + m_multiplayerSession = multiplayerSession; +} + +// Utility method to update a session on the server, synchronously. +MXSM::MultiplayerSession^ DQRNetworkManager::WriteSessionHelper( MXS::XboxLiveContext^ xboxLiveContext, MXSM::MultiplayerSession^ multiplayerSession, MXSM::MultiplayerSessionWriteMode writeMode, HRESULT& hr ) +{ + if (multiplayerSession == nullptr) + { + return nullptr; + } + + auto asyncOpWriteSessionAsync = xboxLiveContext->MultiplayerService->WriteSessionAsync( multiplayerSession, writeMode ); + + MXSM::MultiplayerSession^ outputMultiplayerSession = nullptr; + + create_task(asyncOpWriteSessionAsync) + .then([&outputMultiplayerSession, &hr](task t) + { + try + { + outputMultiplayerSession = t.get(); // if t.get() didn't throw, it succeeded + } + catch ( Platform::COMException^ ex ) + { + hr = ex->HResult; + } + }) + .wait(); + + if( outputMultiplayerSession != nullptr && + outputMultiplayerSession->SessionReference != nullptr ) + { + app.DebugPrintf( "Session written OK\n" ); + } + + return outputMultiplayerSession; +} + +MXSM::MultiplayerSessionMember^ DQRNetworkManager::GetUserMemberInSession( Windows::Xbox::System::User^ user, MXSM::MultiplayerSession^ session) +{ + for each (MXSM::MultiplayerSessionMember^ member in session->Members) + { + if( _wcsicmp(member->XboxUserId->Data(), user->XboxUserId->Data()) == 0) + { + return member; + } + } + + return nullptr; +} + +bool DQRNetworkManager::IsPlayerInSession( Platform::String^ xboxUserId, MXSM::MultiplayerSession^ session, int *smallId ) +{ + if( session == nullptr ) + { + LogComment(L"IsPlayerInSession: invalid session."); + return false; + } + + for each (MXSM::MultiplayerSessionMember^ member in session->Members) + { + if( _wcsicmp(xboxUserId->Data(), member->XboxUserId->Data() ) == 0 ) + { + if( smallId ) + { + // Get small Id for this member + try + { + Windows::Data::Json::JsonObject^ customConstant = Windows::Data::Json::JsonObject::Parse(member->MemberCustomConstantsJson); + Windows::Data::Json::JsonValue^ customValue = customConstant->GetNamedValue(L"smallId"); + *smallId = (int)(customValue->GetNumber()) & 255; + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"Custom constant Parse/GetNamedValue failed", ex->HResult ); + } + } + + return true; + } + } + + return false; +} + +WXM::MultiplayerSessionReference^ DQRNetworkManager::ConvertToWindowsXboxMultiplayerSessionReference(MXSM::MultiplayerSessionReference^ sessionRef ) +{ + return ref new WXM::MultiplayerSessionReference( + sessionRef->SessionName, + sessionRef->ServiceConfigurationId, + sessionRef->SessionTemplateName + ); +} + +MXSM::MultiplayerSessionReference^ DQRNetworkManager::ConvertToMicrosoftXboxServicesMultiplayerSessionReference(WXM::MultiplayerSessionReference^ sessionRef ) +{ + return ref new MXSM::MultiplayerSessionReference( + sessionRef->ServiceConfigurationId, + sessionRef->SessionTemplateName, + sessionRef->SessionName + ); +} + +// This is called on the client, when new room sync data is received. By comparing this with the existing room sync data, +// this method is able to work out who has been added or removed from the game session, and notify the game of these events. +void DQRNetworkManager::UpdateRoomSyncPlayers(RoomSyncData *pNewSyncData) +{ + vector tempPlayers; + vector newPlayers; + + EnterCriticalSection(&m_csRoomSyncData); + + // Make temporary vector with all the current players in + for( int j = 0; j < m_roomSyncData.playerCount; j++ ) + { + tempPlayers.push_back(m_players[j]); + m_players[j] = NULL; + } + + // For each new player, it's either: + // (1) In the temp player array already, so we already knew about it. + // (2) Not in the temp player array already, so its a new player. Need to inform the game. + // And when we are done, anything left in the temporary vector must be a player that left + for( int i = 0; i < pNewSyncData->playerCount; i++ ) + { + PlayerSyncData *pNewPlayer = &pNewSyncData->players[i]; + bool bAlreadyExisted = false; + for( AUTO_VAR(it, tempPlayers.begin()); it != tempPlayers.end(); it++ ) + { + if( pNewPlayer->m_smallId == (*it)->GetSmallId() ) + { + m_players[i] = (*it); + it = tempPlayers.erase(it); + bAlreadyExisted = true; + break; + } + } + if( !bAlreadyExisted ) + { + // 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 + if( i == 0 ) + { + // Player 0 is always the host + m_players[i] = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_HOST, false, pNewPlayer->m_channel, pNewPlayer->m_sessionAddress); + } + else + { + if( pNewPlayer->m_sessionAddress == m_XRNS_LocalAddress ) + { + m_players[i] = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_LOCAL, false, pNewPlayer->m_channel, pNewPlayer->m_sessionAddress); + } + else + { + m_players[i] = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_REMOTE, false, pNewPlayer->m_channel, pNewPlayer->m_sessionAddress); + } + } + + 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); + + m_players[i]->SetSmallId(pNewPlayer->m_smallId); + m_players[i]->SetName(pNewPlayer->m_name); + m_players[i]->SetUID(PlayerUID(pNewPlayer->m_XUID)); + if (m_players[i]->IsLocal()) + { + m_players[i]->SetDisplayName(ProfileManager.GetDisplayName(i)); + } + + newPlayers.push_back( m_players[i] ); + } + } + for( int i = 0; i < m_roomSyncData.playerCount; i++ ) + { + delete [] m_roomSyncData.players[i].m_XUID; + } + memcpy(&m_roomSyncData, pNewSyncData, sizeof(m_roomSyncData)); + + for( int i = 0; i < tempPlayers.size(); i++ ) + { + m_listener->HandlePlayerLeaving(tempPlayers[i]); + delete tempPlayers[i]; + } + for( int i = 0; i < newPlayers.size(); i++ ) + { + m_listener->HandlePlayerJoined(newPlayers[i]); // For clients, this is where we get notified of local and remote players joining + } + LeaveCriticalSection(&m_csRoomSyncData); +} + +// This is called from the host, to add a new player into the room sync data that is then sent out to the clients. +bool DQRNetworkManager::AddRoomSyncPlayer(DQRNetworkPlayer *pPlayer, unsigned int sessionAddress, int channel) +{ + if( m_roomSyncData.playerCount == MAX_ONLINE_PLAYER_COUNT ) return false; + + EnterCriticalSection(&m_csRoomSyncData); + // 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. + int insertAtIdx = m_roomSyncData.playerCount; + for( int i = 1; i < m_roomSyncData.playerCount; i++ ) + { + if( m_roomSyncData.players[i].m_sessionAddress != sessionAddress ) + { + insertAtIdx = i; + break; + } + else + { + // Don't add the same local index twice + if( m_roomSyncData.players[i].m_channel == channel ) + { + LeaveCriticalSection(&m_csRoomSyncData); + return false; + } + } + } + + // Make room for a new entry... + for( int i = m_roomSyncData.playerCount; i > insertAtIdx; i-- ) + { + m_roomSyncData.players[i] = m_roomSyncData.players[i-1]; + m_players[i] = m_players[i - 1]; + } + m_roomSyncData.players[insertAtIdx].m_channel = channel; + m_roomSyncData.players[insertAtIdx].m_sessionAddress = sessionAddress; + int xuidLength = pPlayer->GetUID().toString().length() + 1; // +1 for terminator + m_roomSyncData.players[insertAtIdx].m_XUID = new wchar_t [xuidLength]; + wcsncpy(m_roomSyncData.players[insertAtIdx].m_XUID, pPlayer->GetUID().toString().c_str(), xuidLength); + m_roomSyncData.players[insertAtIdx].m_smallId = pPlayer->GetSmallId(); + wcscpy_s(m_roomSyncData.players[insertAtIdx].m_name, pPlayer->GetName()); + m_players[insertAtIdx] = pPlayer; + + + m_roomSyncData.playerCount++; + + LeaveCriticalSection(&m_csRoomSyncData); + return true; +} + +// This is called from the host to remove players from the room sync data that is sent out to the clients. +// This method removes any players sharing a session address, and is used when one machine leaves the network game. +void DQRNetworkManager::RemoveRoomSyncPlayersWithSessionAddress(unsigned int sessionAddress) +{ + EnterCriticalSection(&m_csRoomSyncData); + vector removedPlayers; + int iWriteIdx = 0; + for( int i = 0; i < m_roomSyncData.playerCount; i++ ) + { + if( m_roomSyncData.players[i].m_sessionAddress == sessionAddress ) + { + removedPlayers.push_back(m_players[i]); + delete [] m_roomSyncData.players[i].m_XUID; + } + else + { + m_roomSyncData.players[iWriteIdx] = m_roomSyncData.players[i]; + m_players[iWriteIdx] = m_players[i]; + iWriteIdx++; + } + } + m_roomSyncData.playerCount = iWriteIdx; + + for( int i = 0; i < removedPlayers.size(); i++ ) + { + m_listener->HandlePlayerLeaving(removedPlayers[i]); + delete removedPlayers[i]; + memset(&m_roomSyncData.players[m_roomSyncData.playerCount + i], 0, sizeof(PlayerSyncData)); + m_players[m_roomSyncData.playerCount + i] = NULL; + } + LeaveCriticalSection(&m_csRoomSyncData); +} + +// This is called from the host a remove player from the room sync data that is sent out to the clients. +void DQRNetworkManager::RemoveRoomSyncPlayer(DQRNetworkPlayer *pPlayer) +{ + vector removedPlayers; + int iWriteIdx = 0; + for( int i = 0; i < m_roomSyncData.playerCount; i++ ) + { + if( m_players[i] == pPlayer ) + { + removedPlayers.push_back(m_players[i]); + delete [] m_roomSyncData.players[i].m_XUID; + } + else + { + m_roomSyncData.players[iWriteIdx] = m_roomSyncData.players[i]; + m_players[iWriteIdx] = m_players[i]; + iWriteIdx++; + } + } + m_roomSyncData.playerCount = iWriteIdx; + + for( int i = 0; i < removedPlayers.size(); i++ ) + { + m_listener->HandlePlayerLeaving(removedPlayers[i]); + delete removedPlayers[i]; + memset(&m_roomSyncData.players[m_roomSyncData.playerCount + i], 0, sizeof(PlayerSyncData)); + m_players[m_roomSyncData.playerCount + i] = NULL; + } +} + +// Broadcast RoomSyncData to all clients (host only) +void DQRNetworkManager::SendRoomSyncInfo() +{ + if( m_isOfflineGame ) return; + + EnterCriticalSection(&m_csRoomSyncData); + // Calculate the size of data we are going to be sending. This is composed of: + // (1) 2 byte general header + // (2) A single byte internal data tag + // (3) An unsigned int encoding the size of the combined size of all the strings in stage (5) + // (4) The RoomSyncData structure itself + // (5) A wchar NULL terminated string for every active player to encode the XUID + unsigned int xuidBytes = 0; + for( int i = 0 ; i < m_roomSyncData.playerCount; i++ ) + { + LogCommentFormat(L"Sending room sync info for player with XUID %s",m_roomSyncData.players[i].m_XUID); + xuidBytes += ( wcslen(m_roomSyncData.players[i].m_XUID) + 1 ) * sizeof(wchar_t); // 2 bytes per character, including 0 terminator + } + + unsigned int internalBytes = 1 + 4 + sizeof(RoomSyncData) + xuidBytes; + unsigned int totalBytes = 2 + internalBytes; + + unsigned char *data = new unsigned char[totalBytes]; + + uint32_t sizeHigh = internalBytes >> 8; + uint32_t sizeLow = internalBytes & 0xff; + + data[0] = 0x80 | sizeHigh; // Header - flag as internal data (0x80), sending + data[1] = sizeLow; // Data following has the a single byte to say what it is, followed by the room sync data itself + data[2] = DQR_INTERNAL_PLAYER_TABLE; + + memcpy(data + 3, &xuidBytes, 4); + memcpy(data + 7, &m_roomSyncData, sizeof(RoomSyncData)); + unsigned char *pucCurr = data + 7 + sizeof(RoomSyncData); + + for( int i = 0 ; i < m_roomSyncData.playerCount; i++ ) + { + unsigned int thisBytes = ( wcslen(m_roomSyncData.players[i].m_XUID) + 1 ) * sizeof(wchar_t); + memcpy(pucCurr, m_roomSyncData.players[i].m_XUID, thisBytes); + pucCurr += thisBytes; + } + + SendBytesRaw(-1, data, totalBytes, true); + + delete [] data; + LeaveCriticalSection(&m_csRoomSyncData); +} + +// Broadcast the fact that joining a particular XUID has failed (host only) +void DQRNetworkManager::SendAddPlayerFailed(Platform::String^ xuid) +{ + if( m_isOfflineGame ) return; + + // Calculate the size of data we are going to be sending. This is composed of: + // (1) 2 byte general header + // (2) A single byte internal data tag + // (3) An unsigned int encoding the size size of the string + // (5) A wchar NULL terminated string storing the xuid of the player which has failed to join + + unsigned int xuidBytes = sizeof(wchar_t) * ( xuid->Length() + 1 ); + + unsigned int internalBytes = 1 + 4 + xuidBytes; + unsigned int totalBytes = 2 + internalBytes; + + unsigned char *data = new unsigned char[totalBytes]; + + uint32_t sizeHigh = internalBytes >> 8; + uint32_t sizeLow = internalBytes & 0xff; + + data[0] = 0x80 | sizeHigh; // Header - flag as internal data (0x80), sending + data[1] = sizeLow; // Data following has the a single byte to say what it is, followed by the room sync data itself + data[2] = DQR_INTERNAL_ADD_PLAYER_FAILED; + + memcpy(data + 3, &xuidBytes, 4); + memcpy(data + 7, xuid->Data(), xuidBytes); + + SendBytesRaw(-1, data, totalBytes, true); + + delete [] data; +} + +// 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, +// it then knows the association between communication channel & small Id, and that a player is actitve. +void DQRNetworkManager::SendSmallId(bool reliableAndSequential, int playerMask) +{ + // Send data with small Id setting mode - see full comment in DQRNetworkManagerEventHandlers::DataReceivedHandler + BYTE data[8]; + data[0] = 0x80; // Send 6 bytes, internal mode. Send on channel 0 but this is ignored. + data[1] = 6; + data[2] = DQR_INTERNAL_ASSIGN_SMALL_IDS; // Internal data type + data[3] = playerMask; // Actual id + data[4] = 0; + data[5] = 0; + data[6] = 0; + data[7] = 0; + + bool bError = false; + for( int j = 0; j < MAX_LOCAL_PLAYER_COUNT; j++ ) + { + if( playerMask & ( 1 << j ) ) + { + bool bFound = false; + for( unsigned int i = 0; i < m_multiplayerSession->Members->Size; i++ ) + { + MXSM::MultiplayerSessionMember^ member = m_multiplayerSession->Members->GetAt(i); + + if( member->XboxUserId == m_joinSessionXUIDs[j] ) + { + BYTE smallId = 0; + try + { + Windows::Data::Json::JsonObject^ customConstant = Windows::Data::Json::JsonObject::Parse(member->MemberCustomConstantsJson); + Windows::Data::Json::JsonValue^ customValue = customConstant->GetNamedValue(L"smallId"); + smallId = (BYTE)(customValue->GetNumber()); + bFound = true; + } + catch (Platform::COMException^ ex) + { + bError = true; + LogCommentWithError( L"Custom constant Parse/GetNamedValue failed", ex->HResult ); + } + + m_channelFromSmallId[smallId] = j; + + data[ 4 + j ] = smallId; + m_connectionInfoClient.m_smallId[ j ] = smallId; + LogCommentFormat( L"SendSmallId, channel %d, id %d\n", m_channelFromSmallId[smallId], smallId); + } + } + if( !bFound ) + { + bError = true; + } + } + } + + assert(bError == false ); + + SendBytesRaw(0, data, 8, reliableAndSequential); +} + +// This is sent by the client to the host, acting to undo a previous SendSmallId call +void DQRNetworkManager::SendUnassignSmallId(int playerIndex) +{ + LogCommentFormat( L"SendUnassignSmallId, channel %d\n", playerIndex); + // Send data with small Id setting mode - see full comment in DQRNetworkManagerEventHandlers::DataReceivedHandler + BYTE data[4]; + data[0] = 0x80 | ( playerIndex << 5 ); // Send 1 byte, internal mode + data[1] = 1; + data[2] = DQR_INTERNAL_UNASSIGN_SMALL_ID; // Internal data type + + SendBytesRaw(0, data, 3, true); +} + +// This method gets the player index within the session document for a particular small Id. +int DQRNetworkManager::GetSessionIndexForSmallId(unsigned char smallId) +{ + for( unsigned int i = 0; i < m_multiplayerSession->Members->Size; i++ ) + { + MXSM::MultiplayerSessionMember^ member = m_multiplayerSession->Members->GetAt(i); + BYTE smallIdMember = 0; + try + { + Windows::Data::Json::JsonObject^ customConstant = Windows::Data::Json::JsonObject::Parse(member->MemberCustomConstantsJson); + Windows::Data::Json::JsonValue^ customValue = customConstant->GetNamedValue(L"smallId"); + smallIdMember = (BYTE)(customValue->GetNumber()); + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"Custom constant Parse/GetNamedValue failed", ex->HResult ); + } + if( smallIdMember == smallId ) + { + return i; + } + } + return -1; +} + +// 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 +int DQRNetworkManager::GetSessionIndexAndSmallIdForHost(unsigned char *smallId) +{ + for( unsigned int i = 0; i < m_multiplayerSession->Members->Size; i++ ) + { + MXSM::MultiplayerSessionMember^ member = m_multiplayerSession->Members->GetAt(i); + int smallIdMember = 0; + try + { + Windows::Data::Json::JsonObject^ customConstant = Windows::Data::Json::JsonObject::Parse(member->MemberCustomConstantsJson); + Windows::Data::Json::JsonValue^ customValue = customConstant->GetNamedValue(L"smallId"); + smallIdMember = customValue->GetNumber(); + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"Custom constant Parse/GetNamedValue failed", ex->HResult ); + } + if( smallIdMember > 255 ) + { + *smallId = (BYTE)(smallIdMember); + return i; + } + } + return -1; +} + +// Connection info is used to store the current state of a communcation endpoint, on both host & client +DQRConnectionInfo::DQRConnectionInfo() +{ + Reset(); +} + +void DQRConnectionInfo::Reset() +{ + m_currentChannel = 0; + m_internalFlag = false; + m_bytesRemaining = 0; + m_internalDataState = ConnectionState_InternalHeaderByte; + for( int i = 0; i < 4; i++ ) + { + m_smallId[i] = 0; + m_channelActive[i] = false; + } + m_state = ConnectionState_HeaderByte0; + m_initialPacketReceived = false; +} + +// 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 +Platform::String^ DQRNetworkManager::GetNextSmallIdAsJsonString() +{ + Windows::Data::Json::JsonObject^ customConstant = ref new Windows::Data::Json::JsonObject(); + + // 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 + int smallIdToSend = m_currentSmallId; + if( smallIdToSend == m_hostSmallId ) + { + smallIdToSend += 256; + } + customConstant->Insert(L"smallId", Windows::Data::Json::JsonValue::CreateNumberValue( smallIdToSend )); + m_sessionAddressFromSmallId[m_currentSmallId++] = 0; + + return customConstant->Stringify(); +} + +int DQRNetworkManager::_HostGameThreadProc( void* lpParameter ) +{ + DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter; + return pDQR->HostGameThreadProc(); +} + +// This is the main asynchronous method that is called when hosting a game (initiated by calling ::createAndJoinSession). This is called for +// both online & offline games, and it must: +// (1) Create a new multiplayer session document, with active players for all local players starting the game +// (2) Create a new game party, with matching players in it (possibly clearing any existing party) +// (3) Get a device token for the host & assign to the session +// (4) Initialise the room sync data, and inform the game of all local players joining at this stage + +int DQRNetworkManager::HostGameThreadProc() +{ + Platform::String^ sessionName; + + // Get primary user that we are going to create the session with, and create an xbox live context from it + + WXS::User^ primaryUser = ProfileManager.GetUser(0); + m_primaryUser = primaryUser; + m_primaryUserXboxLiveContext = nullptr; + if( primaryUser == nullptr ) + { + SetState(DNM_INT_STATE_HOSTING_FAILED); + return 0; + } + + int localSessionAddress = 0; + + if( !m_isOfflineGame ) + { + MXS::XboxLiveContext^ primaryUserXBLContext = ref new MXS::XboxLiveContext(primaryUser); + m_primaryUserXboxLiveContext = primaryUserXBLContext; + if( primaryUserXBLContext == nullptr ) + { + SetState(DNM_INT_STATE_HOSTING_FAILED); + return 0; + } + + EnableDebugXBLContext(m_primaryUserXboxLiveContext); + + // Get a globally unique identifier to use as our session name that we are going to create + GUID sessionNameGUID; + CoCreateGuid( &sessionNameGUID ); + Platform::Guid sessionGuidName = Platform::Guid( sessionNameGUID ); + sessionName = RemoveBracesFromGuidString( sessionGuidName.ToString() ); + + MXSM::MultiplayerSession^ session = nullptr; + // Actually create the session (locally), using the primary player's context + try + { + session = ref new MXSM::MultiplayerSession( primaryUserXBLContext, + ref new MXSM::MultiplayerSessionReference( SERVICE_CONFIG_ID, MATCH_SESSION_TEMPLATE_NAME, sessionName ), + 0, // this means that it will use the maxMembers specified in the session template. + false, + MXSM::MultiplayerSessionVisibility::Open, + nullptr, + nullptr + ); + } + catch (Platform::COMException^ ex) + { + SetState(DNM_INT_STATE_HOSTING_FAILED); + return 0; + } + + // Set custom property with the game session data + session->SetSessionCustomPropertyJson( L"GameSessionData", Windows::Data::Json::JsonValue::CreateStringValue( base64_encode(m_customSessionData, m_customSessionDataSize ) )->Stringify() ); + + // 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. + Windows::Data::Json::JsonArray ^currentXuidArray = ref new Windows::Data::Json::JsonArray(); + for( int i = 0 ; i < MAX_LOCAL_PLAYER_COUNT; i++ ) + { + if( m_currentUserMask & ( 1 << i ) ) + { + WXS::User^ newUser = ProfileManager.GetUser(i); + if( newUser != nullptr ) + { + currentXuidArray->Append( Windows::Data::Json::JsonValue::CreateStringValue( newUser->XboxUserId ) ); + } + else + { + SetState(DNM_INT_STATE_HOSTING_FAILED); + return 0; + } + } + } + session->SetSessionCustomPropertyJson( L"RoomSyncData", currentXuidArray->Stringify() ); + + // Join session with the primary user for whom the session was created (via their xbox live context) + // user to store the small Id + m_hostSmallId = m_currentSmallId; + m_sessionAddressFromSmallId[m_hostSmallId] = 0; + + session->Join( GetNextSmallIdAsJsonString(), true ); + session->SetCurrentUserStatus( MXSM::MultiplayerSessionMemberStatus::Active ); + + // Get device ID for current user & set in the session + Platform::String^ secureDeviceAddress = WXN::SecureDeviceAddress::GetLocal()->GetBase64String(); + session->SetCurrentUserSecureDeviceAddressBase64( secureDeviceAddress ); + LogComment(L"Setting host secure device: " + secureDeviceAddress + L"\n"); + + // If there is a party currently, then remove all our local players from it so that we can start our own one + m_partyController->RefreshPartyView(); + WXM::PartyView^ partyView = m_partyController->GetPartyView(); + + if( partyView != nullptr ) + { + m_partyController->RemoveLocalUsersFromParty(primaryUser); + } + + m_partyController->AddLocalUsersToParty(m_currentUserMask, primaryUser); + partyView = m_partyController->GetPartyView(); + + // If there is no party by this stage, then we can't proceed. + if( partyView == nullptr ) + { + SetState(DNM_INT_STATE_HOSTING_FAILED); + return 0; + } + m_partyController->SetJoinability(m_listener->IsSessionJoinable()); + + // Add reservations for anyone in the party, who isn't the primary player. Just adding local players for now, but perhaps this should add + // other party members at this stage? + for ( WXM::PartyMember^ member : partyView->Members ) + { + if( member->IsLocal ) + { + if ( _wcsicmp(primaryUser->XboxUserId->Data(), member->XboxUserId->Data()) != 0) + { + session->AddMemberReservation( member->XboxUserId, GetNextSmallIdAsJsonString(), true ); + LogCommentFormat( L"Added %s to session\n", member->XboxUserId->Data()); + } + } + } + + // 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. + HRESULT hr = S_OK; + session = WriteSessionHelper( primaryUserXBLContext, session, MXSM::MultiplayerSessionWriteMode::UpdateOrCreateNew, hr ); + + // 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 + HandleSessionChange(session); + + // If this was successful, then do further set up of the session + if( session != nullptr ) + { + // Get reference to the current user within the session + MXSM::MultiplayerSessionMember^ hostMember = GetUserMemberInSession(primaryUser, session); + + // Set the device token of the host from this user (since we're hosting) + session->SetHostDeviceToken( hostMember->DeviceToken ); + + m_partyController->RegisterGamePlayersChangedEventHandler(); + + // Update session on the server + HRESULT hr = S_OK; + session = WriteSessionHelper( primaryUserXBLContext, session, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr ); + + if ( FAILED(hr) ) + { + app.DebugPrintf("Failed setting host device token"); + + SetState(DNM_INT_STATE_HOSTING_FAILED); + return 0; + } + + // Convert the session reference (in Microsoft::Xbox::Services::Multiplayer namespace) to Windows::Xbox::Multiplayer names space so we can use in the party system + WXM::MultiplayerSessionReference^ winSessionRef = ConvertToWindowsXboxMultiplayerSessionReference(session->SessionReference); + + // Register this multiplayer session as the current session for the party + auto registerSessionAsync = WXM::Party::RegisterGameSessionAsync(primaryUser, winSessionRef); + create_task(registerSessionAsync).then([this](task t) + { + try + { + t.get(); // if t.get() didn't throw, it succeeded + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"RegisterGameSessionAsync failed", ex->HResult ); + + SetState(DNM_INT_STATE_HOSTING_FAILED); + } + }) + .wait(); + if( m_state == DNM_INT_STATE_HOSTING_FAILED) return 0; + + m_partyController->RefreshPartyView(); + + // 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 + // 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 + // 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. + auto pullPlayersAsync = WXM::Party::PullReservedPlayersAsync(primaryUser, winSessionRef); + create_task(pullPlayersAsync).then([this](task t) + { + try + { + t.get(); // if t.get() didn't throw, it succeeded + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"PullReservedPlayersAsync failed", ex->HResult ); + + // SetState(DNM_INT_STATE_HOSTING_FAILED); // This does seem to fail (harmlessly?) with a code of 0x87cc0008 sometimes + } + }) + .wait(); + if( m_state == DNM_INT_STATE_HOSTING_FAILED) return 0; + + sockaddr_in6 localSocketAddressStorage; + + ZeroMemory(&localSocketAddressStorage, sizeof(localSocketAddressStorage)); + + localSocketAddressStorage.sin6_family = AF_INET6; + localSocketAddressStorage.sin6_port = htons(m_associationTemplate->AcceptorSocketDescription->BoundPortRangeLower); + + memcpy(&localSocketAddressStorage.sin6_addr, &in6addr_any, sizeof(in6addr_any)); + + m_localSocketAddress = Platform::ArrayReference(reinterpret_cast(&localSocketAddressStorage), sizeof(localSocketAddressStorage)); + + // 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 + if( m_XRNS_Session ) + { + RTS_Terminate(); + do + { + Sleep(20); + } while ( m_XRNS_Session != nullptr ); + } + + // Start a new session (this actually happens in the RTS processing thread), and wait until it exists + RTS_StartHost(); + do + { + Sleep(20); + } while ( m_XRNS_Session == nullptr ); + + // 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 + // document from each player's xbox live context in turn + + for( int i = 1; i < 4; i++ ) + { + if( m_currentUserMask & ( 1 << i ) ) + { + WXS::User^ extraUser = ProfileManager.GetUser(i); + if( extraUser == nullptr ) + { + SetState(DNM_INT_STATE_HOSTING_FAILED); + return 0; + } + MXS::XboxLiveContext^ extraUserXBLContext = ref new MXS::XboxLiveContext(extraUser); + if( extraUserXBLContext == nullptr ) + { + SetState(DNM_INT_STATE_HOSTING_FAILED); + return 0; + } + + auto asyncOp = extraUserXBLContext->MultiplayerService->GetCurrentSessionAsync( session->SessionReference ); + create_task(asyncOp).then([this, extraUserXBLContext, &session] (task t) + { + try + { + MXSM::MultiplayerSession^ currentSession = t.get(); + + currentSession->SetCurrentUserStatus(MXSM::MultiplayerSessionMemberStatus::Active); + HRESULT hr = S_OK; + session = WriteSessionHelper(extraUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr); + } + catch ( Platform::COMException^ ex ) + { + } + }).wait(); + } + } + + HandleSessionChange(session); + } + else + { + app.DebugPrintf("Error creating session"); + SetState(DNM_INT_STATE_HOSTING_FAILED); + return 0; + } + m_XRNS_LocalAddress = m_XRNS_Session->LocalSessionAddress; + m_XRNS_OldestAddress = m_XRNS_Session->OldestSessionAddress; + localSessionAddress = m_XRNS_Session->LocalSessionAddress; + } + else + { + m_hostSmallId = m_currentSmallId; + // Offline game - get small id incremented to the same value that it would have ended up with + for( int i = 0; i < 4; i++ ) + { + if( m_currentUserMask & ( 1 << i ) ) + { + m_currentSmallId++; + } + } + } + + // 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 + int smallId = m_hostSmallId; + for( int i = 0; i < 4; i++ ) + { + // If user is present in mask and currently signed in + if( m_currentUserMask & ( 1 << i ) && ProfileManager.IsSignedIn(i)) + { + auto user = ProfileManager.GetUser(i); + wstring displayName = ProfileManager.GetDisplayName(i); + + DQRNetworkPlayer* pPlayer = new DQRNetworkPlayer(this, ( ( smallId == m_hostSmallId ) ? DQRNetworkPlayer::DNP_TYPE_HOST : DQRNetworkPlayer::DNP_TYPE_LOCAL ), true, i, localSessionAddress); + pPlayer->SetSmallId(smallId); + pPlayer->SetName(user->DisplayInfo->Gamertag->Data()); + pPlayer->SetDisplayName(displayName); + pPlayer->SetUID(PlayerUID(user->XboxUserId->Data())); + + AddRoomSyncPlayer( pPlayer, localSessionAddress, i); + + m_listener->HandlePlayerJoined(pPlayer); + + smallId++; + } + } + + SetState(DNM_INT_STATE_HOSTING_WAITING_TO_PLAY); + + return 0; +} + +int DQRNetworkManager::_LeaveRoomThreadProc( void* lpParameter ) +{ + + DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter; + return pDQR->LeaveRoomThreadProc(); +} + +int DQRNetworkManager::LeaveRoomThreadProc() +{ + WXS::User^ primaryUser = ProfileManager.GetUser(0, true); + + LogComment(L"LeaveRoomThreadProc"); + + if( !m_isOfflineGame && m_multiplayerSession != nullptr ) + { + // 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 + // or else if we subsequently become a client in another game, things will get confused + if( m_isHosting ) + { + m_partyController->DisassociateSessionFromParty( m_multiplayerSession->SessionReference ); + + m_partyController->UnregisterGamePlayersEventHandler(); + } + + // Remove local players from the party + m_partyController->RemoveLocalUsersFromParty(primaryUser, m_currentUserMask, m_multiplayerSession->SessionReference); + + // Request RTS to be terminated + RTS_Terminate(); + + // Now leave the game session. We need to do this for each player in turn, writing each time + bool bError = false; + for( int i = 0; i < 4; i++ ) + { + if( m_currentUserMask & ( 1 << i ) ) + { + LogCommentFormat(L"DQRNetworkManager::LeaveRoomThreadProc: Attempting to remove player %d from session",i); + WXS::User^ leavingUser = ProfileManager.GetUser(i); + if( leavingUser == nullptr ) + { + bError = true; + continue; + } + if( m_chat ) + { + m_chat->RemoveLocalUser(leavingUser); + } + + MXS::XboxLiveContext^ leavingUserXBLContext = ref new MXS::XboxLiveContext(leavingUser); + if( leavingUserXBLContext == nullptr ) + { + bError = true; + continue; + } + + auto asyncOp = leavingUserXBLContext->MultiplayerService->GetCurrentSessionAsync( m_multiplayerSession->SessionReference ); + create_task(asyncOp).then([this, leavingUserXBLContext,&bError] (task t) + { + try + { + MXSM::MultiplayerSession^ currentSession = t.get(); + + if (currentSession != nullptr) + { + currentSession->Leave(); + HRESULT hr = S_OK; + WriteSessionHelper(leavingUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr); + } + else + { + // Specific error case where session is gone, this generally happens if all players have left (e.g. party of one and player signs out) + app.DebugPrintf("DQRNetworkManager::LeaveRoomThreadProc: Error removing a player from the session, session was null (user probably signed out)"); + } + } + catch ( Platform::COMException^ ex ) + { + bError = true; + } + }).wait(); + } + } + + if ( bError ) + { + app.DebugPrintf("DQRNetworkManager::LeaveRoomThreadProc: Error removing a player from the session"); + assert(true); + } + } + + ProfileManager.CompleteDeferredSignouts(); + app.DebugPrintf("DQRNetworkManager::LeaveRoomThreadProc: Completing deferred sign out"); + ProfileManager.SetDeferredSignoutEnabled(false); + + m_multiplayerSession = nullptr; + m_currentUserMask = 0; + m_joinSessionUserMask = 0; + UnflagInvitedToFullSession(); + + SetState(DNM_INT_STATE_ENDING); + + return 0; +} + +int DQRNetworkManager::_TidyUpJoinThreadProc( void* lpParameter ) +{ + + DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter; + return pDQR->TidyUpJoinThreadProc(); +} + +// This method is called when joining the game fails in some situations, ie when we attempted to join with a +// set of players and not all managed to get into the session. Tidies things up by removing any players that +// Did get into the session, and removing any players we added to the party. +int DQRNetworkManager::TidyUpJoinThreadProc() +{ + WXS::User^ primaryUser = ProfileManager.GetUser(0); + + LogComment(L"TidyUpJoinThreadProc"); + + if( primaryUser != nullptr ) + { + // 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 + m_partyController->RefreshPartyView(); + MXSM::MultiplayerSessionReference ^sessionRef = m_partyController->GetGamePartySessionReference(); + m_partyController->RemoveLocalUsersFromParty(primaryUser); + + if( sessionRef != nullptr ) + { + // Now leave the game session. We need to do this for each player in turn, writing each time. Consider that any of the joining + // members *may* have a slot (reserved or active) depending on how far progressed the joining got. + bool bError = false; + + // 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, + // but we Do have m_currentUserMask set. Any of these should be considered users we should be attempting to remove from the session. + int removeSessionMask = m_joinSessionUserMask | m_currentUserMask; + for( int i = 0; i < 4; i++ ) + { + if( removeSessionMask & ( 1 << i ) ) + { + LogCommentFormat(L"Attempting to remove player %d from session",i); + WXS::User^ leavingUser = ProfileManager.GetUser(i); + if( leavingUser == nullptr ) + { + bError = true; + continue; + } + MXS::XboxLiveContext^ leavingUserXBLContext = ref new MXS::XboxLiveContext(leavingUser); + if( leavingUserXBLContext == nullptr ) + { + bError = true; + continue; + } + + EnableDebugXBLContext(leavingUserXBLContext); + + auto asyncOp = leavingUserXBLContext->MultiplayerService->GetCurrentSessionAsync( sessionRef ); + create_task(asyncOp).then([this, leavingUser, leavingUserXBLContext,&bError] (task t) + { + try + { + MXSM::MultiplayerSession^ currentSession = t.get(); + + if( currentSession ) + { + bool bFound = false; + for( int i = 0; i < currentSession->Members->Size; i++ ) + { + if( currentSession->Members->GetAt(i)->XboxUserId == leavingUser->XboxUserId ) + { + bFound = true; + break; + } + } + + if( bFound ) + { + currentSession->Leave(); + HRESULT hr = S_OK; + WriteSessionHelper(leavingUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr); + } + } + } + catch ( Platform::COMException^ ex ) + { + bError = true; + } + }).wait(); + } + } + + if ( bError ) + { + app.DebugPrintf("Error removing a player from the session"); + assert(true); + } + } + } + + m_multiplayerSession = nullptr; + m_currentUserMask = 0; + m_joinSessionUserMask = 0; + + // 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 + SetState(DNM_INT_STATE_IDLE); + + return 0; +} + +int DQRNetworkManager::_UpdateCustomSessionDataThreadProc( void* lpParameter ) +{ + + DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter; + return pDQR->UpdateCustomSessionDataThreadProc(); +} + +// 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 +// 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 +// represents, however we use it to synchronise a GameSessionData structure containing details of the current game session. This data is base 64 encoded +// and then put in the session document as a custom value as a json string name/value pair. +int DQRNetworkManager::UpdateCustomSessionDataThreadProc() +{ + LogComment(L"Starting thread to update custom data"); + WXS::User^ primaryUser = ProfileManager.GetUser(0); + + if( primaryUser == nullptr ) + { + return 0; + } + MXS::XboxLiveContext^ primaryUserXBLContext = ref new MXS::XboxLiveContext(primaryUser); + if( primaryUserXBLContext == nullptr ) + { + return 0; + } + + MXSM::MultiplayerSession^ session = nullptr; + + auto multiplayerSessionAsync = primaryUserXBLContext->MultiplayerService->GetCurrentSessionAsync( m_multiplayerSession->SessionReference ); + create_task(multiplayerSessionAsync).then([&session,this](task t) + { + try + { + session = t.get(); // if t.get() didn't throw, it succeeded + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"MultiplayerSession failed", ex->HResult ); + } + }) + .wait(); + + if( session != nullptr ) + { + // Set custom property with the game session data + session->SetSessionCustomPropertyJson( L"GameSessionData", Windows::Data::Json::JsonValue::CreateStringValue( base64_encode(m_customSessionData, m_customSessionDataSize ) )->Stringify() ); + + // Update XUIDs from room sync data which we also have as custom data + EnterCriticalSection(&m_csRoomSyncData); + Windows::Data::Json::JsonArray ^currentXuidArray = ref new Windows::Data::Json::JsonArray(); + for( int i = 0 ; i < m_roomSyncData.playerCount; i++ ) + { + currentXuidArray->Append( Windows::Data::Json::JsonValue::CreateStringValue( ref new Platform::String(m_roomSyncData.players[i].m_XUID ) ) ); + } + session->SetSessionCustomPropertyJson( L"RoomSyncData", currentXuidArray->Stringify() ); + LeaveCriticalSection(&m_csRoomSyncData); + + HRESULT hr = S_OK; + WriteSessionHelper( primaryUserXBLContext, session, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr ); + + // If we didn't succeed, then retry later + if( hr != S_OK ) + { + if( m_customDataDirtyUpdateTicks == 0 ) + { + LogCommentFormat(L"Error updating custom data 0x%x, will retry", hr); + m_customDataDirtyUpdateTicks = 20; + } + } + } + + LogComment(L"Ending thread to update custom data"); + + return 0; +} + +int DQRNetworkManager::_CheckInviteThreadProc(void* lpParameter) +{ + DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter; + return pDQR->CheckInviteThreadProc(); +} + +int DQRNetworkManager::CheckInviteThreadProc() +{ + for( int i = 4; i < 12; i++ ) + { + WXS::User^ anyUser = InputManager.GetUserForGamepad(i); + if( anyUser ) + { + m_partyController->CheckPartySessionFull(anyUser); + return 0; + } + } + return 0; +} + +// 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 +// has called HandlePlayerRemovedFromParty, if the player is being removed from one party only to be placed in another. If Only +// HandlePlayerRemovedFromParty is called (with no following HandleNewPartyFoundForPlayer), then we must assume that the player was +// 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 +// to pass after a user is removed from a party before processing it. +void DQRNetworkManager::HandleNewPartyFoundForPlayer() +{ + LogCommentFormat(L"HandleNewPartyFoundForPlayer called after %d ms\n", System::currentTimeMillis() - m_playersLeftPartyTime); + m_playersLeftParty = 0; +} + +void DQRNetworkManager::HandlePlayerRemovedFromParty(int playerMask) +{ + if( m_state == DNM_INT_STATE_PLAYING ) + { + if( m_isHosting ) + { + // We should check that they're still here, they might already have left + for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ ) + { + if( playerMask & ( 1 << i ) ) + { + if (!ProfileManager.IsSignedIn(i)) + { + // Player is already gone so remove them from the mask + playerMask &= ~(1 << i); + } + } + } + + LogCommentFormat(L"HandlePlayerRemovedFromParty called mask %d\n", playerMask); + m_playersLeftParty |= playerMask; + m_playersLeftPartyTime = System::currentTimeMillis(); + + // Check for forced sign out + CheckForcedSignOut(); + } + else + { + // 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 + if( playerMask & 1 ) + { + DQRNetworkManager::LogComment(L"Primary player on this system has left the party - leaving game\n"); + app.SetDisconnectReason(DisconnectPacket::eDisconnect_ExitedGame); + LeaveRoom(); + } + else + { + // Secondary player(s) leaving, just remove as if they had chosen to exit themselves from the game + for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ ) + { + if( playerMask & ( 1 << i ) ) + { + RemoveLocalPlayerByUserIndex(i); + } + } + } + } + } + else + { + LogComment(L"HandlePlayerRemovedFromParty called, ignoring as not in state DNM_INT_STATE_PLAYING\n"); + } +} + +// 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 +// 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 +// 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. +void DQRNetworkManager::ChatPlayerJoined(int idx) +{ + EnterCriticalSection(&m_csVecChatPlayers); + m_vecChatPlayersJoined.push_back(idx); + LeaveCriticalSection(&m_csVecChatPlayers); +} + +bool DQRNetworkManager::IsReadyToPlayOrIdle() +{ + return (( m_state == DNM_INT_STATE_HOSTING_WAITING_TO_PLAY ) || ( m_state == DNM_INT_STATE_PLAYING ) || ( m_state == DNM_INT_STATE_IDLE ) ); +} + +void DQRNetworkManager::StartGame() +{ + SetState( DNM_INT_STATE_STARTING); + SetState( DNM_INT_STATE_PLAYING); +} + +// Removes all local players from a network game (aysnchronously) and leaves the game +void DQRNetworkManager::LeaveRoom() +{ + m_playersLeftParty = 0; + if( ( m_state == DNM_INT_STATE_HOSTING_WAITING_TO_PLAY ) || + ( m_state == DNM_INT_STATE_STARTING ) || + ( m_state == DNM_INT_STATE_PLAYING ) ) + { + SetState(DNM_INT_STATE_LEAVING); + + // Empty out the room of players & notify the game (needs separate loops for each as HandlePlayerLeaving checks the players) + for( int i = 0; i < m_roomSyncData.playerCount; i++ ) + { + m_listener->HandlePlayerLeaving(m_players[i]); + } + + for( int i = 0; i < m_roomSyncData.playerCount; i++ ) + { + delete m_players[i]; + m_players[i] = NULL; + } + memset(&m_roomSyncData, 0, sizeof(m_roomSyncData)); + m_displayNames.clear(); + + m_LeaveRoomThread = new C4JThread(&DQRNetworkManager::_LeaveRoomThreadProc, this, "Leave room"); + m_LeaveRoomThread->Run(); + } + else + { + SetState(DNM_INT_STATE_IDLE); + } +} + +void DQRNetworkManager::TidyUpFailedJoin() +{ + m_TidyUpJoinThread = new C4JThread(&DQRNetworkManager::_TidyUpJoinThreadProc, this, "Tidying up failed join"); + m_TidyUpJoinThread->Run(); +} + +// 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, +// and see if it is full of people that aren't us +void DQRNetworkManager::SetInviteReceivedFlag() +{ + m_inviteReceived = true; +} + +// 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. +void DQRNetworkManager::SetPartyProcessJoinParty() +{ + m_partyProcess = DQRNetworkManager::DNM_PARTY_PROCESS_JOIN_PARTY; +} + +// 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. +void DQRNetworkManager::SetPartyProcessJoinSession(int bootUserIndex, Platform::String^ bootSessionName, Platform::String^ bootServiceConfig, Platform::String^ bootSessionTemplate) +{ + if( s_pDQRManager ) + { + // Don't do anything if we are already in this session + if( s_pDQRManager->m_multiplayerSession ) + { + if( s_pDQRManager->m_multiplayerSession->SessionReference->SessionName == bootSessionName ) + { + return; + } + } + } + m_bootUserIndex = bootUserIndex; + m_bootSessionName = bootSessionName->Data(); + m_bootServiceConfig = bootServiceConfig->Data(); + m_bootSessionTemplate = bootSessionTemplate->Data(); + m_partyProcess = DQRNetworkManager::DNM_PARTY_PROCESS_JOIN_SPECIFIED; +} + +// Brings up the system GUI so that invites can be sent to invite friends to join the current game party +void DQRNetworkManager::SendInviteGUI(int quadrant) +{ + Windows::Xbox::UI::SystemUI::ShowSendInvitesAsync(ProfileManager.GetUser(quadrant)); +} + +bool DQRNetworkManager::IsAddingPlayer() +{ + return ( m_addLocalPlayerState != DNM_ADD_PLAYER_STATE_IDLE ); +} + +// Attempt to join a party that we have found, for a given set of local players. This adds the specified users to the +// 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 +// slots are reserved, we will receive a game session ready notification through the party manager & can take things from there. +bool DQRNetworkManager::JoinPartyFromSearchResult(SessionSearchResult *searchResult, int playerMask) +{ + // Assume that primary player is always player 0 + if( ( playerMask & 1 ) == 0 ) + { + return false; + } + + // Gather set of XUIDs for the players that we are joining with + for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ ) + { + if( playerMask & ( 1 << i ) ) + { + WXS::User^ user = ProfileManager.GetUser(i); + if( user == nullptr ) + { + return false; + } + m_joinSessionXUIDs[i] = user->XboxUserId; + } + else + { + m_joinSessionXUIDs[i] = nullptr; + } + } + + // Set up primary user & context to be used generally by other things once we are in the game + m_primaryUser = ProfileManager.GetUser(0); + if( m_primaryUser == nullptr ) + { + return false; + } + + m_primaryUserXboxLiveContext = ref new MXS::XboxLiveContext(m_primaryUser); + if( m_primaryUserXboxLiveContext == nullptr ) + { + return false; + } + + m_currentUserMask = 0; + m_joinSessionUserMask = playerMask; + m_isInSession = true; + m_isOfflineGame = false; + + m_startedWaitingForReservationsTime = System::currentTimeMillis(); + SetState(DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS); + + // There is a small window that we currently allow cancelling + m_cancelJoinFromSearchResult = false; + + // Before we join the party, check if any of the joining players are in the session already, and remove. Joining the game cleanly depends + // 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 + // if we are already in the session + + MXSM::MultiplayerSessionReference ^sessionRef = ref new MXSM::MultiplayerSessionReference(SERVICE_CONFIG_ID, MATCH_SESSION_TEMPLATE_NAME, ref new Platform::String(searchResult->m_sessionName.c_str())); + + bool shownCancelScreen = false; + if( sessionRef != nullptr ) + { + // Allow 2 seconds before we let the player cancel + __int64 allowCancelTime = System::currentTimeMillis() + (1000 * 2); + + // Now leave the game session. We need to do this for each player in turn, writing each time. Consider that any of the joining + // members *may* have a slot (reserved or active) depending on how far progressed the joining got. + bool bError = false; + for( int i = 0; i < 4; i++ ) + { + if( playerMask & ( 1 << i ) ) + { + LogCommentFormat(L"Attempting to remove player %d from session",i); + WXS::User^ leavingUser = ProfileManager.GetUser(i); + if( leavingUser == nullptr ) + { + bError = true; + continue; + } + MXS::XboxLiveContext^ leavingUserXBLContext = ref new MXS::XboxLiveContext(leavingUser); + if( leavingUserXBLContext == nullptr ) + { + bError = true; + continue; + } + + EnableDebugXBLContext(leavingUserXBLContext); + + auto asyncOp = leavingUserXBLContext->MultiplayerService->GetCurrentSessionAsync( sessionRef ); + auto ccTask = create_task(asyncOp).then([this, leavingUser, leavingUserXBLContext,&bError] (task t) + { + try + { + MXSM::MultiplayerSession^ currentSession = t.get(); + + if( currentSession == nullptr ) + { + bError = true; + } + else + { + bool bFound = false; + for( int i = 0; i < currentSession->Members->Size; i++ ) + { + if( currentSession->Members->GetAt(i)->XboxUserId == leavingUser->XboxUserId ) + { + bFound = true; + break; + } + } + + if( bFound ) + { + currentSession->Leave(); + HRESULT hr = S_OK; + WriteSessionHelper(leavingUserXBLContext, currentSession, MXSM::MultiplayerSessionWriteMode::UpdateExisting, hr); + } + } + } + catch ( Platform::COMException^ ex ) + { + bError = true; + } + }); + + while(!ccTask.is_done()) + { + // Check for being disconnected from the network + if(!ProfileManager.IsSignedInLive(i) || m_cancelJoinFromSearchResult) + { + asyncOp->Cancel(); + bError = true; + break; + } + + __int64 currentTime = System::currentTimeMillis(); + if( currentTime > allowCancelTime) + { + shownCancelScreen = true; + + ConnectionProgressParams *param = new ConnectionProgressParams(); + param->iPad = 0; + param->stringId = -1; + param->showTooltips = true; + param->cancelFunc = &g_NetworkManager.CancelJoinGame; + param->cancelFuncParam = &g_NetworkManager; + + // Show a progress spinner so that we can cancel the join. + ui.NavigateToScene(0, eUIScene_ConnectingProgress, param); + + allowCancelTime = LONGLONG_MAX; + } + + // Tick some simple things + ProfileManager.Tick(); + StorageManager.Tick(); + InputManager.Tick(); + RenderManager.Tick(); + ui.tick(); + ui.render(); + RenderManager.Present(); + } + // Check for being disconnected from the network + if(!ProfileManager.IsSignedInLive(i)) + { + bError = true; + break; + } + } + } + + if ( bError && !m_cancelJoinFromSearchResult ) + { + app.DebugPrintf("Error removing a player from the session"); + assert(true); + } + } + + if(shownCancelScreen) + { + ui.NavigateToScene(0,eUIScene_Timer); + } + + if(m_cancelJoinFromSearchResult) + { + SetState(DNM_INT_STATE_IDLE); + m_cancelJoinFromSearchResult= false; + + return false; + } + + m_cancelJoinFromSearchResult = false; + + + // Now we join the party with each of joining players, and then wait to be informed of our reserved slots + for( int i = 0; i < MAX_LOCAL_PLAYER_COUNT; i++ ) + { + if( playerMask & ( 1 << i ) ) + { + WXS::User^ user = ProfileManager.GetUser(i); + + auto joinPartyAsync = WXM::Party::JoinPartyByIdAsync(user, ref new Platform::String(searchResult->m_partyId.c_str())); + auto ccTask = create_task(joinPartyAsync).then([this](task t) + { + try + { + t.get(); // if t.get() didn't throw, it succeeded + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"JoinPartyByIdAsync failed", ex->HResult ); + SetState(DNM_INT_STATE_JOINING_FAILED); + } + }); + + + while(!ccTask.is_done()) + { + // Check for being disconnected from the network + if(!ProfileManager.IsSignedInLive(i)) + { + joinPartyAsync->Cancel(); + break; + } + + // Tick some simple things + ProfileManager.Tick(); + StorageManager.Tick(); + InputManager.Tick(); + RenderManager.Tick(); + ui.tick(); + ui.render(); + RenderManager.Present(); + } + // Check for being disconnected from the network + if(!ProfileManager.IsSignedInLive(i)) + { + break; + } + } + } + + return ( m_state == DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS ); +} + +void DQRNetworkManager::CancelJoinPartyFromSearchResult() +{ + m_cancelJoinFromSearchResult = true; +} + +// 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. +bool DQRNetworkManager::GetBestPartyUserIndex() +{ + bool playerFound = false; + + // Use context from any user at all for this + WXS::User ^anyUser = nullptr; + if( WXS::User::Users->Size > 0 ) + { + anyUser = WXS::User::Users->GetAt(0); + } + if( anyUser == nullptr ) return 0; + MXS::XboxLiveContext^ xboxLiveContext = ref new MXS::XboxLiveContext(anyUser); + + m_partyController->RefreshPartyView(); + MXSM::MultiplayerSessionReference ^sessionRef = m_partyController->GetGamePartySessionReference(); + if( sessionRef != nullptr ) + { + auto asyncOp = xboxLiveContext->MultiplayerService->GetCurrentSessionAsync( sessionRef ); + create_task(asyncOp) + .then([this,&playerFound] (task t) + { + try + { + Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ session = t.get(); + + int playerIdx = -1; + 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 + { + MXSM::MultiplayerSessionMember^ member = session->Members->GetAt(i); + for( int j = 0; j < MAX_LOCAL_PLAYERS; j++ ) + { + WXS::User ^user = ProfileManager.GetUser(j); + if( user != nullptr ) + { + if( user->XboxUserId == member->XboxUserId ) + { + DQRNetworkManager::LogCommentFormat(L"Found already signed in player %s to act as invite recipient",member->XboxUserId->Data()); + playerIdx = j; + playerFound = true; + break; + } + } + } + } + if( playerIdx == -1 ) // If nothing found, second pass through to attempt to find a controller that matches + { + for( int i = 0; i < session->Members->Size; i++ ) + { + MXSM::MultiplayerSessionMember^ member = session->Members->GetAt(i); + for( int j = 4; j < 12; j++ ) + { + WXS::User ^user = InputManager.GetUserForGamepad(j); + if( user != nullptr ) + { + if( user->XboxUserId == member->XboxUserId ) + { + 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()); + playerIdx = ProfileManager.AddGamepadToGame(j); + if( playerIdx != -1 ) + { + playerFound = true; + DQRNetworkManager::LogCommentFormat(L"Assigned controller to user index %d",playerIdx); + break; + } + } + } + } + } + } + } + catch ( Platform::COMException^ ex ) + { + } + }).wait(); + } + return playerFound; +} + +// Request the GameDisplayName for this player asynchronously +void DQRNetworkManager::RequestDisplayName(DQRNetworkPlayer *player) +{ + if (player->IsLocal()) + { + // Player is local so we can just ask profile manager + SetDisplayName(player->GetUID(), ProfileManager.GetDisplayName(player->GetLocalPlayerIndex())); + } + else + { + // Player is remote so we need to do an async request + PlayerUID xuid = player->GetUID(); + ProfileManager.GetProfile(xuid, &GetProfileCallback, this); + } +} + +void DQRNetworkManager::GetProfileCallback(LPVOID pParam, Microsoft::Xbox::Services::Social::XboxUserProfile^ profile) +{ + DQRNetworkManager *dqnm = (DQRNetworkManager *)pParam; + dqnm->SetDisplayName(PlayerUID(profile->XboxUserId->Data()), profile->GameDisplayName->Data()); +} + +// Set player display name +void DQRNetworkManager::SetDisplayName(PlayerUID xuid, wstring displayName) +{ + EnterCriticalSection(&m_csRoomSyncData); + for (int i = 0; i < m_roomSyncData.playerCount; i++) + { + if( m_players[i] ) + { + if (m_players[i]->GetUID() == xuid) + { + // Set player display name + m_players[i]->SetDisplayName(displayName); + // Add display name to map + m_displayNames.insert(std::make_pair(m_players[i]->m_name, m_players[i]->m_displayName)); + } + } + } + LeaveCriticalSection(&m_csRoomSyncData); +} + +void DQRNetworkManager::CheckForcedSignOut() +{ + auto asyncOp = m_primaryUserXboxLiveContext->MultiplayerService->GetCurrentSessionAsync(m_multiplayerSession->SessionReference); + create_task(asyncOp) + .then([this] (task t) + { + try + { + t.get(); + } + catch (Platform::COMException^ ex) + { + if (ex->HResult == 0x8015DC16) + { + m_handleForcedSignOut = true; + } + } + }); +} + +void DQRNetworkManager::HandleForcedSignOut() +{ + // Bin the session (we can't use it anymore) + m_multiplayerSession = nullptr; + + // Bin the party + m_partyController->SetPartyView(nullptr); + + // Forced sign out destroyed the party so time to go + app.DebugPrintf("DQRNetworkManager::HandleForcedSignOut: Forced sign out destroyed the party, aborting game\n"); + + if (IsInSession() && !g_NetworkManager.IsLeavingGame()) + { + // Exit world + app.SetAction(0, eAppAction_ExitWorld); + } + else + { + app.DebugPrintf("DQRNetworkManager::HandleForcedSignOut: Already leaving the game, skipping abort\n"); + } +} diff --git a/Minecraft.Client/Durango/Network/DQRNetworkManager.h b/Minecraft.Client/Durango/Network/DQRNetworkManager.h new file mode 100644 index 00000000..5f7b8d90 --- /dev/null +++ b/Minecraft.Client/Durango/Network/DQRNetworkManager.h @@ -0,0 +1,582 @@ +#pragma once + +#include "DQRNetworkPlayer.h" +#include "..\Minecraft.World\C4JThread.h" +#include + +class IDQRNetworkManagerListener; +class PartyController; + +class DQRNetworkManager; +class ChatIntegrationLayer; + +namespace MXS = Microsoft::Xbox::Services; +namespace MXSM = Microsoft::Xbox::Services::Multiplayer; +namespace MXSS = Microsoft::Xbox::Services::Social; +namespace WXM = Windows::Xbox::Multiplayer; +namespace WXN = Windows::Xbox::Networking; +namespace WXNRs = Windows::Xbox::Networking::RealtimeSession; +namespace WFC = Windows::Foundation::Collections; + +#define MATCH_SESSION_TEMPLATE_NAME L"PeerToHostTemplate" +#define MAX_PLAYERS_IN_TEMPLATE 8 + +using namespace std; + +ref class DQRNetworkManagerEventHandlers sealed +{ +internal: + DQRNetworkManagerEventHandlers(DQRNetworkManager *pDQRNet); +public: + void Setup(WXNRs::Session^ session); + void Pulldown(WXNRs::Session^ session); + + void DataReceivedHandler(Platform::Object^ session, WXNRs::DataReceivedEventArgs^ args); + void SessionAddressDataChangedHandler(Platform::Object^ session, WXNRs::SessionAddressDataChangedEventArgs^ args); + void SessionStatusUpdateHandler(Platform::Object^ session, WXNRs::SessionStatusUpdateEventArgs^ args); + void AddedSessionAddressHandler(Platform::Object^ session, WXNRs::AddedSessionAddressEventArgs^ args); + void RemovedSessionAddressHandler(Platform::Object^ session, WXNRs::RemovedSessionAddressEventArgs^ args); + void GlobalSessionDataChangedHandler(Platform::Object^ session, WXNRs::GlobalSessionDataChangedEventArgs^ args); + +private: + Windows::Foundation::EventRegistrationToken m_dataReceivedToken; + Windows::Foundation::EventRegistrationToken m_sessionStatusToken; + Windows::Foundation::EventRegistrationToken m_sessionAddressToken; + Windows::Foundation::EventRegistrationToken m_addedSessionToken; + Windows::Foundation::EventRegistrationToken m_removedSessionToken; + Windows::Foundation::EventRegistrationToken m_globalDataToken; + + DQRNetworkManager *m_pDQRNet; +}; + +typedef enum +{ + DQR_INTERNAL_ASSIGN_SMALL_IDS, + DQR_INTERNAL_UNASSIGN_SMALL_ID, + DQR_INTERNAL_PLAYER_TABLE, + DQR_INTERNAL_ADD_PLAYER_FAILED, +}; + +class DQRConnectionInfo +{ +public: + typedef enum + { + ConnectionState_HeaderByte0, + ConnectionState_HeaderByte1, + ConnectionState_ReadBytes + } eDQRConnectionState; + + typedef enum + { + ConnectionState_InternalHeaderByte, + ConnectionState_InternalAssignSmallIdMask, + ConnectionState_InternalAssignSmallId0, + ConnectionState_InternalAssignSmallId1, + ConnectionState_InternalAssignSmallId2, + ConnectionState_InternalAssignSmallId3, + ConnectionState_InternalRoomSyncData, + ConnectionState_InternalAddPlayerFailedData, + } eDQRInternalDataState; + + DQRConnectionInfo(); + void Reset(); + + eDQRConnectionState m_state; + eDQRInternalDataState m_internalDataState; + + int m_currentChannel; + bool m_internalFlag; + int m_bytesRemaining; + int m_roomSyncDataBytesRead; + int m_roomSyncDataBytesToRead; + unsigned char * m_pucRoomSyncData; + int m_addFailedPlayerDataBytesRead; + int m_addFailedPlayerDataBytesToRead; + unsigned char * m_pucAddFailedPlayerData; + unsigned char m_smallId[4]; + bool m_channelActive[4]; + unsigned char m_smallIdReadMask; + unsigned char *m_pucsmallIdReadMaskResolved; + bool m_initialPacketReceived; +}; + + +class DQRNetworkManager +{ + friend class PartyController; + friend ref class DQRNetworkManagerEventHandlers; + friend class DQRNetworkPlayer; + friend class ChatIntegrationLayer; +public: + static const int JOIN_PACKET_RESEND_DELAY_MS = 200; + static const int JOIN_PACKET_RESEND_TIMEOUT_MS = 20000; + static const int JOIN_RESERVATION_WAIT_TIME = 30000; + static const int JOIN_CREATE_SESSION_MAX_ATTEMPTS = 5; + + static const int PRIMARY_PLAYER_LEAVING_PARTY_WAIT_MS = 20000; // Time between primary player leaving and when we should respond to it, to allow time for us to receive a new party for them to be going to if they are just changing party rather than leaving altogether + + class SessionInfo + { + public: + SessionInfo(wstring& sessionName, wstring& serviceConfig, wstring& sessionTemplate); + SessionInfo(); + bool m_detailsValid; + wstring m_sessionName; + wstring m_serviceConfig; + wstring m_sessionTemplate; + }; + + static const int MAX_LOCAL_PLAYER_COUNT = 4; + static const int MAX_ONLINE_PLAYER_COUNT = 8; + static const int MAX_ONLINE_PLAYER_NAME_LENGTH = 21; + + // This class stores everything about a player that must be synchronised between machines. + class PlayerSyncData + { + public: + wchar_t *m_XUID; // XUID / XboxUserIds are passed round as decimal strings on Xbox 1 + uint32_t m_sessionAddress; // XRNS session address for this player, ie can identify which machine this player is on + uint8_t m_smallId; // Assigned by DQRNetworkManager, to attach a permanent id to this player (until we have to wrap round), to match a similar concept in qnet + uint8_t m_channel; // Local index / communication channel within session address, of player on this machine + wchar_t m_name[MAX_ONLINE_PLAYER_NAME_LENGTH]; + }; + + class RoomSyncData + { + public: + int playerCount; + PlayerSyncData players[MAX_ONLINE_PLAYER_COUNT]; + }; + + class HostGamertagResolveDetails + { + public:; + DQRNetworkPlayer* m_pPlayer; + wstring m_name; + unsigned int m_sessionAddress; + int m_channel; + bool m_sync; + }; + + DQRNetworkManager(IDQRNetworkManagerListener *listener); + void Initialise(); + void EnableDebugXBLContext(MXS::XboxLiveContext^ XBLContext); + + void CreateAndJoinSession(int userMask, unsigned char *customSessionData, unsigned int customSessionDataSize, bool offline); + void JoinSession(int playerMask); + void JoinSessionFromInviteInfo(int playerMask); + bool AddUsersToSession(int playerMask, MXSM::MultiplayerSessionReference^ sessionRef ); + void UpdateCustomSessionData(); + + bool AddLocalPlayerByUserIndex(int userIndex); + bool RemoveLocalPlayerByUserIndex(int userIndex); + + bool IsHost(); + bool IsInSession(); + + // Player retrieval + int GetPlayerCount(); + int GetOnlinePlayerCount(); + DQRNetworkPlayer *GetPlayerByIndex(int idx); + DQRNetworkPlayer *GetPlayerBySmallId(int idx); + DQRNetworkPlayer *GetPlayerByXuid(PlayerUID xuid); + wstring GetDisplayNameByGamertag(wstring gamertag); + DQRNetworkPlayer *GetLocalPlayerByUserIndex(int idx); + DQRNetworkPlayer *GetHostPlayer(); + int GetSessionIndex(DQRNetworkPlayer *player); + void Tick(); + void Tick_XRNS(); + void Tick_VoiceChat(); + void Tick_Party(); + void Tick_CustomSessionData(); + void Tick_AddAndRemoveLocalPlayers(); + void Tick_ResolveGamertags(); + void Tick_PartyProcess(); + void Tick_StateMachine(); + void Tick_CheckInviteParty(); + + bool ShouldMessageForFullSession(); + void FlagInvitedToFullSession(); + void UnflagInvitedToFullSession(); + // Externally exposed state. All internal states are mapped to one of these broader states. + typedef enum + { + DNM_STATE_INITIALISING, + DNM_STATE_INITIALISE_FAILED, + DNM_STATE_IDLE, + + DNM_STATE_HOSTING, + DNM_STATE_JOINING, + + DNM_STATE_STARTING, + DNM_STATE_PLAYING, + + DNM_STATE_LEAVING, + DNM_STATE_ENDING, + } eDQRNetworkManagerState; + + eDQRNetworkManagerState GetState(); + + class SessionSearchResult + { + public: + SessionSearchResult() { m_extData = NULL; } + ~SessionSearchResult() { free(m_extData); } + wstring m_partyId; + wstring m_sessionName; + + // These names/xuids reflect the server controlled list of who is actually in the game + wstring m_playerNames[MAX_ONLINE_PLAYER_COUNT]; + PlayerUID m_playerXuids[MAX_ONLINE_PLAYER_COUNT]; + int m_playerCount; + + // This count & set of xuids reflects the session document list of who is in the game + wstring m_sessionXuids[MAX_ONLINE_PLAYER_COUNT]; + int m_usedSlotCount; + + void *m_extData; + }; + +protected: + // NOTE: If anything changes in here, then the mapping from internal -> external state needs to be updated (m_INTtoEXTStateMappings, defined in the cpp file) + typedef enum + { + DNM_INT_STATE_INITIALISING, + DNM_INT_STATE_INITIALISE_FAILED, + DNM_INT_STATE_IDLE, + DNM_INT_STATE_HOSTING, + DNM_INT_STATE_HOSTING_WAITING_TO_PLAY, + DNM_INT_STATE_HOSTING_FAILED, + DNM_INT_STATE_JOINING, + DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS, + DNM_INT_STATE_JOINING_GET_SDA, + DNM_INT_STATE_JOINING_WAITING_FOR_SDA, + DNM_INT_STATE_JOINING_CREATE_SESSION, + DNM_INT_STATE_JOINING_WAITING_FOR_ACTIVE_SESSION, + DNM_INT_STATE_JOINING_SENDING_UNRELIABLE, + DNM_INT_STATE_JOINING_FAILED_TIDY_UP, + DNM_INT_STATE_JOINING_FAILED, + DNM_INT_STATE_STARTING, + DNM_INT_STATE_PLAYING, + DNM_INT_STATE_LEAVING, + DNM_INT_STATE_LEAVING_FAILED, + DNM_INT_STATE_ENDING, + DNM_INT_STATE_COUNT + } eDQRNetworkManagerInternalState; + + typedef enum + { + DNM_ADD_PLAYER_STATE_IDLE, + DNM_ADD_PLAYER_STATE_PROCESSING, + DNM_ADD_PLAYER_STATE_COMPLETE_SUCCESS, + DNM_ADD_PLAYER_STATE_COMPLETE_FAIL, + DNM_ADD_PLAYER_STATE_COMPLETE_FAIL_FULL, + } eDQRAddLocalPlayerState; + + typedef enum + { + DNM_REMOVE_PLAYER_STATE_IDLE, + DNM_REMOVE_PLAYER_STATE_PROCESSING, + DNM_REMOVE_PLAYER_STATE_COMPLETE_SUCCESS, + DNM_REMOVE_PLAYER_STATE_COMPLETE_FAIL, + } eDQRRemoveLocalPlayerState; + + class StateChangeInfo + { + public: + eDQRNetworkManagerState m_oldState; + eDQRNetworkManagerState m_newState; + StateChangeInfo(eDQRNetworkManagerState oldState, eDQRNetworkManagerState newState) : m_oldState(oldState), m_newState(newState) {} + }; + + typedef enum + { + // Incoming messages + RTS_MESSAGE_DATA_RECEIVED, + RTS_MESSAGE_DATA_RECEIVED_CHAT, + RTS_MESSAGE_ADDED_SESSION_ADDRESS, + RTS_MESSAGE_REMOVED_SESSION_ADDRESS, + RTS_MESSAGE_STATUS_ACTIVE, + RTS_MESSAGE_STATUS_TERMINATED, + + // Outgoing messages + RTS_MESSAGE_START_CLIENT, + RTS_MESSAGE_START_HOST, + RTS_MESSAGE_TERMINATE, + RTS_MESSAGE_SEND_DATA, + } eRTSMessageType; + + typedef enum + { + RTS_MESSAGE_FLAG_BROADCAST_MODE = 1, + RTS_MESSAGE_FLAG_RELIABLE = 2, + RTS_MESSAGE_FLAG_SEQUENTIAL = 4, + RTS_MESSAGE_FLAG_COALESCE = 8, + RTS_MESSAGE_FLAG_GAME_CHANNEL = 16, + } eRTSFlags; + + class RTS_Message + { + public: + eRTSMessageType m_eType; + unsigned int m_sessionAddress; + unsigned char *m_pucData; + unsigned int m_dataSize; + unsigned int m_flags; + }; + + std::queue m_stateChangeQueue; + CRITICAL_SECTION m_csStateChangeQueue; + CRITICAL_SECTION m_csSendBytes; + CRITICAL_SECTION m_csPartyViewVector; + std::queue m_RTSMessageQueueIncoming; + CRITICAL_SECTION m_csRTSMessageQueueIncoming; + std::queue m_RTSMessageQueueOutgoing; + CRITICAL_SECTION m_csRTSMessageQueueOutgoing; +private: + void SetState(DQRNetworkManager::eDQRNetworkManagerInternalState state); + static const eDQRNetworkManagerState m_INTtoEXTStateMappings[DNM_INT_STATE_COUNT]; + eDQRNetworkManagerInternalState m_state; + eDQRNetworkManagerState m_stateExternal; + __int64 m_lastUnreliableSendTime; + __int64 m_firstUnreliableSendTime; + __int64 m_startedWaitingForReservationsTime; + unsigned char *m_customSessionData; + unsigned int m_customSessionDataSize; + int m_customDataDirtyUpdateTicks; + std::shared_ptr m_chat; + + eDQRAddLocalPlayerState m_addLocalPlayerState; + DQRNetworkPlayer *m_addLocalPlayerSuccessPlayer; + int m_addLocalPlayerSuccessIndex; + int m_addLocalPlayerFailedIndex; + + eDQRRemoveLocalPlayerState m_removeLocalPlayerState; + int m_removeLocalPlayerIndex; + + Windows::Xbox::System::User^ m_primaryUser; + MXS::XboxLiveContext^ m_primaryUserXboxLiveContext; + WXN::SecureDeviceAssociationTemplate^ m_associationTemplate; + + CRITICAL_SECTION m_csRoomSyncData; + RoomSyncData m_roomSyncData; + DQRNetworkPlayer *m_players[MAX_ONLINE_PLAYER_COUNT]; + + IDQRNetworkManagerListener *m_listener; + PartyController *m_partyController; + DQRNetworkManagerEventHandlers^ m_eventHandlers; + WXNRs::Session^ m_XRNS_Session; + unsigned int m_XRNS_LocalAddress; + unsigned int m_XRNS_OldestAddress; + MXSM::MultiplayerSession^ m_multiplayerSession; + WXN::SecureDeviceAssociation^ m_sda; + Platform::String^ m_secureDeviceAddressBase64; + unsigned char m_currentSmallId; + unsigned char m_hostSmallId; + bool m_isHosting; + bool m_isInSession; + bool m_isOfflineGame; +public: + int m_currentUserMask; +private: + int m_joinSessionUserMask; + Platform::Array^ m_joinSessionXUIDs; + int m_joinSmallIdMask; + + Platform::Array^ m_remoteSocketAddress; + Platform::Array^ m_localSocketAddress; + int m_joinCreateSessionAttempts; + + C4JThread *m_CreateSessionThread; + C4JThread *m_LeaveRoomThread; + C4JThread *m_TidyUpJoinThread; + C4JThread *m_UpdateCustomSessionDataThread; + C4JThread *m_RTS_DoWorkThread; + C4JThread *m_CheckPartyInviteThread; + + bool m_notifyForFullParty; + + unordered_map m_sessionAddressToConnectionInfoMapHost; // For host - there may be more than one remote session attached to this listening session + unsigned int m_sessionAddressFromSmallId[256]; // For host - for mapping back from small Id, to session address + unsigned char m_channelFromSmallId[256]; // For host and client, for mapping back from small Id, to channel + DQRConnectionInfo m_connectionInfoClient; // For client + unsigned int m_hostSessionAddress; // For client + + CRITICAL_SECTION m_csHostGamertagResolveResults; + queue m_hostGamertagResolveResults; + + void AddPlayerFailed(Platform::String ^xuid); + Platform::String^ RemoveBracesFromGuidString(__in Platform::String^ guid ); + void HandleSessionChange( MXSM::MultiplayerSession^ session ); + MXSM::MultiplayerSession^ WriteSessionHelper( MXS::XboxLiveContext^ xboxLiveContext, + MXSM::MultiplayerSession^ multiplayerSession, + MXSM::MultiplayerSessionWriteMode writeMode, + HRESULT& hr); + MXSM::MultiplayerSessionMember^ GetUserMemberInSession( Windows::Xbox::System::User^ user, MXSM::MultiplayerSession^ session); + bool IsPlayerInSession( Platform::String^ xboxUserId, MXSM::MultiplayerSession^ session, int *smallId ); + + WXM::MultiplayerSessionReference^ ConvertToWindowsXboxMultiplayerSessionReference( MXSM::MultiplayerSessionReference^ sessionRef); + MXSM::MultiplayerSessionReference^ ConvertToMicrosoftXboxServicesMultiplayerSessionReference( WXM::MultiplayerSessionReference^ sessionRef ); + + void BytesReceived(int smallId, BYTE *bytes, int byteCount); + void BytesReceivedInternal(DQRConnectionInfo *connectionInfo, unsigned int sessionAddress, BYTE *bytes, int byteCount); + void SendBytes(int smallId, BYTE *bytes, int byteCount); + int GetQueueSizeBytes(); + int GetQueueSizeMessages(); + void SendBytesRaw(int smallId, BYTE *bytes, int byteCount, bool reliableAndSequential); + void SendBytesChat(unsigned int address, BYTE *bytes, int byteCount, bool reliable, bool sequential, bool broadcast); + + bool AddRoomSyncPlayer(DQRNetworkPlayer *pPlayer, unsigned int sessionAddress, int channel); + void RemoveRoomSyncPlayersWithSessionAddress(unsigned int sessionAddress); + void RemoveRoomSyncPlayer(DQRNetworkPlayer *pPlayer); + void UpdateRoomSyncPlayers(RoomSyncData *pNewSyncData); + void SendRoomSyncInfo(); + void SendAddPlayerFailed(Platform::String^ xuid); + void SendSmallId(bool reliableAndSequential, int playerMask); + void SendUnassignSmallId(int playerIndex); + int GetSessionIndexForSmallId(unsigned char smallId); + int GetSessionIndexAndSmallIdForHost(unsigned char *smallId); + + static void LogComment( Platform::String^ strText ); + static void LogCommentFormat( LPCWSTR strMsg, ... ); + static void LogCommentWithError( Platform::String^ strTest, HRESULT hr ); + + static Platform::String^ GetErrorString( HRESULT hr ); + static Platform::String^ FormatString( LPCWSTR strMsg, ... ); + static Platform::String^ ConvertHResultToErrorName( HRESULT hr ); + + Platform::String^ GetNextSmallIdAsJsonString(); + static int _HostGameThreadProc( void* lpParameter ); + int HostGameThreadProc(); + static int _LeaveRoomThreadProc( void* lpParameter ); + int LeaveRoomThreadProc(); + static int _TidyUpJoinThreadProc( void* lpParameter ); + int TidyUpJoinThreadProc(); + static int _UpdateCustomSessionDataThreadProc( void* lpParameter ); + int UpdateCustomSessionDataThreadProc(); + static int _CheckInviteThreadProc(void *lpParameter); + int CheckInviteThreadProc(); + static int _RTSDoWorkThread(void* lpParameter); + int RTSDoWorkThread(); + + CRITICAL_SECTION m_csVecChatPlayers; + vector m_vecChatPlayersJoined; +public: + void HandleNewPartyFoundForPlayer(); + void HandlePlayerRemovedFromParty(int playerMask); + + void ChatPlayerJoined(int idx); + bool IsReadyToPlayOrIdle(); + void StartGame(); + void LeaveRoom(); + void TidyUpFailedJoin(); + + static void SetInviteReceivedFlag(); + static void SetPartyProcessJoinParty(); + static void SetPartyProcessJoinSession(int bootUserIndex, Platform::String^ bootSessionName, Platform::String^ bootServiceConfig, Platform::String^ bootSessionTemplate); + + void SendInviteGUI(int quadrant); + bool IsAddingPlayer(); + + bool FriendPartyManagerIsBusy(); + int FriendPartyManagerGetCount(); + bool FriendPartyManagerSearch(); + void FriendPartyManagerGetSessionInfo(int idx, SessionSearchResult *searchResult); + WFC::IVectorView^ FilterPartiesByPermission(MXS::XboxLiveContext ^context, WFC::IVectorView^ partyResults); + + bool JoinPartyFromSearchResult(SessionSearchResult *searchResult, int playerMask); + void CancelJoinPartyFromSearchResult(); + void RequestDisplayName(DQRNetworkPlayer *player); + void SetDisplayName(PlayerUID xuid, wstring displayName); + +private: + __int64 m_playersLeftPartyTime; + int m_playersLeftParty; + + bool GetBestPartyUserIndex(); + C4JThread *m_GetFriendPartyThread; + static int _GetFriendsThreadProc( void* lpParameter ); + int GetFriendsThreadProc(); + bool IsSessionFriendsOfFriends(MXSM::MultiplayerSession^ session); + bool GetGameSessionData(MXSM::MultiplayerSession^ session, void *gameSessionData); + +public: + static Platform::Collections::Vector^ GetFriends(); + +private: + SessionSearchResult *m_sessionSearchResults; + int m_sessionResultCount; + bool m_cancelJoinFromSearchResult; + + map m_displayNames; // Player display names by gamertag + + + + typedef enum + { + DNM_PARTY_PROCESS_NONE, + DNM_PARTY_PROCESS_JOIN_PARTY, + DNM_PARTY_PROCESS_JOIN_SPECIFIED + } ePartyProcessType; + static int m_bootUserIndex; + static ePartyProcessType m_partyProcess; + static wstring m_bootSessionName; + static wstring m_bootServiceConfig; + static wstring m_bootSessionTemplate; + static bool m_inviteReceived; + + static DQRNetworkManager *s_pDQRManager; + + static void GetProfileCallback(LPVOID pParam, Microsoft::Xbox::Services::Social::XboxUserProfile^ profile); + + // Forced signout + bool m_handleForcedSignOut; + + void CheckForcedSignOut(); + void HandleForcedSignOut(); + + unsigned int m_RTS_Stat_totalBytes; + unsigned int m_RTS_Stat_totalSends; + + void UpdateRTSStats(); + + // Incoming messages - to be called from the main thread, to get incoming messages from the RTS work thread + void ProcessRTSMessagesIncoming(); + void Process_RTS_MESSAGE_DATA_RECEIVED(RTS_Message &message); + void Process_RTS_MESSAGE_DATA_RECEIVED_CHAT(RTS_Message &message); + void Process_RTS_MESSAGE_ADDED_SESSION_ADDRESS(RTS_Message &message); + void Process_RTS_MESSAGE_REMOVED_SESSION_ADDRESS(RTS_Message &message); + void Process_RTS_MESSAGE_STATUS_ACTIVE(RTS_Message &message); + void Process_RTS_MESSAGE_STATUS_TERMINATED(RTS_Message &message); + + // Outgoing messages - to be called from the RTS work thread, to process requests from the main thread + + void ProcessRTSMessagesOutgoing(); + void Process_RTS_MESSAGE_START_CLIENT(RTS_Message &message); + void Process_RTS_MESSAGE_START_HOST(RTS_Message &message); + void Process_RTS_MESSAGE_TERMINATE(RTS_Message &message); + void Process_RTS_MESSAGE_SEND_DATA(RTS_Message &message); + + // These methods are called from the main thread, to put an outgoing message onto the queue to be processed by these previous methods + void RTS_StartCient(); + void RTS_StartHost(); + void RTS_Terminate(); + void RTS_SendData(unsigned char *pucData, unsigned int dataSize, unsigned int sessionAddress, bool reliable, bool sequential, bool coalesce, bool includeMode, bool gameChannel ); + +}; + +// Class defining interface to be implemented for class that handles callbacks +class IDQRNetworkManagerListener +{ +public: + virtual void HandleDataReceived(DQRNetworkPlayer *playerFrom, DQRNetworkPlayer *playerTo, unsigned char *data, unsigned int dataSize) = 0; + virtual void HandlePlayerJoined(DQRNetworkPlayer *player) = 0; + virtual void HandlePlayerLeaving(DQRNetworkPlayer *player) = 0; + virtual void HandleStateChange(DQRNetworkManager::eDQRNetworkManagerState oldState, DQRNetworkManager::eDQRNetworkManagerState newState) = 0; +// virtual void HandleResyncPlayerRequest(DQRNetworkPlayer **aPlayers) = 0; + virtual void HandleAddLocalPlayerFailed(int idx, bool serverFull) = 0; + virtual void HandleDisconnect(bool bLostRoomOnly) = 0; + virtual void HandleInviteReceived(int playerIndex, DQRNetworkManager::SessionInfo *pInviteInfo) = 0; + virtual bool IsSessionJoinable() = 0; +}; diff --git a/Minecraft.Client/Durango/Network/DQRNetworkManager_FriendSessions.cpp b/Minecraft.Client/Durango/Network/DQRNetworkManager_FriendSessions.cpp new file mode 100644 index 00000000..3a14a2d5 --- /dev/null +++ b/Minecraft.Client/Durango/Network/DQRNetworkManager_FriendSessions.cpp @@ -0,0 +1,591 @@ +#include "stdafx.h" + +#include "DQRNetworkManager.h" +#include "PartyController.h" +#include +#include +#include +#include "..\Minecraft.World\StringHelpers.h" +#include "base64.h" + +#ifdef _DURANGO +#include "..\Minecraft.World\DurangoStats.h" +#endif + +#include "ChatIntegrationLayer.h" + +using namespace Concurrency; +using namespace Windows::Foundation::Collections; + +// Returns true if we are already processing a request to find game parties of friends +bool DQRNetworkManager::FriendPartyManagerIsBusy() +{ + if( m_GetFriendPartyThread ) + { + if( m_GetFriendPartyThread->isRunning() ) + { + return true; + } + } + return false; +} + +// Returns the total count of game parties that we found for our friends +int DQRNetworkManager::FriendPartyManagerGetCount() +{ + return m_sessionResultCount; +} + +// Initiate the (asynchronous) search for game parties of our friends +bool DQRNetworkManager::FriendPartyManagerSearch() +{ + if( m_GetFriendPartyThread ) + { + if( m_GetFriendPartyThread->isRunning() ) + { + return false; + } + } + + m_sessionResultCount = 0; + delete [] m_sessionSearchResults; + m_sessionSearchResults = NULL; + + m_GetFriendPartyThread = new C4JThread(&_GetFriendsThreadProc,this,"GetFriendsThreadProc"); + m_GetFriendPartyThread->Run(); + + return true; +} + +// Get a particular search result for a game party that we have discovered. Index should be from 0 to the value returned by FriendPartyManagerGetCount. +void DQRNetworkManager::FriendPartyManagerGetSessionInfo(int idx, SessionSearchResult *searchResult) +{ + assert( idx < m_sessionResultCount ); + assert( ( m_GetFriendPartyThread == NULL ) || ( !m_GetFriendPartyThread->isRunning()) ); + + // Need to make sure that copied data has independently allocated m_extData, so both copies can be freed + *searchResult = m_sessionSearchResults[idx]; + searchResult->m_extData = malloc(sizeof(GameSessionData)); + memcpy(searchResult->m_extData, m_sessionSearchResults[idx].m_extData, sizeof(GameSessionData)); +} + +int DQRNetworkManager::_GetFriendsThreadProc(void* lpParameter) +{ + DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter; + return pDQR->GetFriendsThreadProc(); +} + +// This is the main thread that is kicked off to find game sessions associated with our friends. We have to do this +// by finding parties associated with our friends, and from the parties get the assocated game session. +int DQRNetworkManager::GetFriendsThreadProc() +{ + LogComment(L"Starting GetFriendsThreadProc"); + WXS::User^ primaryUser = ProfileManager.GetUser(0); + + if( primaryUser == nullptr ) + { + return -1; + } + MXS::XboxLiveContext^ primaryUserXboxLiveContext = ref new MXS::XboxLiveContext(primaryUser); + if( primaryUserXboxLiveContext == nullptr ) + { + return -1; + } + + MXSS::XboxSocialRelationshipResult^ socialRelationshipResult = nullptr; + + // First get our friends list (people we follow who may or may not follow us back), note we're requesting all friends + auto getSocialRelationshipsAsync = primaryUserXboxLiveContext->SocialService->GetSocialRelationshipsAsync(MXSS::SocialRelationship::All, 0, 1100); + create_task(getSocialRelationshipsAsync).then([this,&socialRelationshipResult](task t) + { + try + { + socialRelationshipResult = t.get(); + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"GetSocialRelationshipsAsync failed", ex->HResult ); + } + }) + .wait(); + if( socialRelationshipResult == nullptr ) + { + return -1; + } + + IVector^ friendXUIDs = ref new Platform::Collections::Vector; + + // Now construct a vector of these users, that follow us back - these are our "friends" + for( int i = 0; i < socialRelationshipResult->TotalCount; i++ ) + { + MXSS::XboxSocialRelationship^ relationship = socialRelationshipResult->Items->GetAt(i); + if(relationship->IsFollowingCaller) + { + friendXUIDs->Append(relationship->XboxUserId); + } + } + + // If we don't have any such friends, we're done + if( friendXUIDs->Size == 0 ) + { + return 0; + } + + // Now get party associations for these friends + auto getPartyAssociationsAsync = WXM::Party::GetUserPartyAssociationsAsync(primaryUser, friendXUIDs->GetView() ); + + IVectorView^ partyResults = nullptr; + + create_task(getPartyAssociationsAsync).then([this,&partyResults](task^> t) + { + try + { + partyResults = t.get(); + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"getPartyAssociationsAsync failed", ex->HResult ); + } + }) + .wait(); + + if( partyResults == nullptr ) + { + return -1; + } + + if( partyResults->Size == 0 ) + { + return 0; + } + + // Filter these parties by whether we have permission to see them online + partyResults = FilterPartiesByPermission(primaryUserXboxLiveContext, partyResults); + + + // At this point, we have Party Ids for our friends. Now we need to get Party Views for each of these Ids. + + LogComment("Parties found"); + + // Get party views for each of the user party associations that we have. These seem to be able to (individually) raise errors, so + // accumulate results into 2 matched vectors declared below so that we can ignore any broken UserPartyAssociations from now + vector partyViewVector; + vector partyResultsVector; + + vector> taskVector; + for each(WXM::UserPartyAssociation^ remoteParty in partyResults) + { + auto asyncOp = WXM::Party::GetPartyViewByPartyIdAsync( primaryUser, remoteParty->PartyId ); + task asyncTask = create_task(asyncOp); + + taskVector.push_back(asyncTask.then([this, &partyViewVector, &partyResultsVector, remoteParty] (task t) + { + try + { + WXM::PartyView^ partyView = t.get(); + + if( partyView != nullptr ) + { + app.DebugPrintf("Got party view\n"); + EnterCriticalSection(&m_csPartyViewVector); + partyViewVector.push_back(partyView); + partyResultsVector.push_back(remoteParty); + LeaveCriticalSection(&m_csPartyViewVector); + } + } + catch ( Platform::COMException^ ex ) + { + app.DebugPrintf("Getting party view error 0x%x\n",ex->HResult); + } + })); + } + for( auto it = taskVector.begin(); it != taskVector.end(); it++ ) + { + it->wait(); + } + + if( partyViewVector.size() == 0 ) + { + return 0; + } + + // Filter the party view, and party results vector (partyResultsVector) this is matched to, to remove any that don't have game sessions - or game sessions that aren't this game + vector partyViewVectorFiltered; + vector partyResultsFiltered; + + for( int i = 0; i < partyViewVector.size(); i++ ) + { + WXM::PartyView^ partyView = partyViewVector[i]; + + if( partyView->Joinability == WXM::SessionJoinability::JoinableByFriends ) + { + if( partyView->GameSession ) + { + if( partyView->GameSession->ServiceConfigurationId == SERVICE_CONFIG_ID ) + { + partyViewVectorFiltered.push_back( partyView ); + partyResultsFiltered.push_back( partyResultsVector[i] ); + } + } + } + } + + // We now have matched vectors: + // + // partyResultsFiltered + // partyViewVectorFiltered + // + // and, from the party views, we can now attempt to get game sessions + + vector sessionVector; + vector partyViewVectorValid; + vector partyResultsValid; + + for( int i = 0; i < partyViewVectorFiltered.size(); i++ ) + { + WXM::PartyView^ partyView = partyViewVectorFiltered[i]; + Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ sessionRef = ConvertToMicrosoftXboxServicesMultiplayerSessionReference(partyView->GameSession); + + LogComment(L"Party view vector " + sessionRef->SessionName + L" " + partyResultsFiltered[i]->QueriedXboxUserIds->GetAt(0)); + + MXSM::MultiplayerSession^ session = nullptr; + auto asyncOp = primaryUserXboxLiveContext->MultiplayerService->GetCurrentSessionAsync( sessionRef ); + create_task(asyncOp).then([&session] (task t) + { + try + { + session = t.get(); + } + catch (Platform::COMException^ ex) + { + } + }) + .wait(); + if( session ) + { + sessionVector.push_back(session); + partyViewVectorValid.push_back(partyView); + partyResultsValid.push_back(partyResultsFiltered[i]); + } + } + + if( sessionVector.size() == 0 ) + { + return 0; + } + + // We now have matched vectors: + // + // partyResultsValid + // partyViewVectorValid + // sessionVector + + // The next stage is to resolve the display names for the XUIDs of all the players in each of the sessions. It is possible that + // a session won't have any XUIDs to resolve, which would make GetUserProfilesAsync unhappy, so we'll only be creating a task + // when there are members. Creating new matching arrays for party results and sessions, to match the results (we don't care about the party view anymore) + + vector^>> nameResolveTaskVector; + vector^> nameResolveVector; + vector newSessionVector; + vector newPartyVector; + + for( int j = 0; j < sessionVector.size(); j++ ) + { + MXSM::MultiplayerSession^ session = sessionVector[j]; + IVector^ memberXUIDs = ref new Platform::Collections::Vector; + + Windows::Data::Json::JsonArray^ roomSyncArray = nullptr; + try + { + Windows::Data::Json::JsonObject^ customJson = Windows::Data::Json::JsonObject::Parse(session->SessionProperties->SessionCustomPropertiesJson); + Windows::Data::Json::JsonValue^ customValue = customJson->GetNamedValue(L"RoomSyncData"); + roomSyncArray = customValue->GetArray(); + LogComment("Attempting to parse RoomSyncData"); + for( int i = 0; i < roomSyncArray->Size; i++ ) + { + LogComment(roomSyncArray->GetAt(i)->GetString()); + } + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"Custom RoomSyncData Parse/GetNamedValue failed", ex->HResult ); + continue; + } + + if( roomSyncArray && ( roomSyncArray->Size > 0 ) ) + { + // For each session, we want to order these XUIDs so the display name of the first one is what we will name the session by. Prioritise doing this by: + // + // (1) If the host player (indicated by having a small id of 0) is our friend, use that + // (2) Otherwise use anyone who is our friend + + // Default to true + bool friendsOfFriends = true; + + int hostIndexFound = -1; + int friendIndexFound = -1; + + friendsOfFriends = IsSessionFriendsOfFriends(session); + + for( int i = 0; i < roomSyncArray->Size; i++ ) + { + Platform::String^ roomSyncXuid = roomSyncArray->GetAt(i)->GetString(); + + // Determine if this player is a friend + bool isFriend = false; + for each( Platform::String^ friendXUID in friendXUIDs ) + { + if( friendXUID == roomSyncXuid ) + { + isFriend = true; + break; + } + } + + bool isHost = i == 0; + + // Store that what we found at this index if it is a friend, or a friend who is a host + if( isFriend && ( friendsOfFriends || isHost ) ) + { + friendIndexFound = i; + if( isHost ) // Host is always in slot 0 + { + hostIndexFound = i; + } + } + } + + // Prefer to use index of host who is our friend + int bestIndex = friendIndexFound; + if( hostIndexFound != -1 ) + { + bestIndex = hostIndexFound; + } + + // Only consider if we have at least found one friend in the list of players + if( bestIndex != -1 ) + { + // Compile list of XUIDs to resolve with our specially chosen player as entry 0, then the rest + memberXUIDs->Append(roomSyncArray->GetAt(bestIndex)->GetString()); + for( int i = 0; i < roomSyncArray->Size; i++ ) + { + if( i != bestIndex ) + { + memberXUIDs->Append(roomSyncArray->GetAt(i)->GetString()); + } + } + nameResolveTaskVector.push_back( create_task( primaryUserXboxLiveContext->ProfileService->GetUserProfilesAsync( memberXUIDs->GetView() ) ) ); + newSessionVector.push_back(session); + newPartyVector.push_back(partyResultsValid[j]); + } + } + } + + try + { + auto joinTask = when_all(begin(nameResolveTaskVector), end(nameResolveTaskVector) ).then([this, &nameResolveVector](vector^> results) + { + nameResolveVector = results; + }) + .wait(); + } + catch(Platform::COMException^ ex) + { + return -1; + } + + // We now have matched vectors: + // + // newPartyVector - contains the party Ids that we'll need should we wish to join + // nameResolveVector - contains vectors views of the names of the members of the session each of these parties is in + // newSessionVector - contains the session information itself associated with each of the parties + + // Construct the final result vector + m_sessionResultCount = newSessionVector.size(); + m_sessionSearchResults = new SessionSearchResult[m_sessionResultCount]; + for( int i = 0; i < m_sessionResultCount; i++ ) + { + m_sessionSearchResults[i].m_partyId = newPartyVector[i]->PartyId->Data(); + m_sessionSearchResults[i].m_sessionName = newSessionVector[i]->SessionReference->SessionName->Data(); + for( int j = 0; j < nameResolveVector[i]->Size; j++ ) + { + m_sessionSearchResults[i].m_playerNames[j] = nameResolveVector[i]->GetAt(j)->GameDisplayName->Data(); + m_sessionSearchResults[i].m_playerXuids[j] = PlayerUID(nameResolveVector[i]->GetAt(j)->XboxUserId->Data()); + } + m_sessionSearchResults[i].m_playerCount = nameResolveVector[i]->Size; + m_sessionSearchResults[i].m_usedSlotCount = newSessionVector[i]->Members->Size; + if( m_sessionSearchResults[i].m_usedSlotCount > MAX_ONLINE_PLAYER_COUNT ) + { + // Don't think this could ever happen, but no harm in checking + m_sessionSearchResults[i].m_usedSlotCount = MAX_ONLINE_PLAYER_COUNT; + } + for( int j = 0; j < m_sessionSearchResults[i].m_usedSlotCount; j++ ) + { + m_sessionSearchResults[i].m_sessionXuids[j] = wstring( newSessionVector[i]->Members->GetAt(j)->XboxUserId->Data() ); + } + + m_sessionSearchResults[i].m_extData = malloc( sizeof(GameSessionData) ); + memset( m_sessionSearchResults[i].m_extData, 0, sizeof(GameSessionData) ); + + GetGameSessionData(newSessionVector[i], m_sessionSearchResults[i].m_extData); + } + + return 0; +} + +// Filters list of parties based on online presence permission (whether the friend is set to invisible or not) +IVectorView^ DQRNetworkManager::FilterPartiesByPermission(MXS::XboxLiveContext ^context, IVectorView^ partyResults) +{ + Platform::Collections::Vector^ filteredPartyResults = ref new Platform::Collections::Vector(); + + // List of permissions we want + auto permissionIds = ref new Platform::Collections::Vector(1, ref new Platform::String(L"ViewTargetPresence")); + + // List of target users + auto targetXboxUserIds = ref new Platform::Collections::Vector(); + for (int i = 0; i < partyResults->Size; i++) + { + assert(partyResults->GetAt(i)->QueriedXboxUserIds->Size > 0); + targetXboxUserIds->Append( partyResults->GetAt(i)->QueriedXboxUserIds->GetAt(0) ); + } + + // Check + auto checkPermissionsAsync = context->PrivacyService->CheckMultiplePermissionsWithMultipleTargetUsersAsync(permissionIds->GetView(), targetXboxUserIds->GetView()); + create_task(checkPermissionsAsync).then([&partyResults, &filteredPartyResults](task^> t) + { + try + { + auto results = t.get(); + + // For each party, check to see if we have permission for the user + for (int i = 0; i < partyResults->Size; i++) + { + // For each permissions result + for (int j = 0; j < results->Size; j++) + { + auto result = results->GetAt(j); + + // If allowed to see this user AND it's the same user, add the party to the just + if ((result->Items->GetAt(0)->IsAllowed) && (partyResults->GetAt(i)->QueriedXboxUserIds->GetAt(0) == result->XboxUserId)) + { + filteredPartyResults->Append(partyResults->GetAt(i)); + break; + } + } + } + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"CheckMultiplePermissionsWithMultipleTargetUsersAsync failed", ex->HResult ); + } + }) + .wait(); + + app.DebugPrintf("DQRNetworkManager::FilterPartiesByPermission: Removed %i parties because of online presence permissions\n", partyResults->Size - filteredPartyResults->Size); + + return filteredPartyResults->GetView(); +} + +// Get all friends (list of XUIDs) syncronously from the service (slow, may take 300ms+), returns empty list if something goes wrong +Platform::Collections::Vector^ DQRNetworkManager::GetFriends() +{ + auto friends = ref new Platform::Collections::Vector; + + auto primaryUser = ProfileManager.GetUser(0); + if (primaryUser == nullptr) + { + // Return empty + return friends; + } + + auto xboxLiveContext = ref new MXS::XboxLiveContext(primaryUser); + + // Request ALL friends because there's no other way to check friendships without using the REST API + auto getSocialRelationshipsAsync = xboxLiveContext->SocialService->GetSocialRelationshipsAsync(MXSS::SocialRelationship::All, 0, 1100); + MXSS::XboxSocialRelationshipResult^ socialRelationshipResult = nullptr; + + // First get our friends list (people we follow who may or may not follow us back) + Concurrency::create_task(getSocialRelationshipsAsync).then([&socialRelationshipResult](Concurrency::task t) + { + try + { + socialRelationshipResult = t.get(); + } + catch (Platform::COMException^ ex) + { + app.DebugPrintf("DQRNetworkManager::GetFriends: GetSocialRelationshipsAsync failed ()\n", ex->HResult); + } + }) + .wait(); + + if (socialRelationshipResult == nullptr) + { + // Return empty + return friends; + } + + app.DebugPrintf("DQRNetworkManager::GetFriends: Retrieved %i relationships\n", socialRelationshipResult->TotalCount); + + // Now construct a vector of these users, that follow us back - these are our "friends" + for( int i = 0; i < socialRelationshipResult->TotalCount; i++ ) + { + MXSS::XboxSocialRelationship^ relationship = socialRelationshipResult->Items->GetAt(i); + if(relationship->IsFollowingCaller) + { + app.DebugPrintf("DQRNetworkManager::GetFriends: Found friend \"%ls\"\n", relationship->XboxUserId->Data()); + friends->Append(relationship->XboxUserId); + } + } + + app.DebugPrintf("DQRNetworkManager::GetFriends: Found %i 2-way friendships\n", friends->Size); + + return friends; +} + +// If data for game settings exists returns FriendsOfFriends value, otherwise returns true +bool DQRNetworkManager::IsSessionFriendsOfFriends(MXSM::MultiplayerSession^ session) +{ + // Default to true, don't want to incorrectly prevent joining + bool friendsOfFriends = true; + + // We retrieve the game session data later too, shouldn't really duplicate this + void *gameSessionData = malloc( sizeof(GameSessionData)); + memset(gameSessionData, 0, sizeof(GameSessionData)); + + bool result = GetGameSessionData(session, gameSessionData); + + if (result) + { + friendsOfFriends = app.GetGameHostOption(((GameSessionData *)gameSessionData)->m_uiGameHostSettings, eGameHostOption_FriendsOfFriends); + } + + free(gameSessionData); + + return friendsOfFriends; +} + +// Parses custom json data from session and populates game session data param, return true if parse succeeded +bool DQRNetworkManager::GetGameSessionData(MXSM::MultiplayerSession^ session, void *gameSessionData) +{ + Platform::String ^gameSessionDataJson = session->SessionProperties->SessionCustomPropertiesJson; + if( gameSessionDataJson ) + { + try + { + Windows::Data::Json::JsonObject^ customParam = Windows::Data::Json::JsonObject::Parse(gameSessionDataJson); + Windows::Data::Json::JsonValue^ customValue = customParam->GetNamedValue(L"GameSessionData"); + Platform::String ^customValueString = customValue->GetString(); + if( customValueString ) + { + base64_decode( customValueString, (unsigned char *)gameSessionData, sizeof(GameSessionData) ); + return true; + } + } + catch (Platform::COMException^ ex) + { + LogCommentWithError( L"Custom GameSessionData parameter Parse/GetNamedValue failed", ex->HResult ); + } + } + + return false; +} \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/DQRNetworkManager_Log.cpp b/Minecraft.Client/Durango/Network/DQRNetworkManager_Log.cpp new file mode 100644 index 00000000..cf66ea6f --- /dev/null +++ b/Minecraft.Client/Durango/Network/DQRNetworkManager_Log.cpp @@ -0,0 +1,303 @@ +#include "stdafx.h" + +#include "DQRNetworkManager.h" +#include "PartyController.h" +#include +#include +#include +#include "..\Minecraft.World\StringHelpers.h" +#include "base64.h" + +#ifdef _DURANGO +#include "..\Minecraft.World\DurangoStats.h" +#endif + +#include "ChatIntegrationLayer.h" + +using namespace Concurrency; +using namespace Windows::Foundation::Collections; + +void DQRNetworkManager::LogComment( Platform::String^ strText ) +{ +#ifndef _CONTENT_PACKAGE + static int64_t firstTime = 0; + wchar_t buf[64]; + + int64_t currentTime = System::currentTimeMillis(); + if( firstTime != 0 ) + { + _i64tow_s(currentTime - firstTime, buf, 64, 10); + OutputDebugString(buf); + OutputDebugString(L" ms: "); + } + else + { + firstTime = currentTime; + } + OutputDebugString(strText->Data()); + OutputDebugString(L"\n"); +#endif +} + +void DQRNetworkManager::LogCommentFormat( LPCWSTR strMsg, ... ) +{ + WCHAR strBuffer[2048]; + + va_list args; + va_start(args, strMsg); + _vsnwprintf_s( strBuffer, 2048, _TRUNCATE, strMsg, args ); + strBuffer[2047] = L'\0'; + + va_end(args); + + LogComment(ref new Platform::String(strBuffer)); +} + +void DQRNetworkManager::LogCommentWithError( Platform::String^ strTest, HRESULT hr ) +{ + Platform::String^ final = strTest + GetErrorString(hr); + LogComment(final); +} + +Platform::String^ DQRNetworkManager::GetErrorString( HRESULT hr ) +{ + Platform::String^ str = FormatString(L" %s [0x%0.8x]", ConvertHResultToErrorName(hr)->Data(), hr ); + return str; +} + +Platform::String^ DQRNetworkManager::FormatString( LPCWSTR strMsg, ... ) +{ + WCHAR strBuffer[2048]; + + va_list args; + va_start(args, strMsg); + _vsnwprintf_s( strBuffer, 2048, _TRUNCATE, strMsg, args ); + strBuffer[2047] = L'\0'; + + va_end(args); + + Platform::String^ str = ref new Platform::String(strBuffer); + return str; +} + +Platform::String^ DQRNetworkManager::ConvertHResultToErrorName( HRESULT hr ) +{ + switch( hr ) + { + // Generic errors + case S_OK: return L"S_OK"; + case S_FALSE: return L"S_FALSE"; + case E_OUTOFMEMORY: return L"E_OUTOFMEMORY"; + case E_ACCESSDENIED: return L"E_ACCESSDENIED"; + case E_INVALIDARG: return L"E_INVALIDARG"; + case E_UNEXPECTED: return L"E_UNEXPECTED"; + case E_ABORT: return L"E_ABORT"; + case E_FAIL: return L"E_FAIL"; + case E_NOTIMPL: return L"E_NOTIMPL"; + case E_ILLEGAL_METHOD_CALL: return L"E_ILLEGAL_METHOD_CALL"; + + // Authentication specific errors + case 0x87DD0003: return L"AM_E_XASD_UNEXPECTED"; + case 0x87DD0004: return L"AM_E_XASU_UNEXPECTED"; + case 0x87DD0005: return L"AM_E_XAST_UNEXPECTED"; + case 0x87DD0006: return L"AM_E_XSTS_UNEXPECTED"; + case 0x87DD0007: return L"AM_E_XDEVICE_UNEXPECTED"; + case 0x87DD0008: return L"AM_E_DEVMODE_NOT_AUTHORIZED"; + case 0x87DD0009: return L"AM_E_NOT_AUTHORIZED"; + case 0x87DD000A: return L"AM_E_FORBIDDEN"; + case 0x87DD000B: return L"AM_E_UNKNOWN_TARGET"; + case 0x87DD000C: return L"AM_E_INVALID_NSAL_DATA"; + case 0x87DD000D: return L"AM_E_TITLE_NOT_AUTHENTICATED"; + case 0x87DD000E: return L"AM_E_TITLE_NOT_AUTHORIZED"; + case 0x87DD000F: return L"AM_E_DEVICE_NOT_AUTHENTICATED"; + case 0x87DD0010: return L"AM_E_INVALID_USER_INDEX"; + case 0x87DD0011: return L"AM_E_USER_HASH_MISSING"; + case 0x87DD0012: return L"AM_E_ACTOR_NOT_SPECIFIED"; + case 0x87DD0013: return L"AM_E_USER_NOT_FOUND"; + case 0x87DD0014: return L"AM_E_INVALID_SUBTOKEN"; + case 0x87DD0015: return L"AM_E_INVALID_ENVIRONMENT"; + case 0x87DD0016: return L"AM_E_XASD_TIMEOUT"; + case 0x87DD0017: return L"AM_E_XASU_TIMEOUT"; + case 0x87DD0018: return L"AM_E_XAST_TIMEOUT"; + case 0x87DD0019: return L"AM_E_XSTS_TIMEOUT"; + case 0x8015DC00: return L"XO_E_DEVMODE_NOT_AUTHORIZED"; + case 0x8015DC01: return L"XO_E_SYSTEM_UPDATE_REQUIRED"; + case 0x8015DC02: return L"XO_E_CONTENT_UPDATE_REQUIRED"; + case 0x8015DC03: return L"XO_E_ENFORCEMENT_BAN"; + case 0x8015DC04: return L"XO_E_THIRD_PARTY_BAN"; + case 0x8015DC05: return L"XO_E_ACCOUNT_PARENTALLY_RESTRICTED"; + case 0x8015DC06: return L"XO_E_DEVICE_SUBSCRIPTION_NOT_ACTIVATED"; + case 0x8015DC08: return L"XO_E_ACCOUNT_BILLING_MAINTENANCE_REQUIRED"; + case 0x8015DC09: return L"XO_E_ACCOUNT_CREATION_REQUIRED"; + case 0x8015DC0A: return L"XO_E_ACCOUNT_TERMS_OF_USE_NOT_ACCEPTED"; + case 0x8015DC0B: return L"XO_E_ACCOUNT_COUNTRY_NOT_AUTHORIZED"; + case 0x8015DC0C: return L"XO_E_ACCOUNT_AGE_VERIFICATION_REQUIRED"; + case 0x8015DC0D: return L"XO_E_ACCOUNT_CURFEW"; + case 0x8015DC0E: return L"XO_E_ACCOUNT_ZEST_MAINTENANCE_REQUIRED"; + case 0x8015DC0F: return L"XO_E_ACCOUNT_CSV_TRANSITION_REQUIRED"; + case 0x8015DC10: return L"XO_E_ACCOUNT_MAINTENANCE_REQUIRED"; + case 0x8015DC11: return L"XO_E_ACCOUNT_TYPE_NOT_ALLOWED"; + case 0x8015DC12: return L"XO_E_CONTENT_ISOLATION"; + case 0x8015DC13: return L"XO_E_ACCOUNT_NAME_CHANGE_REQUIRED"; + case 0x8015DC14: return L"XO_E_DEVICE_CHALLENGE_REQUIRED"; + case 0x8015DC20: return L"XO_E_EXPIRED_DEVICE_TOKEN"; + case 0x8015DC21: return L"XO_E_EXPIRED_TITLE_TOKEN"; + case 0x8015DC22: return L"XO_E_EXPIRED_USER_TOKEN"; + case 0x8015DC23: return L"XO_E_INVALID_DEVICE_TOKEN"; + case 0x8015DC24: return L"XO_E_INVALID_TITLE_TOKEN"; + case 0x8015DC25: return L"XO_E_INVALID_USER_TOKEN"; + + // winsock errors + case MAKE_HRESULT(1,7,WSAEWOULDBLOCK) : return L"WSAEWOULDBLOCK"; + case MAKE_HRESULT(1,7,WSAEINPROGRESS) : return L"WSAEINPROGRESS"; + case MAKE_HRESULT(1,7,WSAEALREADY) : return L"WSAEALREADY"; + case MAKE_HRESULT(1,7,WSAENOTSOCK) : return L"WSAENOTSOCK"; + case MAKE_HRESULT(1,7,WSAEDESTADDRREQ) : return L"WSAEDESTADDRREQ"; + case MAKE_HRESULT(1,7,WSAEMSGSIZE) : return L"WSAEMSGSIZE"; + case MAKE_HRESULT(1,7,WSAEPROTOTYPE) : return L"WSAEPROTOTYPE"; + case MAKE_HRESULT(1,7,WSAENOPROTOOPT) : return L"WSAENOPROTOOPT"; + case MAKE_HRESULT(1,7,WSAEPROTONOSUPPORT) : return L"WSAEPROTONOSUPPORT"; + case MAKE_HRESULT(1,7,WSAESOCKTNOSUPPORT) : return L"WSAESOCKTNOSUPPORT"; + case MAKE_HRESULT(1,7,WSAEOPNOTSUPP) : return L"WSAEOPNOTSUPP"; + case MAKE_HRESULT(1,7,WSAEPFNOSUPPORT) : return L"WSAEPFNOSUPPORT"; + case MAKE_HRESULT(1,7,WSAEAFNOSUPPORT) : return L"WSAEAFNOSUPPORT"; + case MAKE_HRESULT(1,7,WSAEADDRINUSE) : return L"WSAEADDRINUSE"; + case MAKE_HRESULT(1,7,WSAEADDRNOTAVAIL) : return L"WSAEADDRNOTAVAIL"; + case MAKE_HRESULT(1,7,WSAENETDOWN) : return L"WSAENETDOWN"; + case MAKE_HRESULT(1,7,WSAENETUNREACH) : return L"WSAENETUNREACH"; + case MAKE_HRESULT(1,7,WSAENETRESET) : return L"WSAENETRESET"; + case MAKE_HRESULT(1,7,WSAECONNABORTED) : return L"WSAECONNABORTED"; + case MAKE_HRESULT(1,7,WSAECONNRESET) : return L"WSAECONNRESET"; + case MAKE_HRESULT(1,7,WSAENOBUFS) : return L"WSAENOBUFS"; + case MAKE_HRESULT(1,7,WSAEISCONN) : return L"WSAEISCONN"; + case MAKE_HRESULT(1,7,WSAENOTCONN) : return L"WSAENOTCONN"; + case MAKE_HRESULT(1,7,WSAESHUTDOWN) : return L"WSAESHUTDOWN"; + case MAKE_HRESULT(1,7,WSAETOOMANYREFS) : return L"WSAETOOMANYREFS"; + case MAKE_HRESULT(1,7,WSAETIMEDOUT) : return L"WSAETIMEDOUT"; + case MAKE_HRESULT(1,7,WSAECONNREFUSED) : return L"WSAECONNREFUSED"; + case MAKE_HRESULT(1,7,WSAELOOP) : return L"WSAELOOP"; + case MAKE_HRESULT(1,7,WSAENAMETOOLONG) : return L"WSAENAMETOOLONG"; + case MAKE_HRESULT(1,7,WSAEHOSTDOWN) : return L"WSAEHOSTDOWN"; + case MAKE_HRESULT(1,7,WSAEHOSTUNREACH) : return L"WSAEHOSTUNREACH"; + case MAKE_HRESULT(1,7,WSAENOTEMPTY) : return L"WSAENOTEMPTY"; + case MAKE_HRESULT(1,7,WSAEPROCLIM) : return L"WSAEPROCLIM"; + case MAKE_HRESULT(1,7,WSAEUSERS) : return L"WSAEUSERS"; + case MAKE_HRESULT(1,7,WSAEDQUOT) : return L"WSAEDQUOT"; + case MAKE_HRESULT(1,7,WSAESTALE) : return L"WSAESTALE"; + case MAKE_HRESULT(1,7,WSAEREMOTE) : return L"WSAEREMOTE"; + case MAKE_HRESULT(1,7,WSASYSNOTREADY) : return L"WSASYSNOTREADY"; + case MAKE_HRESULT(1,7,WSAVERNOTSUPPORTED) : return L"WSAVERNOTSUPPORTED"; + case MAKE_HRESULT(1,7,WSANOTINITIALISED) : return L"WSANOTINITIALISED"; + case MAKE_HRESULT(1,7,WSAEDISCON) : return L"WSAEDISCON"; + case MAKE_HRESULT(1,7,WSAENOMORE) : return L"WSAENOMORE"; + case MAKE_HRESULT(1,7,WSAECANCELLED) : return L"WSAECANCELLED"; + case MAKE_HRESULT(1,7,WSAEINVALIDPROCTABLE) : return L"WSAEINVALIDPROCTABLE"; + case MAKE_HRESULT(1,7,WSAEINVALIDPROVIDER) : return L"WSAEINVALIDPROVIDER"; + case MAKE_HRESULT(1,7,WSAEPROVIDERFAILEDINIT) : return L"WSAEPROVIDERFAILEDINIT"; + case MAKE_HRESULT(1,7,WSASYSCALLFAILURE) : return L"WSASYSCALLFAILURE"; + case MAKE_HRESULT(1,7,WSASERVICE_NOT_FOUND) : return L"WSASERVICE_NOT_FOUND"; + case MAKE_HRESULT(1,7,WSATYPE_NOT_FOUND) : return L"WSATYPE_NOT_FOUND"; + case MAKE_HRESULT(1,7,WSA_E_NO_MORE) : return L"WSA_E_NO_MORE"; + case MAKE_HRESULT(1,7,WSA_E_CANCELLED) : return L"WSA_E_CANCELLED"; + case MAKE_HRESULT(1,7,WSAEREFUSED) : return L"WSAEREFUSED"; + case MAKE_HRESULT(1,7,WSAHOST_NOT_FOUND) : return L"WSAHOST_NOT_FOUND"; + case MAKE_HRESULT(1,7,WSATRY_AGAIN) : return L"WSATRY_AGAIN"; + case MAKE_HRESULT(1,7,WSANO_RECOVERY) : return L"WSANO_RECOVERY"; + case MAKE_HRESULT(1,7,WSANO_DATA) : return L"WSANO_DATA"; + case MAKE_HRESULT(1,7,WSA_QOS_RECEIVERS) : return L"WSA_QOS_RECEIVERS"; + case MAKE_HRESULT(1,7,WSA_QOS_SENDERS) : return L"WSA_QOS_SENDERS"; + case MAKE_HRESULT(1,7,WSA_QOS_NO_SENDERS) : return L"WSA_QOS_NO_SENDERS"; + case MAKE_HRESULT(1,7,WSA_QOS_NO_RECEIVERS) : return L"WSA_QOS_NO_RECEIVERS"; + case MAKE_HRESULT(1,7,WSA_QOS_REQUEST_CONFIRMED) : return L"WSA_QOS_REQUEST_CONFIRMED"; + case MAKE_HRESULT(1,7,WSA_QOS_ADMISSION_FAILURE) : return L"WSA_QOS_ADMISSION_FAILURE"; + case MAKE_HRESULT(1,7,WSA_QOS_POLICY_FAILURE) : return L"WSA_QOS_POLICY_FAILURE"; + case MAKE_HRESULT(1,7,WSA_QOS_BAD_STYLE) : return L"WSA_QOS_BAD_STYLE"; + case MAKE_HRESULT(1,7,WSA_QOS_BAD_OBJECT) : return L"WSA_QOS_BAD_OBJECT"; + case MAKE_HRESULT(1,7,WSA_QOS_TRAFFIC_CTRL_ERROR) : return L"WSA_QOS_TRAFFIC_CTRL_ERROR"; + case MAKE_HRESULT(1,7,WSA_QOS_GENERIC_ERROR) : return L"WSA_QOS_GENERIC_ERROR"; + case MAKE_HRESULT(1,7,WSA_QOS_ESERVICETYPE) : return L"WSA_QOS_ESERVICETYPE"; + case MAKE_HRESULT(1,7,WSA_QOS_EFLOWSPEC) : return L"WSA_QOS_EFLOWSPEC"; + case MAKE_HRESULT(1,7,WSA_QOS_EPROVSPECBUF) : return L"WSA_QOS_EPROVSPECBUF"; + case MAKE_HRESULT(1,7,WSA_QOS_EFILTERSTYLE) : return L"WSA_QOS_EFILTERSTYLE"; + case MAKE_HRESULT(1,7,WSA_QOS_EFILTERTYPE) : return L"WSA_QOS_EFILTERTYPE"; + case MAKE_HRESULT(1,7,WSA_QOS_EFILTERCOUNT) : return L"WSA_QOS_EFILTERCOUNT"; + case MAKE_HRESULT(1,7,WSA_QOS_EOBJLENGTH) : return L"WSA_QOS_EOBJLENGTH"; + case MAKE_HRESULT(1,7,WSA_QOS_EFLOWCOUNT) : return L"WSA_QOS_EFLOWCOUNT"; + case MAKE_HRESULT(1,7,WSA_QOS_EUNKOWNPSOBJ) : return L"WSA_QOS_EUNKOWNPSOBJ"; + case MAKE_HRESULT(1,7,WSA_QOS_EPOLICYOBJ) : return L"WSA_QOS_EPOLICYOBJ"; + case MAKE_HRESULT(1,7,WSA_QOS_EFLOWDESC) : return L"WSA_QOS_EFLOWDESC"; + case MAKE_HRESULT(1,7,WSA_QOS_EPSFLOWSPEC) : return L"WSA_QOS_EPSFLOWSPEC"; + case MAKE_HRESULT(1,7,WSA_QOS_EPSFILTERSPEC) : return L"WSA_QOS_EPSFILTERSPEC"; + case MAKE_HRESULT(1,7,WSA_QOS_ESDMODEOBJ) : return L"WSA_QOS_ESDMODEOBJ"; + case MAKE_HRESULT(1,7,WSA_QOS_ESHAPERATEOBJ) : return L"WSA_QOS_ESHAPERATEOBJ"; + case MAKE_HRESULT(1,7,WSA_QOS_RESERVED_PETYPE) : return L"WSA_QOS_RESERVED_PETYPE"; + + // HTTP specific errors + case WEB_E_UNSUPPORTED_FORMAT: return L"WEB_E_UNSUPPORTED_FORMAT"; + case WEB_E_INVALID_XML: return L"WEB_E_INVALID_XML"; + case WEB_E_MISSING_REQUIRED_ELEMENT: return L"WEB_E_MISSING_REQUIRED_ELEMENT"; + case WEB_E_MISSING_REQUIRED_ATTRIBUTE: return L"WEB_E_MISSING_REQUIRED_ATTRIBUTE"; + case WEB_E_UNEXPECTED_CONTENT: return L"WEB_E_UNEXPECTED_CONTENT"; + case WEB_E_RESOURCE_TOO_LARGE: return L"WEB_E_RESOURCE_TOO_LARGE"; + case WEB_E_INVALID_JSON_STRING: return L"WEB_E_INVALID_JSON_STRING"; + case WEB_E_INVALID_JSON_NUMBER: return L"WEB_E_INVALID_JSON_NUMBER"; + case WEB_E_JSON_VALUE_NOT_FOUND: return L"WEB_E_JSON_VALUE_NOT_FOUND"; + case HTTP_E_STATUS_UNEXPECTED: return L"HTTP_E_STATUS_UNEXPECTED"; + case HTTP_E_STATUS_UNEXPECTED_REDIRECTION: return L"HTTP_E_STATUS_UNEXPECTED_REDIRECTION"; + case HTTP_E_STATUS_UNEXPECTED_CLIENT_ERROR: return L"HTTP_E_STATUS_UNEXPECTED_CLIENT_ERROR"; + case HTTP_E_STATUS_UNEXPECTED_SERVER_ERROR: return L"HTTP_E_STATUS_UNEXPECTED_SERVER_ERROR"; + case HTTP_E_STATUS_AMBIGUOUS: return L"HTTP_E_STATUS_AMBIGUOUS"; + case HTTP_E_STATUS_MOVED: return L"HTTP_E_STATUS_MOVED"; + case HTTP_E_STATUS_REDIRECT: return L"HTTP_E_STATUS_REDIRECT"; + case HTTP_E_STATUS_REDIRECT_METHOD: return L"HTTP_E_STATUS_REDIRECT_METHOD"; + case HTTP_E_STATUS_NOT_MODIFIED: return L"HTTP_E_STATUS_NOT_MODIFIED"; + case HTTP_E_STATUS_USE_PROXY: return L"HTTP_E_STATUS_USE_PROXY"; + case HTTP_E_STATUS_REDIRECT_KEEP_VERB: return L"HTTP_E_STATUS_REDIRECT_KEEP_VERB"; + case HTTP_E_STATUS_BAD_REQUEST: return L"HTTP_E_STATUS_BAD_REQUEST"; + case HTTP_E_STATUS_DENIED: return L"HTTP_E_STATUS_DENIED"; + case HTTP_E_STATUS_PAYMENT_REQ: return L"HTTP_E_STATUS_PAYMENT_REQ"; + case HTTP_E_STATUS_FORBIDDEN: return L"HTTP_E_STATUS_FORBIDDEN"; + case HTTP_E_STATUS_NOT_FOUND: return L"HTTP_E_STATUS_NOT_FOUND"; + case HTTP_E_STATUS_BAD_METHOD: return L"HTTP_E_STATUS_BAD_METHOD"; + case HTTP_E_STATUS_NONE_ACCEPTABLE: return L"HTTP_E_STATUS_NONE_ACCEPTABLE"; + case HTTP_E_STATUS_PROXY_AUTH_REQ: return L"HTTP_E_STATUS_PROXY_AUTH_REQ"; + case HTTP_E_STATUS_REQUEST_TIMEOUT: return L"HTTP_E_STATUS_REQUEST_TIMEOUT"; + case HTTP_E_STATUS_CONFLICT: return L"HTTP_E_STATUS_CONFLICT"; + case HTTP_E_STATUS_GONE: return L"HTTP_E_STATUS_GONE"; + case HTTP_E_STATUS_LENGTH_REQUIRED: return L"HTTP_E_STATUS_LENGTH_REQUIRED"; + case HTTP_E_STATUS_PRECOND_FAILED: return L"HTTP_E_STATUS_PRECOND_FAILED"; + case HTTP_E_STATUS_REQUEST_TOO_LARGE: return L"HTTP_E_STATUS_REQUEST_TOO_LARGE"; + case HTTP_E_STATUS_URI_TOO_LONG: return L"HTTP_E_STATUS_URI_TOO_LONG"; + case HTTP_E_STATUS_UNSUPPORTED_MEDIA: return L"HTTP_E_STATUS_UNSUPPORTED_MEDIA"; + case HTTP_E_STATUS_RANGE_NOT_SATISFIABLE: return L"HTTP_E_STATUS_RANGE_NOT_SATISFIABLE"; + case HTTP_E_STATUS_EXPECTATION_FAILED: return L"HTTP_E_STATUS_EXPECTATION_FAILED"; + case HTTP_E_STATUS_SERVER_ERROR: return L"HTTP_E_STATUS_SERVER_ERROR"; + case HTTP_E_STATUS_NOT_SUPPORTED: return L"HTTP_E_STATUS_NOT_SUPPORTED"; + case HTTP_E_STATUS_BAD_GATEWAY: return L"HTTP_E_STATUS_BAD_GATEWAY"; + case HTTP_E_STATUS_SERVICE_UNAVAIL: return L"HTTP_E_STATUS_SERVICE_UNAVAIL"; + case HTTP_E_STATUS_GATEWAY_TIMEOUT: return L"HTTP_E_STATUS_GATEWAY_TIMEOUT"; + case HTTP_E_STATUS_VERSION_NOT_SUP: return L"HTTP_E_STATUS_VERSION_NOT_SUP"; + + // WinINet specific errors + case INET_E_INVALID_URL: return L"INET_E_INVALID_URL"; + case INET_E_NO_SESSION: return L"INET_E_NO_SESSION"; + case INET_E_CANNOT_CONNECT: return L"INET_E_CANNOT_CONNECT"; + case INET_E_RESOURCE_NOT_FOUND: return L"INET_E_RESOURCE_NOT_FOUND"; + case INET_E_OBJECT_NOT_FOUND: return L"INET_E_OBJECT_NOT_FOUND"; + case INET_E_DATA_NOT_AVAILABLE: return L"INET_E_DATA_NOT_AVAILABLE"; + case INET_E_DOWNLOAD_FAILURE: return L"INET_E_DOWNLOAD_FAILURE"; + case INET_E_AUTHENTICATION_REQUIRED: return L"INET_E_AUTHENTICATION_REQUIRED"; + case INET_E_NO_VALID_MEDIA: return L"INET_E_NO_VALID_MEDIA"; + case INET_E_CONNECTION_TIMEOUT: return L"INET_E_CONNECTION_TIMEOUT"; + case INET_E_INVALID_REQUEST: return L"INET_E_INVALID_REQUEST"; + case INET_E_UNKNOWN_PROTOCOL: return L"INET_E_UNKNOWN_PROTOCOL"; + case INET_E_SECURITY_PROBLEM: return L"INET_E_SECURITY_PROBLEM"; + case INET_E_CANNOT_LOAD_DATA: return L"INET_E_CANNOT_LOAD_DATA"; + case INET_E_CANNOT_INSTANTIATE_OBJECT: return L"INET_E_CANNOT_INSTANTIATE_OBJECT"; + case INET_E_INVALID_CERTIFICATE: return L"INET_E_INVALID_CERTIFICATE"; + case INET_E_REDIRECT_FAILED: return L"INET_E_REDIRECT_FAILED"; + case INET_E_REDIRECT_TO_DIR: return L"INET_E_REDIRECT_TO_DIR"; + } + + return L""; +} diff --git a/Minecraft.Client/Durango/Network/DQRNetworkManager_SendReceive.cpp b/Minecraft.Client/Durango/Network/DQRNetworkManager_SendReceive.cpp new file mode 100644 index 00000000..9232d095 --- /dev/null +++ b/Minecraft.Client/Durango/Network/DQRNetworkManager_SendReceive.cpp @@ -0,0 +1,409 @@ +#include "stdafx.h" + +#include "DQRNetworkManager.h" +#include "PartyController.h" +#include +#include +#include +#include "..\Minecraft.World\StringHelpers.h" +#include "base64.h" + +#ifdef _DURANGO +#include "..\Minecraft.World\DurangoStats.h" +#endif + +#include "ChatIntegrationLayer.h" + +using namespace Concurrency; +using namespace Windows::Foundation::Collections; + +// This method is called when bytes have been received that are to be passed on to the game itself. The data is associated with a small id so we can specify which network player +// that it was received for. +void DQRNetworkManager::BytesReceived(int smallId, BYTE *bytes, int byteCount) +{ + DQRNetworkPlayer *host = GetPlayerBySmallId(m_hostSmallId); + DQRNetworkPlayer *client = GetPlayerBySmallId(smallId); + + if( ( host == NULL ) || ( client == NULL ) ) + { + return; + } + + if( m_isHosting ) + { + m_listener->HandleDataReceived(client, host, bytes, byteCount ); + } + else + { + m_listener->HandleDataReceived(host, client, bytes, byteCount ); + } +// app.DebugPrintf("%d bytes received: %s\n", byteCount, bytes); +} + +// This method is called when network data is received, that is to be processed by the DQRNetworkManager itself. This is for handling internal +// updates such as assigning & unassigning of small Ids, transmission of the table of players currently in the session etc. +// Processing of these things is handled as a state machine so that we can receive a message split over more than one call to this method should +// the underlying communcation layer split data up somehow. +void DQRNetworkManager::BytesReceivedInternal(DQRConnectionInfo *connectionInfo, unsigned int sessionAddress, BYTE *bytes, int byteCount) +{ + BYTE *pNextByte = bytes; + BYTE *pEndByte = pNextByte + byteCount; + + do + { + BYTE byte = *pNextByte; + switch( connectionInfo->m_internalDataState ) + { + case DQRConnectionInfo::ConnectionState_InternalHeaderByte: + switch( byte ) + { + case DQR_INTERNAL_ASSIGN_SMALL_IDS: + connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAssignSmallIdMask; + break; + case DQR_INTERNAL_UNASSIGN_SMALL_ID: + // Host only + if( connectionInfo->m_channelActive[connectionInfo->m_currentChannel] ) + { + int smallId = connectionInfo->m_smallId[connectionInfo->m_currentChannel]; + connectionInfo->m_channelActive[connectionInfo->m_currentChannel] = false; + m_sessionAddressFromSmallId[smallId] = 0; + DQRNetworkPlayer *pPlayer = GetPlayerBySmallId(smallId); + if( pPlayer ) + { + RemoveRoomSyncPlayer(pPlayer); + SendRoomSyncInfo(); + } + } + break; + case DQR_INTERNAL_PLAYER_TABLE: + connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalRoomSyncData; + connectionInfo->m_pucRoomSyncData = new unsigned char[4]; + connectionInfo->m_roomSyncDataBytesToRead = 0; + connectionInfo->m_roomSyncDataBytesRead = 0; + break; + case DQR_INTERNAL_ADD_PLAYER_FAILED: + connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAddPlayerFailedData; + connectionInfo->m_pucAddFailedPlayerData = new unsigned char[4]; + connectionInfo->m_addFailedPlayerDataBytesToRead = 0; + connectionInfo->m_addFailedPlayerDataBytesRead = 0; + break; + default: + break; + } + pNextByte++; + break; + case DQRConnectionInfo::ConnectionState_InternalAssignSmallIdMask: + // Up to 4 smallIds are assigned at once, with the ones that are being assigned dictated by a mask byte which is passed in first. + // The small Ids themselves follow, always 4 bytes, and any that are masked as being assigned are processed, the other bytes ignored. + // In order work around a bug with the networking library, this particular packet (being the first this that is sent from a client) + // is at first sent unreliably, with retries, until a message is received back to the client, or it times out. We therefore have to be able + // to handle (and ignore) this being received more than once + DQRNetworkManager::LogCommentFormat(L"Small Ids being received"); + connectionInfo->m_smallIdReadMask = byte; + // Create a uniquely allocated byte to which names have been resolved, as another one of these packets could be received whilst that asyncronous process is going o n + connectionInfo->m_pucsmallIdReadMaskResolved = new unsigned char; + *connectionInfo->m_pucsmallIdReadMaskResolved = 0; + connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAssignSmallId0; + connectionInfo->m_initialPacketReceived = true; + + pNextByte++; + break; + case DQRConnectionInfo::ConnectionState_InternalAssignSmallId0: + case DQRConnectionInfo::ConnectionState_InternalAssignSmallId1: + case DQRConnectionInfo::ConnectionState_InternalAssignSmallId2: + case DQRConnectionInfo::ConnectionState_InternalAssignSmallId3: + { + int channel = ((int)connectionInfo->m_internalDataState) - DQRConnectionInfo::ConnectionState_InternalAssignSmallId0; + + if( ( connectionInfo->m_smallIdReadMask & ( 1 << channel ) ) && ( !connectionInfo->m_channelActive[channel] ) ) + { + // HOST ONLY + // Store the small Id that is associated with this send channel. In order work around a bug with the networking library, this particular packet + // (being the first this that is sent from a client) is sent unreliably, with retries, until a message is received back to the client, or it times out. + // We therefore have to be able to handle (and ignore) this being received more than once - hence the check of the bool above. + // At this point, the connection is considered properly active from the point of view of the host. + + int sessionIndex = GetSessionIndexForSmallId(byte); + if( sessionIndex != -1 ) + { + connectionInfo->m_channelActive[channel] = true; + connectionInfo->m_smallId[channel] = byte; + + m_sessionAddressFromSmallId[byte] = sessionAddress; + m_channelFromSmallId[byte] = channel; + + auto pAsyncOp = m_primaryUserXboxLiveContext->ProfileService->GetUserProfileAsync(m_multiplayerSession->Members->GetAt(sessionIndex)->XboxUserId); + DQRNetworkManager::LogCommentFormat(L"Session index of %d found for player with small id %d - attempting to resolve display name\n",sessionIndex,byte); + + DQRNetworkPlayer *pPlayer = new DQRNetworkPlayer(this, DQRNetworkPlayer::DNP_TYPE_REMOTE, true, 0, sessionAddress); + pPlayer->SetSmallId(byte); + pPlayer->SetUID(PlayerUID(m_multiplayerSession->Members->GetAt(sessionIndex)->XboxUserId->Data())); + + HostGamertagResolveDetails *resolveDetails = new HostGamertagResolveDetails(); + resolveDetails->m_pPlayer = pPlayer; + resolveDetails->m_sessionAddress = sessionAddress; + resolveDetails->m_channel = channel; + resolveDetails->m_sync = false; + + int mask = 1 << channel; + unsigned char *pucsmallIdReadMaskResolved = connectionInfo->m_pucsmallIdReadMaskResolved; + unsigned char ucsmallIdReadMask = connectionInfo->m_smallIdReadMask; + create_task( pAsyncOp ).then( [this,resolveDetails,mask,pucsmallIdReadMaskResolved,ucsmallIdReadMask] (task resultTask) + { + try + { + Microsoft::Xbox::Services::Social::XboxUserProfile^ result = resultTask.get(); + + resolveDetails->m_name.assign(result->Gamertag->Data()); // Use the gamertag for this data, as it is synchronised round all the machines and so we can't use a display name that could be a real name + + EnterCriticalSection(&m_csHostGamertagResolveResults); + // Update flags for which names have been resolved, and if this completes this set, then set the flag to say that we should synchronise these out to the clients + *pucsmallIdReadMaskResolved |= mask; + LogCommentFormat(L"<<>> Compare %d to %d",*pucsmallIdReadMaskResolved,ucsmallIdReadMask); + if(ucsmallIdReadMask == *pucsmallIdReadMaskResolved) + { + resolveDetails->m_sync = true; + delete pucsmallIdReadMaskResolved; + } + m_hostGamertagResolveResults.push(resolveDetails); + LeaveCriticalSection(&m_csHostGamertagResolveResults); + } + + catch (Platform::Exception^ ex) + { + LogComment("Name resolve exception raised"); + // TODO - handle errors more usefully than just not setting the name... + EnterCriticalSection(&m_csHostGamertagResolveResults); + // Update flags for which names have been resolved, and if this completes this set, then set the flag to say that we should synchronise these out to the clients + *pucsmallIdReadMaskResolved |= mask; + if(ucsmallIdReadMask == *pucsmallIdReadMaskResolved) + { + resolveDetails->m_sync = true; + delete pucsmallIdReadMaskResolved; + } + m_hostGamertagResolveResults.push(resolveDetails); + LeaveCriticalSection(&m_csHostGamertagResolveResults); + } + }); + } + } + } + + switch(connectionInfo->m_internalDataState) + { + case DQRConnectionInfo::ConnectionState_InternalAssignSmallId0: + connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAssignSmallId1; + break; + case DQRConnectionInfo::ConnectionState_InternalAssignSmallId1: + connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAssignSmallId2; + break; + case DQRConnectionInfo::ConnectionState_InternalAssignSmallId2: + connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalAssignSmallId3; + break; + case DQRConnectionInfo::ConnectionState_InternalAssignSmallId3: + connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalHeaderByte; + break; + } + + pNextByte++; + break; + case DQRConnectionInfo::ConnectionState_InternalRoomSyncData: + connectionInfo->m_pucRoomSyncData[connectionInfo->m_roomSyncDataBytesRead++] = byte; + // The room sync info is sent as a 4 byte count of the length of XUID strings, then the RoomSyncData, then the XUID strings + if( connectionInfo->m_roomSyncDataBytesToRead == 0 ) + { + // At first stage of reading the 4 byte count + if( connectionInfo->m_roomSyncDataBytesRead == 4 ) + { + memcpy( &connectionInfo->m_roomSyncDataBytesToRead, connectionInfo->m_pucRoomSyncData, 4); + delete [] connectionInfo->m_pucRoomSyncData; + connectionInfo->m_roomSyncDataBytesToRead += sizeof(RoomSyncData); + connectionInfo->m_pucRoomSyncData = new unsigned char[ connectionInfo->m_roomSyncDataBytesToRead ]; + connectionInfo->m_roomSyncDataBytesRead = 0; + } + } + else if( connectionInfo->m_roomSyncDataBytesRead == connectionInfo->m_roomSyncDataBytesToRead ) + { + // Second stage of reading the variable length data - when we've read this all, we can created storage for the XUID strings and copy them all in + RoomSyncData *roomSyncData = (RoomSyncData *)connectionInfo->m_pucRoomSyncData; + wchar_t *pwcsData = (wchar_t *)((unsigned char *)connectionInfo->m_pucRoomSyncData + sizeof(RoomSyncData)); + for( int i = 0; i < roomSyncData->playerCount; i++ ) + { + unsigned int thisWchars = ( wcslen(pwcsData) + 1 ); + roomSyncData->players[i].m_XUID = new wchar_t[thisWchars]; + wcsncpy(roomSyncData->players[i].m_XUID, pwcsData, thisWchars); + pwcsData += thisWchars; + } + // Update the room sync data with this new data. This will handle notification of new and removed players + UpdateRoomSyncPlayers((RoomSyncData *)connectionInfo->m_pucRoomSyncData); + + delete connectionInfo->m_pucRoomSyncData; + connectionInfo->m_pucRoomSyncData = NULL; + connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalHeaderByte; + + // If we haven't actually established a connection yet for this channel, then this is the point where we can consider this active + if( !connectionInfo->m_channelActive[connectionInfo->m_currentChannel] ) + { + DQRNetworkManager::LogCommentFormat(L"Received data from host, channel %d considered active (%d bytes)\n",connectionInfo->m_currentChannel,connectionInfo->m_bytesRemaining); + connectionInfo->m_channelActive[connectionInfo->m_currentChannel] = true; + + // This is also the time (as a client) to inform the chat integration layer of the host's session address, since we can now (reliably) send data to it + if(connectionInfo->m_currentChannel == 0) + { + if( m_chat ) + { + m_chat->OnNewSessionAddressAdded(m_hostSessionAddress); + } + } + } + + // Move to starting & playing states, if we are still joining rather than adding an additional player from this client, and we have all the local players here. + // We need to check that they are all here because we could have received a broadcast room sync data caused by another machine joining, and and so we can't assume + // that we're ready to go just yet. + if( m_state == DQRNetworkManager::DNM_INT_STATE_JOINING_SENDING_UNRELIABLE ) + { + bool allLocalPlayersHere = true; + for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ ) + { + if( m_currentUserMask & ( 1 << i ) ) + { + if( GetLocalPlayerByUserIndex(i) == NULL ) + { + allLocalPlayersHere = false; + } + } + } + if( allLocalPlayersHere ) + { + DQRNetworkManager::LogComment(L"All local players present"); + SetState(DQRNetworkManager::DNM_INT_STATE_STARTING); + SetState(DQRNetworkManager::DNM_INT_STATE_PLAYING); + } + else + { + // Our players aren't all here yet. Going to keep on sending unreliable packets though until the connection is up and running + DQRNetworkManager::LogComment(L"All local players not yet present"); + } + } + } + pNextByte++; + break; + case DQRConnectionInfo::ConnectionState_InternalAddPlayerFailedData: + connectionInfo->m_pucAddFailedPlayerData[connectionInfo->m_addFailedPlayerDataBytesRead++] = byte; + // The failed player info is sent as a 4 byte count of the length of XUID string, then the string itself + if( connectionInfo->m_addFailedPlayerDataBytesToRead == 0 ) + { + // At first stage of reading the 4 byte count + if( connectionInfo->m_addFailedPlayerDataBytesRead == 4 ) + { + memcpy( &connectionInfo->m_addFailedPlayerDataBytesToRead, connectionInfo->m_pucAddFailedPlayerData, 4); + delete [] connectionInfo->m_pucAddFailedPlayerData; + connectionInfo->m_pucAddFailedPlayerData = new unsigned char[ connectionInfo->m_addFailedPlayerDataBytesToRead ]; + connectionInfo->m_addFailedPlayerDataBytesRead = 0; + } + } + else if( connectionInfo->m_addFailedPlayerDataBytesRead == connectionInfo->m_addFailedPlayerDataBytesToRead ) + { + // XUID fully read, can now handle what to do with it + AddPlayerFailed(ref new Platform::String( (wchar_t *)connectionInfo->m_pucAddFailedPlayerData ) ); + delete [] connectionInfo->m_pucAddFailedPlayerData; + connectionInfo->m_pucAddFailedPlayerData = NULL; + connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalHeaderByte; + } + + pNextByte++; + break; + } + } while (pNextByte != pEndByte); +} + +// This method directly sends bytes via the network communication layer, used to send both game data & data internal to the DQRNetworkManager itself. +// This is used by higher level sending methods that wrap communications up with headers that can be processed at the receiving end. +void DQRNetworkManager::SendBytesRaw(int smallId, BYTE *bytes, int byteCount, bool reliableAndSequential) +{ + bool broadcast; + unsigned int sessionAddress; + +// app.DebugPrintf("{%d,%d - %d}\n",smallId,reliableAndSequential,byteCount); + + if( smallId == -1 ) + { + LogCommentFormat(L"Attempting broadcast, exception of address m_XRNS_Session->LocalSessionAddress %d %d %d", smallId, byteCount, reliableAndSequential); + // Broadcast, used from host only + broadcast = true; + sessionAddress = 0; + } + else + { + // Send to individual session address + broadcast = false; + if( m_isHosting ) + { + sessionAddress = m_sessionAddressFromSmallId[ smallId ]; + } + else + { + sessionAddress = m_hostSessionAddress; + } + } + RTS_SendData(bytes, byteCount, sessionAddress, reliableAndSequential, reliableAndSequential, reliableAndSequential, broadcast, true); +} + +// This method is called by the chat integration layer to be able to send data +void DQRNetworkManager::SendBytesChat(unsigned int address, BYTE *bytes, int byteCount, bool reliable, bool sequential, bool broadcast) +{ + unsigned int sessionAddress; + + if( broadcast ) + { + sessionAddress = 0; + } + else + { + // Send to individual session address + sessionAddress = address; + } + + RTS_SendData(bytes, byteCount, sessionAddress, reliable, sequential, false, broadcast, false); +} + +// This is the higher level sending method for sending game data - this prefixes the send with a header so that it will get routed to the correct player. +void DQRNetworkManager::SendBytes(int smallId, BYTE *bytes, int byteCount) +{ + EnterCriticalSection(&m_csSendBytes); + unsigned char *tempSendBuffer = (unsigned char *)malloc(8191 + 2); + + BYTE *data = bytes; + BYTE *dataEnd = bytes + byteCount; + + // Data to be sent has a header to say which of our own internal channels it is on (2 bits), and number of bytes that are in the message. + // The number of bytes has to be stored in 13 bits, and so a maximum of 8191 bytes can be send at a time. Split up longer messages into + // blocks of this size. + do + { + int bytesToSend = (int)(dataEnd - data); + if( bytesToSend > 8191 ) bytesToSend = 8191; + + // Send header with data sending mode - see full comment in DQRNetworkManagerEventHandlers::DataReceivedHandler + tempSendBuffer[0] = ( m_channelFromSmallId[smallId] << 5 ) | ( bytesToSend >> 8 ); + tempSendBuffer[1] = bytesToSend & 0xff; + memcpy(&tempSendBuffer[2], data, bytesToSend); + + SendBytesRaw(smallId, tempSendBuffer, bytesToSend + 2, true); + + data += bytesToSend; + } while (data != dataEnd); + + free(tempSendBuffer); + LeaveCriticalSection(&m_csSendBytes); +} + +int DQRNetworkManager::GetQueueSizeBytes() +{ + return m_RTS_Stat_totalBytes; +} + +int DQRNetworkManager::GetQueueSizeMessages() +{ + return m_RTS_Stat_totalSends; +} \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/DQRNetworkManager_XRNSEvent.cpp b/Minecraft.Client/Durango/Network/DQRNetworkManager_XRNSEvent.cpp new file mode 100644 index 00000000..c985a191 --- /dev/null +++ b/Minecraft.Client/Durango/Network/DQRNetworkManager_XRNSEvent.cpp @@ -0,0 +1,651 @@ +#include "stdafx.h" + +#include "DQRNetworkManager.h" +#include "PartyController.h" +#include +#include +#include +#include "..\Minecraft.World\StringHelpers.h" +#include "base64.h" + +#ifdef _DURANGO +#include "..\Minecraft.World\DurangoStats.h" +#endif + +#include "ChatIntegrationLayer.h" + +using namespace Concurrency; +using namespace Windows::Foundation::Collections; + +DQRNetworkManagerEventHandlers::DQRNetworkManagerEventHandlers(DQRNetworkManager *pDQRNet) +{ + m_pDQRNet = pDQRNet; +} + +void DQRNetworkManagerEventHandlers::Setup(WXNRs::Session^ session) +{ + try + { + m_dataReceivedToken = session->DataReceived += ref new Windows::Foundation::EventHandler(this, &DQRNetworkManagerEventHandlers::DataReceivedHandler); + m_sessionStatusToken = session->SessionStatusUpdate += ref new Windows::Foundation::EventHandler(this, &DQRNetworkManagerEventHandlers::SessionStatusUpdateHandler); + m_sessionAddressToken = session->SessionAddressDataChanged += ref new Windows::Foundation::EventHandler(this, &DQRNetworkManagerEventHandlers::SessionAddressDataChangedHandler); + m_addedSessionToken = session->AddedSessionAddress += ref new Windows::Foundation::EventHandler(this, &DQRNetworkManagerEventHandlers::AddedSessionAddressHandler); + m_removedSessionToken = session->RemovedSessionAddress += ref new Windows::Foundation::EventHandler(this, &DQRNetworkManagerEventHandlers::RemovedSessionAddressHandler); + m_globalDataToken = session->GlobalSessionDataChanged += ref new Windows::Foundation::EventHandler(this, &DQRNetworkManagerEventHandlers::GlobalSessionDataChangedHandler); + } + catch(Platform::COMException^ ex) + { + // swallow exceptions + } + catch(...) + { + // swallow exceptions + } +} + +void DQRNetworkManagerEventHandlers::Pulldown(WXNRs::Session^ session) +{ + try + { + session->DataReceived -= m_dataReceivedToken; + session->SessionStatusUpdate -= m_sessionStatusToken; + session->SessionAddressDataChanged -= m_sessionAddressToken; + session->AddedSessionAddress -= m_addedSessionToken; + session->RemovedSessionAddress -= m_removedSessionToken; + session->GlobalSessionDataChanged -= m_globalDataToken; + } + catch(Platform::COMException^ ex) + { + // swallow exceptions + } + catch(...) + { + // swallow exceptions + } +} + +// This event handler is called directly by the realtime session layer, when data is received. We split this data into into data that is meant to be +// handled internally by the DQRNetworkManager, and that which is meant to be passed on to the game itself as communication between players. +void DQRNetworkManagerEventHandlers::DataReceivedHandler(Platform::Object^ session, WXNRs::DataReceivedEventArgs^ args) +{ +// DQRNetworkManager::LogCommentFormat(L"DataReceivedHandler session addr: 0x%x (%d bytes)",args->SessionAddress,args->Data->Length); + + if (session == m_pDQRNet->m_XRNS_Session) + { + EnterCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming); + DQRNetworkManager::RTS_Message rtsMessage; + if( args->ChannelId == WXNRs::ChannelId::DefaultChatReceive ) + { + rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_DATA_RECEIVED_CHAT; + } + else + { + rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_DATA_RECEIVED; + } + rtsMessage.m_sessionAddress = args->SessionAddress; + rtsMessage.m_dataSize = args->Data->Length; + rtsMessage.m_pucData = (unsigned char *)malloc(rtsMessage.m_dataSize); + memcpy( rtsMessage.m_pucData, args->Data->Data, rtsMessage.m_dataSize ); + m_pDQRNet->m_RTSMessageQueueIncoming.push(rtsMessage); + LeaveCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming); + } +} + +// This event handler is called by the realtime session layer, when session address data is updated. We don't currently use session address data. +void DQRNetworkManagerEventHandlers::SessionAddressDataChangedHandler(Platform::Object^ session, WXNRs::SessionAddressDataChangedEventArgs^ args) +{ + DQRNetworkManager::LogComment(L"SessionAddressDataChangedHandler"); +} + +// This event handler is called by the realtime session layer when a session changes status. We use this to determine that a connection has been made made to the host, +// and the case when a connection has been terminated. +void DQRNetworkManagerEventHandlers::SessionStatusUpdateHandler(Platform::Object^ session, WXNRs::SessionStatusUpdateEventArgs^ args) +{ + DQRNetworkManager::LogComment(L"SessionStatusUpdateHandler"); + if (m_pDQRNet->m_XRNS_Session == session) + { + switch(args->NewStatus) + { + case WXNRs::SessionStatus::Active: + { + DQRNetworkManager::LogComment(L"Session active"); + m_pDQRNet->m_XRNS_LocalAddress = m_pDQRNet->m_XRNS_Session->LocalSessionAddress; + m_pDQRNet->m_XRNS_OldestAddress = m_pDQRNet->m_XRNS_Session->OldestSessionAddress; + EnterCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming); + DQRNetworkManager::RTS_Message rtsMessage; + rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_STATUS_ACTIVE; + rtsMessage.m_sessionAddress = 0; + rtsMessage.m_dataSize = 0; + rtsMessage.m_pucData = 0; + m_pDQRNet->m_RTSMessageQueueIncoming.push(rtsMessage); + LeaveCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming); + } + break; + case WXNRs::SessionStatus::Terminated: + { + DQRNetworkManager::LogComment(L"Session terminated"); + EnterCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming); + DQRNetworkManager::RTS_Message rtsMessage; + rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_STATUS_TERMINATED; + rtsMessage.m_sessionAddress = 0; + rtsMessage.m_dataSize = 0; + rtsMessage.m_pucData = 0; + m_pDQRNet->m_RTSMessageQueueIncoming.push(rtsMessage); + LeaveCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming); + } + break; + case WXNRs::SessionStatus::Activating: + DQRNetworkManager::LogComment(L"Session activating"); + break; + case WXNRs::SessionStatus::Terminating: + DQRNetworkManager::LogComment(L"Session terminating"); + break; + } + } +} + +// This event is called from the realtime session layer to notify any clients that a new endpoint has been connected into the network mesh. +void DQRNetworkManagerEventHandlers::AddedSessionAddressHandler(Platform::Object^ session, WXNRs::AddedSessionAddressEventArgs^ args) +{ + DQRNetworkManager::LogCommentFormat(L"AddedSessionAddressHandler session address 0x%x",args->SessionAddress); + EnterCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming); + DQRNetworkManager::RTS_Message rtsMessage; + rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_ADDED_SESSION_ADDRESS; + rtsMessage.m_sessionAddress = args->SessionAddress; + rtsMessage.m_dataSize = 0; + rtsMessage.m_pucData = 0; + m_pDQRNet->m_RTSMessageQueueIncoming.push(rtsMessage); + LeaveCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming); +} + +// This event is called from the realtime session layer to notify any clients that an endpoint has been removed from the network mesh. +void DQRNetworkManagerEventHandlers::RemovedSessionAddressHandler(Platform::Object^ session, WXNRs::RemovedSessionAddressEventArgs^ args) +{ + DQRNetworkManager::LogCommentFormat(L"RemovedSessionAddressHandler session address 0x%x", args->SessionAddress); + EnterCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming); + DQRNetworkManager::RTS_Message rtsMessage; + rtsMessage.m_eType = DQRNetworkManager::eRTSMessageType::RTS_MESSAGE_REMOVED_SESSION_ADDRESS; + rtsMessage.m_sessionAddress = args->SessionAddress; + rtsMessage.m_dataSize = 0; + rtsMessage.m_pucData = 0; + m_pDQRNet->m_RTSMessageQueueIncoming.push(rtsMessage); + LeaveCriticalSection(&m_pDQRNet->m_csRTSMessageQueueIncoming); +} + +// This event is called from the realtime session layer when session global data has been updated. We don't currently use global session data. +void DQRNetworkManagerEventHandlers::GlobalSessionDataChangedHandler(Platform::Object^ session, WXNRs::GlobalSessionDataChangedEventArgs^ args) +{ + DQRNetworkManager::LogComment(L"GlobalSessionDataChangedHandler"); +} + +void DQRNetworkManager::UpdateRTSStats() +{ + Platform::Array ^sessionAddresses = nullptr; + try + { + sessionAddresses = m_XRNS_Session->GetAllRemoteSessionAddresses(WXNRs::RemoteSessionAddressStateOptions::All, WXNRs::RemoteSessionAddressConnectivityOptions::All); + } + catch(Platform::COMException^ ex) + { + // swallow exceptions + } + catch(...) + { + // swallow exceptions + } + + if( sessionAddresses ) + { + unsigned int totalBytes = 0; + unsigned int totalSends = 0; + for( unsigned int i = 0; i < sessionAddresses->Length; i++ ) + { + try + { + totalBytes += m_XRNS_Session->GetSendChannelOutstandingBytes(sessionAddresses->get(i), WXNRs::ChannelId::DefaultGameSend ); + totalSends += m_XRNS_Session->GetSendChannelOutstandingSends(sessionAddresses->get(i), WXNRs::ChannelId::DefaultGameSend ); + } + catch(Platform::COMException^ ex) + { + // swallow exceptions + } + catch(...) + { + // swallow exceptions + } + } + m_RTS_Stat_totalBytes = totalBytes; + m_RTS_Stat_totalSends = totalSends; + } + else + { + m_RTS_Stat_totalBytes = 0; + m_RTS_Stat_totalSends = 0; + } +} + +void DQRNetworkManager::ProcessRTSMessagesIncoming() +{ + EnterCriticalSection(&m_csRTSMessageQueueIncoming); + while(m_RTSMessageQueueIncoming.size() > 0 ) + { + RTS_Message message = m_RTSMessageQueueIncoming.front(); + switch( message.m_eType ) + { + case eRTSMessageType::RTS_MESSAGE_DATA_RECEIVED: + Process_RTS_MESSAGE_DATA_RECEIVED(message); + break; + case eRTSMessageType::RTS_MESSAGE_DATA_RECEIVED_CHAT: + Process_RTS_MESSAGE_DATA_RECEIVED_CHAT(message); + break; + case eRTSMessageType::RTS_MESSAGE_ADDED_SESSION_ADDRESS: + Process_RTS_MESSAGE_ADDED_SESSION_ADDRESS(message); + break; + case eRTSMessageType::RTS_MESSAGE_REMOVED_SESSION_ADDRESS: + Process_RTS_MESSAGE_REMOVED_SESSION_ADDRESS(message); + break; + case eRTSMessageType::RTS_MESSAGE_STATUS_ACTIVE: + Process_RTS_MESSAGE_STATUS_ACTIVE(message); + break; + case eRTSMessageType::RTS_MESSAGE_STATUS_TERMINATED: + Process_RTS_MESSAGE_STATUS_TERMINATED(message); + break; + default: + break; + } + m_RTSMessageQueueIncoming.pop(); + } + LeaveCriticalSection(&m_csRTSMessageQueueIncoming); +}; + +void DQRNetworkManager::Process_RTS_MESSAGE_DATA_RECEIVED(RTS_Message &message) +{ + DQRConnectionInfo *connectionInfo; + if( m_isHosting ) + { + connectionInfo = m_sessionAddressToConnectionInfoMapHost[message.m_sessionAddress]; + } + else + { + connectionInfo = &m_connectionInfoClient; + } + + // Handle any header data, and actual data, in our stream. Data is as follows: + // Byte 0 Byte 1 + // fccsssss ssssssss + // + // Where: f is 0 if this is normal data send (to be passed up to the game), or is 1 if this is to be internally processed + // cc is the channel number that the data belongs to (0 to 3 representing actual player indices) + // sssssssssssss is the count of data bytes to follow (range 0 - 8191) + BYTE *pNextByte = message.m_pucData; + BYTE *pEndByte = pNextByte + message.m_dataSize; + do + { + BYTE byte = *pNextByte; + switch( connectionInfo->m_state ) + { + case DQRConnectionInfo::ConnectionState_HeaderByte0: + connectionInfo->m_currentChannel = ( byte >> 5 ) & 3; + connectionInfo->m_internalFlag = ( ( byte & 0x80 ) == 0x80 ); + + // Byte transfer mode. Bits 0-4 of this byte represent the upper 5 bits of our count of bytes to transfer... lower 8-bits will follow + connectionInfo->m_bytesRemaining = ((int)( byte & 0x1f )) << 8; + connectionInfo->m_state = DQRConnectionInfo::ConnectionState_HeaderByte1; + connectionInfo->m_internalDataState = DQRConnectionInfo::ConnectionState_InternalHeaderByte; + pNextByte++; + break; + case DQRConnectionInfo::ConnectionState_HeaderByte1: + // Add in the lower 8 bits of our byte count, the upper 5 were obtained from the first header byte. + connectionInfo->m_bytesRemaining |= byte; + + // If there isn't any data following, then just go back to the initial state expecting another header byte. + if( connectionInfo->m_bytesRemaining == 0 ) + { + connectionInfo->m_state = DQRConnectionInfo::ConnectionState_HeaderByte0; + } + else + { + connectionInfo->m_state = DQRConnectionInfo::ConnectionState_ReadBytes; + } + pNextByte++; + break; + case DQRConnectionInfo::ConnectionState_ReadBytes: + // At this stage we can send up to connectionInfo->m_bytesRemaining bytes, or the number of bytes that we have remaining in the data received, whichever is lowest. + int bytesInBuffer = (int)(pEndByte - pNextByte); + int bytesToReceive = ( ( connectionInfo->m_bytesRemaining < bytesInBuffer ) ? connectionInfo->m_bytesRemaining : bytesInBuffer ); + + if( connectionInfo->m_internalFlag ) + { + BytesReceivedInternal(connectionInfo, message.m_sessionAddress, pNextByte, bytesToReceive ); + } + else + { + BytesReceived(connectionInfo->m_smallId[connectionInfo->m_currentChannel], pNextByte, bytesToReceive ); + } + + // Adjust counts and pointers + pNextByte += bytesToReceive; + connectionInfo->m_bytesRemaining -= bytesToReceive; + + // Set state back to expect a header if there is no more data bytes to receive + if( connectionInfo->m_bytesRemaining == 0 ) + { + connectionInfo->m_state = DQRConnectionInfo::ConnectionState_HeaderByte0; + } + break; + } + } while (pNextByte != pEndByte); + + free(message.m_pucData); +} + +void DQRNetworkManager::Process_RTS_MESSAGE_DATA_RECEIVED_CHAT(RTS_Message &message) +{ + if( m_chat ) + { + m_chat->OnIncomingChatMessage(message.m_sessionAddress, Platform::ArrayReference(message.m_pucData, message.m_dataSize) ); + free(message.m_pucData); + } +} + +void DQRNetworkManager::Process_RTS_MESSAGE_ADDED_SESSION_ADDRESS(RTS_Message &message) +{ + if( m_chat ) + { + m_chat->OnNewSessionAddressAdded(message.m_sessionAddress); + } + + // New session address - add a mapping for it + if( m_isHosting ) + { + auto it = m_sessionAddressToConnectionInfoMapHost.find(message.m_sessionAddress); + DQRConnectionInfo *connectionInfo; + if( it == m_sessionAddressToConnectionInfoMapHost.end() ) + { + connectionInfo = new DQRConnectionInfo(); + + m_sessionAddressToConnectionInfoMapHost[message.m_sessionAddress] = connectionInfo; + } + else + { + // This shouldn't happen as we should be removing mappings as session addresses are removed. + connectionInfo = it->second; + connectionInfo->Reset(); + } + + } +} + +void DQRNetworkManager::Process_RTS_MESSAGE_REMOVED_SESSION_ADDRESS(RTS_Message &message) +{ + if( m_chat ) + { + m_chat->RemoveRemoteConsole(message.m_sessionAddress); + } + + if( m_isHosting ) + { + auto it = m_sessionAddressToConnectionInfoMapHost.find(message.m_sessionAddress); + + if( it != m_sessionAddressToConnectionInfoMapHost.end() ) + { + delete it->second; + m_sessionAddressToConnectionInfoMapHost.erase(it); + RemoveRoomSyncPlayersWithSessionAddress(message.m_sessionAddress); + SendRoomSyncInfo(); + } + } + else + { + // As the client, if we are disonnected from the host, then it is all over. Proceed as if leaving the room. + if( message.m_sessionAddress == m_hostSessionAddress ) + { + LeaveRoom(); + } + } +} + +void DQRNetworkManager::Process_RTS_MESSAGE_STATUS_ACTIVE(RTS_Message &message) +{ + // When we detect that the session has become active, we start sending unreliable packets, until we get some data back. This is because there is an issue with the + // realtime session layer where it is telling us that the connection is active a bit to early, and it will disconnect if it receives a packet that must be reliable in this + // state. + if( !m_isHosting ) + { + m_firstUnreliableSendTime = 0; + m_hostSessionAddress = m_XRNS_OldestAddress; + // Also initialise the status of this connection + m_connectionInfoClient.Reset(); + SetState(DQRNetworkManager::DNM_INT_STATE_JOINING_SENDING_UNRELIABLE); + } +} + +void DQRNetworkManager::Process_RTS_MESSAGE_STATUS_TERMINATED(RTS_Message &message) +{ + if( m_state == DQRNetworkManager::DNM_INT_STATE_JOINING_WAITING_FOR_ACTIVE_SESSION ) + { + m_joinCreateSessionAttempts++; + if( m_joinCreateSessionAttempts > DQRNetworkManager::JOIN_CREATE_SESSION_MAX_ATTEMPTS ) + { + SetState(DQRNetworkManager::DNM_INT_STATE_JOINING_FAILED); + } + else + { + SetState(DQRNetworkManager::DNM_INT_STATE_JOINING_GET_SDA); + } + } +} + +int DQRNetworkManager::_RTSDoWorkThread(void* lpParameter) +{ + DQRNetworkManager *pDQR = (DQRNetworkManager *)lpParameter; + return pDQR->RTSDoWorkThread(); +} + +static const DWORD XRNS_TERMINATE_LOCAL_SESSION_FLAG_IMMEDIATE = 0x00000001; +int DQRNetworkManager::RTSDoWorkThread() +{ + do + { + if( m_XRNS_Session ) + { + try + { + m_XRNS_Session->DoWork(20); + } + catch(Platform::COMException^ ex) + { + // swallow exceptions + } + catch(...) + { + // swallow exceptions + } + UpdateRTSStats(); + } + else + { + Sleep(20); + } + ProcessRTSMessagesOutgoing(); + } while(true); +} + +void DQRNetworkManager::ProcessRTSMessagesOutgoing() +{ + EnterCriticalSection(&m_csRTSMessageQueueOutgoing); + while(m_RTSMessageQueueOutgoing.size() > 0 ) + { + RTS_Message message = m_RTSMessageQueueOutgoing.front(); + switch( message.m_eType ) + { + case eRTSMessageType::RTS_MESSAGE_START_CLIENT: + Process_RTS_MESSAGE_START_CLIENT(message); + break; + case eRTSMessageType::RTS_MESSAGE_START_HOST: + Process_RTS_MESSAGE_START_HOST(message); + break; + case eRTSMessageType::RTS_MESSAGE_TERMINATE: + Process_RTS_MESSAGE_TERMINATE(message); + break; + case eRTSMessageType::RTS_MESSAGE_SEND_DATA: + Process_RTS_MESSAGE_SEND_DATA(message); + break; + default: + break; + } + m_RTSMessageQueueOutgoing.pop(); + } + LeaveCriticalSection(&m_csRTSMessageQueueOutgoing); +}; + +void DQRNetworkManager::Process_RTS_MESSAGE_START_CLIENT(RTS_Message &message) +{ + if( m_XRNS_Session ) + { + m_eventHandlers->Pulldown(m_XRNS_Session); + // Close XRNS session + try + { + m_XRNS_Session->TerminateLocalSession(XRNS_TERMINATE_LOCAL_SESSION_FLAG_IMMEDIATE); + } + catch(Platform::COMException^ ex) + { + // swallow exceptions + } + catch(...) + { + // swallow exceptions + } + + m_XRNS_Session = nullptr; + } + + m_XRNS_Session = ref new WXNRs::Session( m_localSocketAddress, m_remoteSocketAddress, MAX_PLAYERS_IN_TEMPLATE, 0); + m_XRNS_Session->MinSendRate = 512000; + + LogCommentFormat(L"connect retry period %d retries %d, data retry count %d, data retry timeout %d\n",m_XRNS_Session->ConnectRetryPeriod,m_XRNS_Session->MaxConnectRetries,m_XRNS_Session->MaxDataRetries,m_XRNS_Session->MinDataRetryTimeout); + + m_XRNS_Session->MaxConnectRetries = 50; // 50 at 100ms intervals = 5 seconds of attempting to connect + + m_eventHandlers->Setup(m_XRNS_Session); +} + +void DQRNetworkManager::Process_RTS_MESSAGE_START_HOST(RTS_Message &message) +{ + m_XRNS_Session = ref new WXNRs::Session( m_localSocketAddress, MAX_PLAYERS_IN_TEMPLATE, 0); + m_XRNS_Session->MinSendRate = 512000; + m_XRNS_Session->MaxConnectRetries = 50; // 50 at 100ms intervals = 5 seconds of attempting to connect + m_eventHandlers->Setup(m_XRNS_Session); +} + +void DQRNetworkManager::Process_RTS_MESSAGE_TERMINATE(RTS_Message &message) +{ + if( m_XRNS_Session ) + { + m_eventHandlers->Pulldown(m_XRNS_Session); + // Close XRNS session + try + { + m_XRNS_Session->TerminateLocalSession(XRNS_TERMINATE_LOCAL_SESSION_FLAG_IMMEDIATE); + } + catch(Platform::COMException^ ex) + { + // swallow exceptions + } + catch(...) + { + // swallow exceptions + } + + m_XRNS_Session = nullptr; + } +} + +void DQRNetworkManager::Process_RTS_MESSAGE_SEND_DATA(RTS_Message &message) +{ + if( m_XRNS_Session ) + { + unsigned int sessionAddress = message.m_sessionAddress; + + try + { + if( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_BROADCAST_MODE ) + { + sessionAddress = m_XRNS_Session->LocalSessionAddress; + } + + m_XRNS_Session->Send( ( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_GAME_CHANNEL ) ? WXNRs::ChannelId::DefaultGameSend : WXNRs::ChannelId::DefaultChatSend, + ( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_BROADCAST_MODE ) ? WXNRs::SendExceptionType::ExcludedAddresses : WXNRs::SendExceptionType::IncludedAddresses, + Platform::ArrayReference(&message.m_sessionAddress, 1), + Platform::ArrayReference(message.m_pucData, message.m_dataSize), + 0, + ( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_RELIABLE ) ? WXNRs::Send_Reliability::Reliable : WXNRs::Send_Reliability::NonReliable, + ( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_SEQUENTIAL ) ? WXNRs::Send_Sequence::Sequential : WXNRs::Send_Sequence::NonSequential, + WXNRs::Send_Ack::AckNormal, + ( message.m_flags & eRTSFlags::RTS_MESSAGE_FLAG_COALESCE ) ? WXNRs::Send_Coalesce::CoalesceDelay : WXNRs::Send_Coalesce::CoalesceNever, + WXNRs::Send_MiscState::NoMiscState ); + } + catch(Platform::COMException^ ex) + { + // swallow exceptions + } + catch(...) + { + // swallow exceptions + } + } + free(message.m_pucData); +} + +void DQRNetworkManager::RTS_StartCient() +{ + EnterCriticalSection(&m_csRTSMessageQueueOutgoing); + RTS_Message message; + message.m_eType = eRTSMessageType::RTS_MESSAGE_START_CLIENT; + message.m_pucData = NULL; + message.m_dataSize = 0; + m_RTSMessageQueueOutgoing.push(message); + LeaveCriticalSection(&m_csRTSMessageQueueOutgoing); +} + +void DQRNetworkManager::RTS_StartHost() +{ + EnterCriticalSection(&m_csRTSMessageQueueOutgoing); + RTS_Message message; + message.m_eType = eRTSMessageType::RTS_MESSAGE_START_HOST; + message.m_pucData = NULL; + message.m_dataSize = 0; + m_RTSMessageQueueOutgoing.push(message); + LeaveCriticalSection(&m_csRTSMessageQueueOutgoing); +} + +void DQRNetworkManager::RTS_Terminate() +{ + EnterCriticalSection(&m_csRTSMessageQueueOutgoing); + RTS_Message message; + message.m_eType = eRTSMessageType::RTS_MESSAGE_TERMINATE; + message.m_pucData = NULL; + message.m_dataSize = 0; + m_RTSMessageQueueOutgoing.push(message); + LeaveCriticalSection(&m_csRTSMessageQueueOutgoing); +} + +void DQRNetworkManager::RTS_SendData(unsigned char *pucData, unsigned int dataSize, unsigned int sessionAddress, bool reliable, bool sequential, bool coalesce, bool broadcastMode, bool gameChannel ) +{ + EnterCriticalSection(&m_csRTSMessageQueueOutgoing); + RTS_Message message; + message.m_eType = eRTSMessageType::RTS_MESSAGE_SEND_DATA; + message.m_pucData = (unsigned char *)malloc(dataSize); + memcpy(message.m_pucData, pucData, dataSize); + message.m_dataSize = dataSize; + message.m_sessionAddress = sessionAddress; + message.m_flags = 0; + if( reliable ) message.m_flags |= eRTSFlags::RTS_MESSAGE_FLAG_RELIABLE; + if( sequential ) message.m_flags |= eRTSFlags::RTS_MESSAGE_FLAG_SEQUENTIAL; + if( coalesce ) message.m_flags |= eRTSFlags::RTS_MESSAGE_FLAG_COALESCE; + if( broadcastMode ) message.m_flags |= eRTSFlags::RTS_MESSAGE_FLAG_BROADCAST_MODE; + if( gameChannel ) message.m_flags |= eRTSFlags::RTS_MESSAGE_FLAG_GAME_CHANNEL; + m_RTSMessageQueueOutgoing.push(message); + LeaveCriticalSection(&m_csRTSMessageQueueOutgoing); +} \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/DQRNetworkPlayer.cpp b/Minecraft.Client/Durango/Network/DQRNetworkPlayer.cpp new file mode 100644 index 00000000..e2d82a90 --- /dev/null +++ b/Minecraft.Client/Durango/Network/DQRNetworkPlayer.cpp @@ -0,0 +1,204 @@ +#include "stdafx.h" +#include "DQRNetworkPlayer.h" +#include "ChatIntegrationLayer.h" + +DQRNetworkPlayer::DQRNetworkPlayer() +{ +} + +DQRNetworkPlayer::DQRNetworkPlayer(DQRNetworkManager *manager, eDQRNetworkPlayerType playerType, bool onHost, int localPlayerIdx, unsigned int sessionAddress) +{ + m_localPlayerIdx = localPlayerIdx; + m_type = playerType; + m_host = onHost; + m_manager = manager; + m_customData = 0; + m_sessionAddress = sessionAddress; +} + +DQRNetworkPlayer::~DQRNetworkPlayer() +{ +} + +PlayerUID DQRNetworkPlayer::GetUID() +{ + return m_UID; +} + +void DQRNetworkPlayer::SetUID(PlayerUID UID) +{ + m_UID = UID; +} + +int DQRNetworkPlayer::GetLocalPlayerIndex() +{ + return m_localPlayerIdx; +} + +uintptr_t DQRNetworkPlayer::GetCustomDataValue() +{ + return m_customData; +} + +void DQRNetworkPlayer::SetCustomDataValue(uintptr_t data) +{ + m_customData = data; +} + +bool DQRNetworkPlayer::IsRemote() +{ + return !IsLocal(); +} + +bool DQRNetworkPlayer::IsHost() +{ + return (m_type == DNP_TYPE_HOST); +} + +bool DQRNetworkPlayer::IsLocal() +{ + // m_host determines whether this *machine* is hosting the game, not this player (which is determined by m_type) + if( m_host ) + { + // If we are the hosting machine, then both the host & local players are local to this machine + return (m_type == DNP_TYPE_HOST) || (m_type == DNP_TYPE_LOCAL); + } + else + { + // Not hosting, just local players are actually physically local + return (m_type == DNP_TYPE_LOCAL) ; + } +} + +bool DQRNetworkPlayer::IsSameSystem(DQRNetworkPlayer *other) +{ + return ( m_sessionAddress == other->m_sessionAddress ); +} + +bool DQRNetworkPlayer::IsTalking() +{ + if(m_manager->m_chat == nullptr) return false; + Microsoft::Xbox::GameChat::ChatUser^ chatUser = m_manager->m_chat->GetChatUserByXboxUserId(ref new Platform::String(m_UID.toString().c_str())); + + if( chatUser == nullptr ) return false; + if( chatUser->TalkingMode == Microsoft::Xbox::GameChat::ChatUserTalkingMode::NotTalking ) + { + return false; + } + else + { + return true; + } +} + +bool DQRNetworkPlayer::HasVoice() +{ + if(m_manager->m_chat == nullptr) return false; + Microsoft::Xbox::GameChat::ChatUser^ chatUser = m_manager->m_chat->GetChatUserByXboxUserId(ref new Platform::String(m_UID.toString().c_str())); + + if( chatUser == nullptr ) return false; + if( ((int)chatUser->ParticipantType) & ((int)Windows::Xbox::Chat::ChatParticipantTypes::Talker) ) + { + return true; + } + else + { + return false; + } +} + +bool DQRNetworkPlayer::HasCamera() +{ + return false; +} + +LPCWSTR DQRNetworkPlayer::GetGamertag() +{ + return m_name; +} + +int DQRNetworkPlayer::GetSmallId() +{ + return (int)m_smallId; +} + +void DQRNetworkPlayer::SetSmallId(unsigned char smallId) +{ + m_smallId = smallId; +} + +int DQRNetworkPlayer::GetSessionIndex() +{ + return m_manager->GetSessionIndex(this); +} + +// Attempt to send data, of any size, from this player to that specified by pPlayerTarget. This may not be possible depending on the two players, due to +// our star shaped network connectivity. Data may be any size, and is copied so on returning from this method it does not need to be preserved. +void DQRNetworkPlayer::SendData( DQRNetworkPlayer *pPlayerTarget, const void *data, unsigned int dataSize ) +{ + // Our network is connected as a star. If we are the host, then we can send to any remote player. If we're a client, we can send only to the host. + // The host can also send to other local players, but this doesn't need to go through Rudp. + if( m_host ) + { + if( ( m_type == DNP_TYPE_HOST ) && ( pPlayerTarget->m_type == DNP_TYPE_REMOTE ) ) + { + // Rudp communication from host to remote player - handled by remote player instance + pPlayerTarget->SendInternal(data,dataSize); + } + else + { + // Can't do any other types of communications + assert(false); + } + } + else + { + if( ( m_type == DNP_TYPE_LOCAL ) && ( pPlayerTarget->m_type == DNP_TYPE_HOST ) ) + { + // Rudp communication from client to host - handled by this player instace + SendInternal(data, dataSize); + } + else + { + // Can't do any other types of communications + assert(false); + } + } +} + +void DQRNetworkPlayer::SendInternal(const void *data, unsigned int dataSize) +{ + m_manager->SendBytes(m_smallId, (BYTE *)data, dataSize); +} + +int DQRNetworkPlayer::GetSendQueueSizeBytes() +{ + return m_manager->GetQueueSizeBytes(); +} + +int DQRNetworkPlayer::GetSendQueueSizeMessages() +{ + return m_manager->GetQueueSizeMessages(); +} + +wchar_t *DQRNetworkPlayer::GetName() +{ + return m_name; +} + +void DQRNetworkPlayer::SetName(const wchar_t *name) +{ + wcscpy_s(m_name, name); +} + +// Return display name (if display name is not set, return name instead) +wstring DQRNetworkPlayer::GetDisplayName() +{ + return (m_displayName == L"") ? m_name : m_displayName; +} + +// Set display name +void DQRNetworkPlayer::SetDisplayName(wstring displayName) +{ + m_displayName = displayName; +} \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/DQRNetworkPlayer.h b/Minecraft.Client/Durango/Network/DQRNetworkPlayer.h new file mode 100644 index 00000000..fd140722 --- /dev/null +++ b/Minecraft.Client/Durango/Network/DQRNetworkPlayer.h @@ -0,0 +1,65 @@ +#pragma once +#include "DQRNetworkManager.h" +#include + +// This is the lowest level class for handling the concept of a player on Durango. This is managed by DQRNetworkManager. The game shouldn't directly communicate +// with this class, as it is wrapped by NetworkPlayerDurango which is an implementation of a platform-independent interface INetworkPlayer. + +class DQRNetworkPlayer +{ +public: + friend class DQRNetworkManager; + + typedef enum + { + DNP_TYPE_HOST, // This player represents the host + DNP_TYPE_LOCAL, // On host - this player is a local player that needs communicated with specially not using rudp. On clients - this is a local player, where network communications can be used to communicate with the host + DNP_TYPE_REMOTE, // On host - this player can be used to communicate from between the host and this player. On clients - this is a remote player that cannot be communicated with + } eDQRNetworkPlayerType; + + DQRNetworkPlayer(); + DQRNetworkPlayer(DQRNetworkManager *manager, eDQRNetworkPlayerType playerType, bool onHost, int localPlayerIdx, unsigned int sessionAddress); + ~DQRNetworkPlayer(); + + PlayerUID GetUID(); + void SetUID(PlayerUID UID); + int GetLocalPlayerIndex(); + uintptr_t GetCustomDataValue(); + void SetCustomDataValue(uintptr_t data); + bool IsRemote(); + bool IsHost(); + bool IsLocal(); + bool IsSameSystem(DQRNetworkPlayer *other); + bool HasVoice(); + bool IsTalking(); + bool HasCamera(); + LPCWSTR GetGamertag(); + int GetSmallId(); + void SetSmallId(unsigned char smallId); + int GetSessionIndex(); + void SendData( DQRNetworkPlayer *pPlayerTarget, const void *data, unsigned int dataSize ); + + int GetSendQueueSizeBytes(); + int GetSendQueueSizeMessages(); + + wchar_t *GetName(); + void SetName(const wchar_t *name); + + std::wstring GetDisplayName(); + void SetDisplayName(std::wstring displayName); +private: + void SendInternal(const void *data, unsigned int dataSize); + + eDQRNetworkPlayerType m_type; // The player type + bool m_host; // Whether this actual player class is stored on a host (not whether it represents the host, or a player on the host machine) + int m_localPlayerIdx; // Index of this player on the machine to which it belongs + DQRNetworkManager *m_manager; // Pointer back to the manager that is managing this player + + PlayerUID m_UID; + uintptr_t m_customData; + unsigned char m_smallId; + unsigned int m_sessionAddress; + + wchar_t m_name[21]; + std::wstring m_displayName; +}; \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/NetworkPlayerDurango.cpp b/Minecraft.Client/Durango/Network/NetworkPlayerDurango.cpp new file mode 100644 index 00000000..fd2181df --- /dev/null +++ b/Minecraft.Client/Durango/Network/NetworkPlayerDurango.cpp @@ -0,0 +1,113 @@ +#include "stdafx.h" +#include "NetworkPlayerDurango.h" + +NetworkPlayerDurango::NetworkPlayerDurango(DQRNetworkPlayer *qnetPlayer) +{ + m_dqrPlayer = qnetPlayer; + m_pSocket = NULL; +} + +unsigned char NetworkPlayerDurango::GetSmallId() +{ + return m_dqrPlayer->GetSmallId(); +} + +void NetworkPlayerDurango::SendData(INetworkPlayer *player, const void *pvData, int dataSize, bool lowPriority) +{ + m_dqrPlayer->SendData( ((NetworkPlayerDurango *)player)->m_dqrPlayer, pvData, dataSize ); +} + +bool NetworkPlayerDurango::IsSameSystem(INetworkPlayer *player) +{ + return m_dqrPlayer->IsSameSystem(((NetworkPlayerDurango *)player)->m_dqrPlayer); +} + +int NetworkPlayerDurango::GetSendQueueSizeBytes( INetworkPlayer *player, bool lowPriority ) +{ + return m_dqrPlayer->GetSendQueueSizeBytes(); +} + +int NetworkPlayerDurango::GetSendQueueSizeMessages( INetworkPlayer *player, bool lowPriority ) +{ + return m_dqrPlayer->GetSendQueueSizeMessages(); +} + +int NetworkPlayerDurango::GetCurrentRtt() +{ + return 0; // TODO +} + +bool NetworkPlayerDurango::IsHost() +{ + return m_dqrPlayer->IsHost(); +} + +bool NetworkPlayerDurango::IsGuest() +{ + return false; // TODO +} + +bool NetworkPlayerDurango::IsLocal() +{ + return m_dqrPlayer->IsLocal(); +} + +int NetworkPlayerDurango::GetSessionIndex() +{ + return m_dqrPlayer->GetSessionIndex(); +} + +bool NetworkPlayerDurango::IsTalking() +{ + return m_dqrPlayer->IsTalking(); +} + +bool NetworkPlayerDurango::IsMutedByLocalUser(int userIndex) +{ + return false; +} + +bool NetworkPlayerDurango::HasVoice() +{ + return m_dqrPlayer->HasVoice(); +} + +bool NetworkPlayerDurango::HasCamera() +{ + return false; // TODO +} + +int NetworkPlayerDurango::GetUserIndex() +{ + return m_dqrPlayer->GetLocalPlayerIndex(); +} + +void NetworkPlayerDurango::SetSocket(Socket *pSocket) +{ + m_pSocket = pSocket; +} + +Socket *NetworkPlayerDurango::GetSocket() +{ + return m_pSocket; +} + +const wchar_t *NetworkPlayerDurango::GetOnlineName() +{ + return m_dqrPlayer->GetName(); +} + +wstring NetworkPlayerDurango::GetDisplayName() +{ + return m_dqrPlayer->GetDisplayName(); +} + +PlayerUID NetworkPlayerDurango::GetUID() +{ + return m_dqrPlayer->GetUID(); +} + +void NetworkPlayerDurango::SetUID(PlayerUID UID) +{ + m_dqrPlayer->SetUID(UID); +} \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/NetworkPlayerDurango.h b/Minecraft.Client/Durango/Network/NetworkPlayerDurango.h new file mode 100644 index 00000000..c95e8aef --- /dev/null +++ b/Minecraft.Client/Durango/Network/NetworkPlayerDurango.h @@ -0,0 +1,39 @@ +#pragma once + +#include "..\..\Common\Network\NetworkPlayerInterface.h" +#include "DQRNetworkPlayer.h" + +// This is an implementation of the INetworkPlayer interface, for Durango. It effectively wraps the DQRNetworkPlayer class in a non-platform-specific way. + +class NetworkPlayerDurango : public INetworkPlayer +{ +public: + // Common player interface + NetworkPlayerDurango(DQRNetworkPlayer *sqrPlayer); + virtual unsigned char GetSmallId(); + virtual void SendData(INetworkPlayer *player, const void *pvData, int dataSize, bool lowPriority); + virtual bool IsSameSystem(INetworkPlayer *player); + virtual int GetSendQueueSizeBytes( INetworkPlayer *player, bool lowPriority ); + virtual int GetSendQueueSizeMessages( INetworkPlayer *player, bool lowPriority ); + virtual int GetCurrentRtt(); + virtual bool IsHost(); + virtual bool IsGuest(); + virtual bool IsLocal(); + virtual int GetSessionIndex(); + virtual bool IsTalking(); + virtual bool IsMutedByLocalUser(int userIndex); + virtual bool HasVoice(); + virtual bool HasCamera(); + virtual int GetUserIndex(); + virtual void SetSocket(Socket *pSocket); + virtual Socket *GetSocket(); + virtual const wchar_t *GetOnlineName(); + virtual wstring GetDisplayName(); + virtual PlayerUID GetUID(); + + void SetUID(PlayerUID UID); + +private: + DQRNetworkPlayer *m_dqrPlayer; + Socket *m_pSocket; +}; \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/PartyController.cpp b/Minecraft.Client/Durango/Network/PartyController.cpp new file mode 100644 index 00000000..753a71ac --- /dev/null +++ b/Minecraft.Client/Durango/Network/PartyController.cpp @@ -0,0 +1,1205 @@ +//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +//// PARTICULAR PURPOSE. +//// +//// Copyright (c) Microsoft Corporation. All rights reserved +#include "stdafx.h" +#include "PartyController.h" +#include "DQRNetworkManager.h" +#include + +/* +#include "time.h" +#include "../GameLogic/Game.h" +#include "../Utils/LogController.h" +#include "../Utils/Utils.h" +#include "UserController.h" +#include "SessionController.h" +*/ + +using namespace Concurrency; +using namespace Microsoft::Xbox::Services::Multiplayer; +using namespace Microsoft::Xbox::Services::Matchmaking; +using namespace Microsoft::Xbox::Services; +using namespace Windows::Foundation::Collections; +using namespace Windows::Foundation; +using namespace Windows::Storage::Streams; +using namespace Windows::Storage; +using namespace Windows::System; +using namespace Windows::UI::Core; +using namespace Windows::Xbox::Multiplayer; +using namespace Windows::Xbox::Networking; +using namespace Windows::Xbox::System; + + +PartyController::PartyController(DQRNetworkManager *pDQRNet) : + m_isGameSessionReadyEventTriggered(false), + m_isGamePlayerEventRegistered(false), + m_pDQRNet(pDQRNet) +{ +} + +void PartyController::DebugPrintPartyView( Windows::Xbox::Multiplayer::PartyView^ partyView ) +{ + DQRNetworkManager::LogComment( L"PartyView:" ); + if( partyView == nullptr ) + { + DQRNetworkManager::LogComment( L" No party view" ); + return; + } + + DQRNetworkManager::LogComment( L" GameSession::SessionName: " + (partyView->GameSession ? partyView->GameSession->SessionName : L"NONE") ); + DQRNetworkManager::LogComment( L" GameSession::SessionTemplateName " + (partyView->GameSession ? partyView->GameSession->SessionTemplateName : L"NONE") ); + DQRNetworkManager::LogComment( L" GameSession::ServiceConfigurationId " + (partyView->GameSession ? partyView->GameSession->ServiceConfigurationId : L"NONE") ); + DQRNetworkManager::LogComment( L" MatchSession: " + (partyView->MatchSession ? partyView->MatchSession->SessionName : L"NONE") ); + DQRNetworkManager::LogComment( L" IsPartyInAnotherTitle: " + partyView->IsPartyInAnotherTitle.ToString() ); + + DQRNetworkManager::LogComment( L" Members: " + partyView->Members->Size.ToString() ); + for( PartyMember^ member : partyView->Members ) + { + DQRNetworkManager::LogComment( L" Member:" ); + DQRNetworkManager::LogComment( L" XboxUserID: " + member->XboxUserId ); + DQRNetworkManager::LogCommentFormat( L" IsLocalUser: %s", member->IsLocal ? L"TRUE" : L"FALSE" ); +// DQRNetworkManager::LogComment( L" JoinTime: " + Utils::DateTimeToString(member->JoinTime) ); + } + + DQRNetworkManager::LogComment( L" ReservedMembers: " + partyView->ReservedMembers->Size.ToString() ); + for( Platform::String^ memberXuid : partyView->ReservedMembers ) + { + DQRNetworkManager::LogComment( L" ReservedMember:" ); + DQRNetworkManager::LogComment( L" XboxUserID " + memberXuid ); + } + + DQRNetworkManager::LogComment( L" MembersGroupedByDevice: " + partyView->MembersGroupedByDevice->Size.ToString() ); + for( PartyMemberDeviceGroup^ partyMemberDeviceGroup : partyView->MembersGroupedByDevice ) + { + DQRNetworkManager::LogComment( L" Device:" ); + for( PartyMember^ member : partyMemberDeviceGroup->Members ) + { + DQRNetworkManager::LogComment( L" Member:" ); + DQRNetworkManager::LogComment( L" XboxUserID: " + member->XboxUserId ); + DQRNetworkManager::LogCommentFormat( L" IsLocalUser: %s", member->IsLocal ? L"TRUE" : L"FALSE" ); +// DQRNetworkManager::LogComment( L" JoinTime: " + Utils::DateTimeToString(member->JoinTime) ); + } + } +} + +void PartyController::RefreshPartyView() +{ +// LogComment( L"RefreshPartyView" ); + + PartyView^ partyView; + try + { + IAsyncOperation^ partyOperation = Party::GetPartyViewAsync(); + create_task(partyOperation) + .then([this, &partyView] (task t) + { + try + { + Concurrency::critical_section::scoped_lock lock(m_lock); + partyView = t.get(); + } + catch ( Platform::Exception^ ex ) + { + if( ex->HResult != (int)Windows::Xbox::Multiplayer::PartyErrorStatus::EmptyParty ) + { +// LogCommentWithError( L"GetPartyView failed", ex->HResult ); + } + + } + }).wait(); + } + catch ( Platform::Exception^ ex ) + { + partyView = nullptr; +// LogCommentWithError( L"GetPartyView failed", ex->HResult ); + } + + DebugPrintPartyView(partyView); + + SetPartyView(partyView); +} + +// Add any players specified in userMask to the party. This method returns a bool that is true if the a player was added, which wasn't already +// in the game session associated with the party. This is used to determine whether we should be waiting for a slot to be added for it by the host. +bool PartyController::AddLocalUsersToParty(int userMask, Windows::Xbox::System::User^ primaryUser) +{ + bool addedNewPlayerToPartyThatIsntInSession = false; + + PartyView^ partyView = GetPartyView(); + + // If there's already a party, then attempt to get a session document as we'll be needing this later + MultiplayerSession^ session; + if( partyView ) + { + MXS::XboxLiveContext^ xblContext = ref new MXS::XboxLiveContext(primaryUser); + if( xblContext ) + { + // Get a copy of the session document, for this user + auto multiplayerSessionAsync = xblContext->MultiplayerService->GetCurrentSessionAsync( m_pDQRNet->ConvertToMicrosoftXboxServicesMultiplayerSessionReference(partyView->GameSession)); + create_task(multiplayerSessionAsync).then([&session,this](task t) + { + try + { + session = t.get(); // if t.get() didn't throw, it succeeded + } + catch (Platform::COMException^ ex) + { + DQRNetworkManager::LogCommentWithError( L"GetCurrentSessionAsync failed", ex->HResult ); + } + }) + .wait(); + } + } + + // Adds all local players that are specified in userMask to the party belonging to the primary player + IVector^ localUsersToAddVector = ref new Platform::Collections::Vector; + for( int i = 0; i < 4; i++ ) + { + if( userMask & ( 1 << i ) ) + { + bool alreadyHere = false; + if( partyView ) + { + Windows::Xbox::System::User^ userToAdd = ProfileManager.GetUser(i, true); + for( int j = 0; j < partyView->Members->Size; j++ ) + { + if( partyView->Members->GetAt(j)->XboxUserId == userToAdd->XboxUserId ) + { + primaryUser = userToAdd; // If there is already a party, then the acting user passed to AddLocalUsersAsync must be a user that is in that party, so make sure this is the case. Doesn't matter Which user. + alreadyHere = true; + break; + } + } + } + + if( !alreadyHere ) + { + localUsersToAddVector->Append(ProfileManager.GetUser(i)); + bool alreadyInSession = false; + if( session ) + { + if( m_pDQRNet->IsPlayerInSession( ProfileManager.GetUser(i, true)->XboxUserId, session, NULL ) ) + { + alreadyInSession = true; + } + } + if( !alreadyInSession ) + { + addedNewPlayerToPartyThatIsntInSession = true; + } + } + } + } + + IVectorView^ localUsersToAddVecView = localUsersToAddVector->GetView(); + + try + { + IAsyncAction^ addMemberOperation = Party::AddLocalUsersAsync( primaryUser, localUsersToAddVecView ); + + create_task(addMemberOperation) + .then([this] (task t) + { + try + { + t.get(); + RefreshPartyView(); + } + catch (Platform::COMException^ ex) + { + DQRNetworkManager::LogCommentWithError( L"AddLocalUsersAsync failed", ex->HResult ); + } + catch (Platform::OperationCanceledException^ ex) + { + DQRNetworkManager::LogCommentWithError( L"AddLocalUsersAsync failed - operation cancelled", ex->HResult ); + } + }).wait(); + } + catch (Platform::COMException^ ex) + { + DQRNetworkManager::LogCommentWithError( L"AddLocalUsersAsync failed to create", ex->HResult ); + } + + return addedNewPlayerToPartyThatIsntInSession; +} + +void PartyController::CheckPartySessionFull(Windows::Xbox::System::User^ primaryUser) +{ + RefreshPartyView(); + + PartyView^ partyView = GetPartyView(); + + // If there's already a party, then attempt to get a session document + MultiplayerSession^ session; + if( partyView && partyView->GameSession) + { + MXS::XboxLiveContext^ xblContext = ref new MXS::XboxLiveContext(primaryUser); + if( xblContext ) + { + // Get a copy of the session document, for this user + auto multiplayerSessionAsync = xblContext->MultiplayerService->GetCurrentSessionAsync( m_pDQRNet->ConvertToMicrosoftXboxServicesMultiplayerSessionReference(partyView->GameSession)); + create_task(multiplayerSessionAsync).then([&session,this](task t) + { + try + { + session = t.get(); // if t.get() didn't throw, it succeeded + } + catch (Platform::COMException^ ex) + { + DQRNetworkManager::LogCommentWithError( L"GetCurrentSessionAsync failed", ex->HResult ); + } + }) + .wait(); + } + if( session ) + { + int nonLocalPlayerCount = 0; + for( int i = 0; i < session->Members->Size; i++ ) + { + MXSM::MultiplayerSessionMember^ member = session->Members->GetAt(i); + + bool isLocalPlayer = false; + for( int j = 4; j < 12; j++ ) + { + WXS::User ^user = InputManager.GetUserForGamepad(j); + if( user != nullptr ) + { + if( user->XboxUserId == member->XboxUserId ) + { + isLocalPlayer = true; + } + } + } + if( !isLocalPlayer ) + { + nonLocalPlayerCount++; + } + } + app.DebugPrintf(">>>>> Invited to a game with a non-local player count of %d\n", nonLocalPlayerCount); + + if( nonLocalPlayerCount >= DQRNetworkManager::MAX_ONLINE_PLAYER_COUNT ) + { + m_pDQRNet->FlagInvitedToFullSession(); + } + } + } +} + +void PartyController::SetJoinability(bool isJoinable) +{ + Party::Joinability = isJoinable ? WXM::SessionJoinability::JoinableByFriends : WXM::SessionJoinability::InviteOnly; +} + +void PartyController::DisassociateSessionFromParty( Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ sessionReference) +{ + RefreshPartyView(); + + PartyView^ partyView = GetPartyView(); + + if( partyView ) + { + // We need a user to be the acting user, when disassociating the party. Attempt to find a party member who is local. + for( int j = 0; j < partyView->Members->Size; j++ ) + { + Windows::Xbox::Multiplayer::PartyMember^ partyMember = partyView->Members->GetAt(j); + if( partyMember->IsLocal ) + { + IVectorView^ localUsers = Windows::Xbox::System::User::Users; + + for( int i = 0; i < localUsers->Size; i++ ) + { + Windows::Xbox::System::User^ localUser = localUsers->GetAt(i); + if( localUser->XboxUserId == partyMember->XboxUserId ) + { + // Convert the session reference (in Microsoft::Xbox::Services::Multiplayer namespace) to Windows::Xbox::Multiplayer names space so we can use in the party system + WXM::MultiplayerSessionReference^ winSessionRef = m_pDQRNet->ConvertToWindowsXboxMultiplayerSessionReference(sessionReference); + + auto disassociateSessionAsync = WXM::Party::DisassociateGameSessionAsync(localUser, winSessionRef); + create_task(disassociateSessionAsync).then([this](task t) + { + try + { + t.get(); // if t.get() didn't throw, it succeeded + } + catch (Platform::COMException^ ex) + { + m_pDQRNet->LogCommentWithError( L"DisassociateGameSessionAsync failed", ex->HResult ); + } + }); + //.wait(); // There are situations in which this never completes (?!) so waiting isn't a good idea + return; + } + } + } + } + } + + +} + +void PartyController::RemoveLocalUsersFromParty(Windows::Xbox::System::User^ primaryUser) +{ + PartyView^ partyView = GetPartyView(); + + if( partyView == nullptr ) return; + + // Attempt to remove all local users that are currently in the party + IVectorView^ localUsers = Windows::Xbox::System::User::Users; + IVector^ localUsersToRemoveVector = ref new Platform::Collections::Vector; + for( int i = 0; i < localUsers->Size; i++ ) + { + Windows::Xbox::System::User^ userToRemove = localUsers->GetAt(i); + for( int j = 0; j < partyView->Members->Size; j++ ) + { + if( partyView->Members->GetAt(j)->XboxUserId == userToRemove->XboxUserId ) + { + localUsersToRemoveVector->Append(userToRemove); + break; + } + } + } + + IVectorView^ localUsersToRemoveVecView = localUsersToRemoveVector->GetView(); + + IAsyncAction^ removeMemberOperation = Party::RemoveLocalUsersAsync( localUsersToRemoveVecView ); + + create_task(removeMemberOperation) + .then([this] (task t) + { + try + { + t.get(); + RefreshPartyView(); + } + catch (Platform::COMException^ ex) + { + DQRNetworkManager::LogCommentWithError( L"RemoveLocalUsersAsync failed", ex->HResult ); + } + }).wait(); +} + +void PartyController::RemoveLocalUsersFromParty(Windows::Xbox::System::User^ primaryUser, int playerMask, Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ sessionReference) +{ + PartyView^ partyView = GetPartyView(); + + if( partyView == nullptr ) return; + + // Don't leave the party if it isn't actually the party belonging to the session we are in - this can happen when switching from one game session to another, after accepting an invite to a game whilst already playing + if( sessionReference != nullptr ) + { + if( partyView->GameSession != nullptr ) + { + if( partyView->GameSession->SessionName != sessionReference->SessionName ) return; + } + } + + // Attempt to remove all specified local users that are currently in the party. Check that each player is actually in + // the party, as we'll get an exception for trying to remove players that aren't + + IVector^ localUsersToRemoveVector = ref new Platform::Collections::Vector; + + for( int i = 0; i < MAX_LOCAL_PLAYERS; i++ ) + { + if( playerMask & ( 1 << i ) ) + { + Windows::Xbox::System::User^ userToRemove = ProfileManager.GetUser(i, true); + if(userToRemove!=nullptr) + { + for( int j = 0; j < partyView->Members->Size; j++ ) + { + if( partyView->Members->GetAt(j)->XboxUserId == userToRemove->XboxUserId ) + { + localUsersToRemoveVector->Append(userToRemove); + break; + } + } + } + } + } + + IVectorView^ localUsersToRemoveVecView = localUsersToRemoveVector->GetView(); + + IAsyncAction^ removeMemberOperation = Party::RemoveLocalUsersAsync( localUsersToRemoveVecView ); + + create_task(removeMemberOperation) + .then([this] (task t) + { + try + { + t.get(); + RefreshPartyView(); + } + catch (Platform::COMException^ ex) + { + DQRNetworkManager::LogCommentWithError( L"RemoveLocalUsersAsync failed", ex->HResult ); + } + }).wait(); +} + +void PartyController::RemoveLocalUserFromParty(Windows::Xbox::System::User^ userToRemove) +{ + // Attempt to a specific local user from the party + + RefreshPartyView(); + + // Don't need to do anything if there isn't currently a party + PartyView^ partyView = GetPartyView(); + + if( partyView == nullptr ) return; + + // Don't need to do anything if this player isn't in the current party - the remove will raise an exception + bool inParty = false; + for( int i = 0; i < partyView->Members->Size; i++ ) + { + if( partyView->Members->GetAt(i)->XboxUserId == userToRemove->XboxUserId ) + { + inParty = true; + break; + } + } + if( !inParty ) return; + + IVector^ localUsersToRemoveVector = ref new Platform::Collections::Vector; + localUsersToRemoveVector->Append(userToRemove); + IVectorView^ localUsersToRemoveVecView = localUsersToRemoveVector->GetView(); + + IAsyncAction^ removeMemberOperation = Party::RemoveLocalUsersAsync( localUsersToRemoveVecView ); + + create_task(removeMemberOperation) + .then([this] (task t) + { + try + { + t.get(); + RefreshPartyView(); + } + catch (Platform::COMException^ ex) + { + DQRNetworkManager::LogCommentWithError( L"RemoveLocalUsersAsync failed", ex->HResult ); + } + }).wait(); +} + +void PartyController::RegisterGamePlayersChangedEventHandler() +{ + // Listen to Party::GamePlayersChanged + // Only the HOST should register for this event to detect if any party members were added that should be pulled into the game. + EventHandler^ partyGamePlayersChanged = ref new EventHandler( + [this] (Platform::Object^, GamePlayersChangedEventArgs^ eventArgs) + { + OnGamePlayersChanged( eventArgs ); + }); + m_partyGamePlayersChangedToken = Party::GamePlayersChanged += partyGamePlayersChanged; + m_isGamePlayerEventRegistered = true; +} + +void PartyController::RegisterEventHandlers() +{ + // Listen to Party Roster Changed + EventHandler^ partyRosterChangedEvent = ref new EventHandler( + [this] (Platform::Object^, PartyRosterChangedEventArgs^ eventArgs) + { + OnPartyRosterChanged( eventArgs ); + }); + m_partyRosterChangedToken = Party::PartyRosterChanged += partyRosterChangedEvent; + + // Listen to Party State Changed + EventHandler^ partyStateChangedEvent = ref new EventHandler( + [this] (Platform::Object^, PartyStateChangedEventArgs^ eventArgs) + { + OnPartyStateChanged( eventArgs ); + }); + m_partyStateChangedToken = Party::PartyStateChanged += partyStateChangedEvent; + // Listen to Game Session Ready + EventHandler^ gameSessionReadyEvent = ref new EventHandler( + [this] (Platform::Object^, GameSessionReadyEventArgs^ eventArgs) + { + OnGameSessionReady(eventArgs); + }); + m_partyGameSessionReadyToken = Party::GameSessionReady += gameSessionReadyEvent; +} + +void PartyController::UnregisterEventHandlers() +{ + Party::PartyRosterChanged -= m_partyRosterChangedToken; + Party::PartyStateChanged -= m_partyStateChangedToken; + Party::GameSessionReady -= m_partyGameSessionReadyToken; +} + +void PartyController::UnregisterGamePlayersEventHandler() +{ + if(m_isGamePlayerEventRegistered) + { + Party::GamePlayersChanged -= m_partyGamePlayersChangedToken; + } +} + +// This event will fire when : +// - A new Match Session registered to party, or +// - A new Game Session is registered to party (via RegisterGame call, or as a result of matchmaking), and +// - Party Title ID changes (which will trigger change in IsPartyInAnotherTItle bool flag). +void PartyController::OnPartyStateChanged( PartyStateChangedEventArgs^ eventArgs ) +{ + DQRNetworkManager::LogComment( L"OnPartyStateChanged"); + RefreshPartyView(); +} + +void PartyController::OnPartyRosterChanged( PartyRosterChangedEventArgs^ eventArgs ) +{ + if( m_pDQRNet->m_multiplayerSession == nullptr) return; + + RefreshPartyView(); + + XboxLiveContext^ xboxLiveContext = m_pDQRNet->m_primaryUserXboxLiveContext; + if( xboxLiveContext == nullptr ) return; + + DQRNetworkManager::LogComment( L"OnPartyRosterChanged"); + + if( eventArgs->RemovedMembers->Size ) + { + DQRNetworkManager::LogComment( L"Removed Members:"); + } + + // First, establish whether an active player local to this machine have been removed + bool activePlayerRemoved = false; + int playerLeavingMask = 0; + + // If there's no party anymore, then we're all leaving + if( m_partyView == nullptr ) + { + playerLeavingMask = m_pDQRNet->m_currentUserMask; + } + else + { + // Still a party, find out who left + for( int i = 0; i < eventArgs->RemovedMembers->Size; i++ ) + { + DQRNetworkPlayer *player = m_pDQRNet->GetPlayerByXuid(PlayerUID(eventArgs->RemovedMembers->GetAt(i)->Data())); + if( player ) + { + if( player->IsLocal() ) + { + playerLeavingMask |= ( 1 << player->GetLocalPlayerIndex() ); + } + } + DQRNetworkManager::LogComment(eventArgs->RemovedMembers->GetAt(i)); + } + } + + // If a local player is leaving the party, we want to handle it generally as if they had selected to exit from within the game, assuming that they have just deliberatly removed themselves from + // the party via the system interface. However... we may be being removed from this party because we have just accepted a request to join Another party via a "game session ready" sort of prompt. + // I don't think there's any way to distinguish these two things happening at this stage, so at this point we will signal to the DQR layer what has just happened, + // and it will only do something about it after some period of time has passed without a new game party becoming ready for the player to join + if( playerLeavingMask ) + { + m_pDQRNet->HandlePlayerRemovedFromParty(playerLeavingMask); + } +} + +#define LAST_INVITED_TIME_TIMEOUT 3 * 60 //secs +void PartyController::AddAvailableGamePlayers(IVectorView^ availablePlayers, int& remainingSlots, MultiplayerSession^ currentSession) +{ + bool bNewMembersAdded = false; + for each (Windows::Xbox::Multiplayer::GamePlayer^ player in availablePlayers) + { + if( remainingSlots <= 0 ) + { + DQRNetworkManager::LogCommentFormat( L"No more available slots - broadcasting failure of adding player %s",player->XboxUserId->Data()); + m_pDQRNet->SendAddPlayerFailed(player->XboxUserId); + continue; + } + + // Not sure what this condition is actually for - removing until I can see a use for it +#if 0 + if( GetTimeBetweenInSeconds(player->LastInvitedTime, GetCurrentTime()) < LAST_INVITED_TIME_TIMEOUT ) + { + DQRNetworkManager::LogComment( L"Possible user just exited; skipping join request" ); + continue; + } +#endif + + if( m_pDQRNet->IsPlayerInSession(player->XboxUserId, currentSession, NULL)) + { + DQRNetworkManager::LogComment( L"Player is already in session; skipping join request: " + player->XboxUserId->ToString() ); + continue; + } + + // Have a search through our local players, to see if we have a controller for this player. If so, then we'll want to add them directly to the game (if at all), rather than using + // the reservation system + bool bFoundLocal = false; + for( int i = 4; i < 12; i++ ) + { + WXS::User^ user = InputManager.GetUserForGamepad(i); + if( user != nullptr ) + { + if( user->XboxUserId == player->XboxUserId ) + { + bFoundLocal = true; + // Check that they aren't in the game already (don't see how this could be the case anyway) + if( m_pDQRNet->GetPlayerByXuid( PlayerUID(player->XboxUserId->Data()) ) == NULL ) + { + // And check whether they are signed in yet or not + int userIdx = -1; + for( int j = 0; j < MAX_LOCAL_PLAYERS; j++ ) + { + WXS::User^ user2 = ProfileManager.GetUser(j); + if( user2 != nullptr ) + { + if(user2->XboxUserId == user->XboxUserId) + { + userIdx = j; + break; + } + } + } + + // If not found, then attempt to add them since we've found a controller + if( userIdx == -1 ) + { + // Found the appropriate controller. Attempt to add it - this will return -1 if unsuccessful + userIdx = ProfileManager.AddGamepadToGame(i); + } + + // Found a slot, so just need to add the user now + if( userIdx != -1 ) + { + m_pDQRNet->AddLocalPlayerByUserIndex(userIdx); + } + } + } + } + } + if(bFoundLocal) + { + continue; + } + + currentSession->AddMemberReservation( player->XboxUserId, m_pDQRNet->GetNextSmallIdAsJsonString(), true ); + DQRNetworkManager::LogComment( L"Member added: " + player->XboxUserId->ToString() ); + bNewMembersAdded = true; + remainingSlots--; + } + + if(bNewMembersAdded) + { + XboxLiveContext^ xboxLiveContext = m_pDQRNet->m_primaryUserXboxLiveContext; + DQRNetworkManager::LogComment( L"New members found and added from related parties" ); + HRESULT hr = S_OK; + Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ outputSession = m_pDQRNet->WriteSessionHelper( + xboxLiveContext, + currentSession, + Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionWriteMode::UpdateExisting, + hr + ); + + if(outputSession != nullptr) + { + Windows::Xbox::Multiplayer::MultiplayerSessionReference^ convertedSessionRef = + m_pDQRNet->ConvertToWindowsXboxMultiplayerSessionReference(currentSession->SessionReference); + Windows::Xbox::System::User^ actingUser = m_pDQRNet->m_primaryUser; + Party::PullReservedPlayersAsync(actingUser, convertedSessionRef); + + m_pDQRNet->HandleSessionChange( outputSession ); + } + } +} + +void PartyController::OnGamePlayersChanged( GamePlayersChangedEventArgs^ eventArgs ) +{ + DQRNetworkManager::LogComment( L"OnGamePlayersChanged"); + + RefreshPartyView(); + + if( m_pDQRNet->m_primaryUser == nullptr ) return; + + IVectorView^ availablePlayers = nullptr; + auto asyncGetAvailablePlayers = Party::GetAvailableGamePlayersAsync(m_pDQRNet->m_primaryUser); + create_task(asyncGetAvailablePlayers).then([&availablePlayers](task^> t) + { + try + { + availablePlayers = t.get(); + } + catch( Platform::COMException^ ex ) + { + } + }).wait(); + + if( availablePlayers == nullptr ) return; + + Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ sessionRef = + m_pDQRNet->ConvertToMicrosoftXboxServicesMultiplayerSessionReference(eventArgs->GameSession); + + if(sessionRef == nullptr) + { +// LogComment(L"OnGamePlayersChanged: invalid sessionRef."); + return; + } + + XboxLiveContext^ xboxLiveContext = m_pDQRNet->m_primaryUserXboxLiveContext; + IAsyncOperation^ asyncOp = xboxLiveContext->MultiplayerService->GetCurrentSessionAsync( sessionRef ); + create_task(asyncOp) + .then([this, availablePlayers, &xboxLiveContext] (task t) + { + try + { + Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ currentSession = t.get(); + + int remainingSlots = currentSession->SessionConstants->MaxMembersInSession - currentSession->Members->Size; + + DQRNetworkManager::LogCommentFormat( L"OnGamePlayersChanged - calling AddAvailableGamePlayers, with %d players available",availablePlayers->Size); + + // This should be called if we have available slots or not, because it also handles broadcasting failed joins to clients already in the game, which + // the clients need to know that adding a local player has failed + AddAvailableGamePlayers(availablePlayers, remainingSlots, currentSession); + } + catch ( Platform::COMException^ ex ) + { +// LogCommentWithError( L"OnGameSessionReady failed to retrieve current session", ex->HResult ); + } + }).wait(); +} + +void PartyController::OnGameSessionReady( GameSessionReadyEventArgs^ eventArgs ) +{ + DQRNetworkManager::LogComment( L"OnGameSessionReady"); + + // If we are already in this session, then we'll be trying to add a player to the session rather than start up a new game. Set a flag if this is the case. + bool bInSession = false; + if( m_pDQRNet->m_multiplayerSession ) + { + if( m_pDQRNet->m_multiplayerSession->SessionReference->SessionName == eventArgs->GameSession->SessionName ) + { + bInSession = true; + } + } + + // Get a current copy of the MPSD, and search for the players that we are trying to join in it + Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ sessionRef = + m_pDQRNet->ConvertToMicrosoftXboxServicesMultiplayerSessionReference(eventArgs->GameSession); + + vector localAdhocAdditions; + + // Use context from any user at all for this, since this might happen before we are in a game and won't have anything set up in the network manager itself. We are only + // using it to read the session so there shouldn't be any requirements to use a particular live context + WXS::User ^anyUser = nullptr; + if( WXS::User::Users->Size > 0 ) + { + anyUser = WXS::User::Users->GetAt(0); + } + if( anyUser == nullptr ) + { + app.DebugPrintf("Abandoning gamesessionready, no user found\n"); + return; + } + XboxLiveContext^ xboxLiveContext = ref new MXS::XboxLiveContext(anyUser); + + app.DebugPrintf("Gamesessionready user and xboxlivecontext found\n"); + + IAsyncOperation^ asyncOp = xboxLiveContext->MultiplayerService->GetCurrentSessionAsync( sessionRef ); + create_task(asyncOp) + .then([this, eventArgs, bInSession, &localAdhocAdditions,sessionRef] (task t) + { + try + { + Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ session = t.get(); + + // Check if our joining users are in the session + int userFoundMask = 0; + m_pDQRNet->LogCommentFormat(L"Found session, size %d\n",session->Members->Size); + for( int i = 0; i < session->Members->Size; i++ ) + { + MXSM::MultiplayerSessionMember^ member = session->Members->GetAt(i); + + Platform::String^ memberXUID = member->XboxUserId; + bool isAJoiningXuid = false; + for( int j = 0; j < MAX_LOCAL_PLAYERS; j++ ) + { + if( m_pDQRNet->m_joinSessionUserMask & ( 1 << j ) ) + { + if( m_pDQRNet->m_joinSessionXUIDs[j] == memberXUID ) + { + userFoundMask |= ( 1 << j ); + isAJoiningXuid = true; + } + } + } + + // If: + // + // (1) this isn't a player we are actively trying to join + // (2) it isn't currently in the game (only applicable if we are in the same session we're considering going to) + // (3) we've got a controller assigned to a user with a matching xuid + // + // then we might still be interested in this as this could be someone joining via the system interface + if( !isAJoiningXuid ) // Isn't someone we are actively trying to join + { + if( (!bInSession) || // If not in a game session at all + ( ( m_pDQRNet->GetState() == DQRNetworkManager::DNM_INT_STATE_PLAYING ) && ( m_pDQRNet->GetPlayerByXuid( PlayerUID(memberXUID->Data()) ) == NULL ) ) // Or we're fully in, and this player isn't in it + ) + { + for( int j = 4; j < 12; j++ ) // Final check that we have a gamepad for this xuid so that we might possibly be able to pair someone up + { + WXS::User^ user = InputManager.GetUserForGamepad(j); + if( user != nullptr ) + { + m_pDQRNet->LogCommentFormat(L"%d %d %s vs %s\n",i,j,user->XboxUserId->Data(), memberXUID->Data()); + if( user->XboxUserId == memberXUID ) + { + localAdhocAdditions.push_back( memberXUID ); + } + } + } + } + } + } + + // If we are in the middle of a game-controlled join, then we only (currently) care about users involved in this turning up in the session + if( m_pDQRNet->m_joinSessionUserMask != 0 ) + { + m_pDQRNet->LogComment(L"In game controlled join\n"); + // If all the users we are expecting to join are here, then proceed to either start a new game or just add to the existing session{ + if( userFoundMask == m_pDQRNet->m_joinSessionUserMask ) + { + m_pDQRNet->m_joinSessionUserMask = 0; + m_pDQRNet->m_currentUserMask |= userFoundMask; + + if( m_pDQRNet->m_state == DQRNetworkManager::DNM_INT_STATE_JOINING_WAITING_FOR_RESERVATIONS ) + { + // Attempting to join a game via the discovered list of parties that our friends are in + m_pDQRNet->JoinSession(userFoundMask); + } + else + { + if( bInSession ) + { + // Already in a game, and adding a new local player - make them active + m_pDQRNet->AddUsersToSession( userFoundMask, m_pDQRNet->ConvertToMicrosoftXboxServicesMultiplayerSessionReference( eventArgs->GameSession )); + } + } + } + if( localAdhocAdditions.size() > 0 ) + { + // TODO - need to flag up the fact that there were adhoc additions here that we've just ignored, so we can come back and do something with them at a more appropriate time (ie when m_joinSessionUserMask transitions back to 0) + } + } + else if( localAdhocAdditions.size() > 0 ) + { + m_pDQRNet->LogComment(L"Not in game controlled join, but have other player(s) of possible interest\n"); + // Not trying to do a game controlled join at the moment, and we've got at least one user of interest that we've got a controller for and isn't currently playing + + // If we are in a session, then we might be able to add local players into the game + if( bInSession ) + { + m_pDQRNet->LogComment(L"In session, may be able to add local player to game\n"); + int adhocMask = 0; + for( int i = 0; i < localAdhocAdditions.size(); i++ ) + { + // First search to see if we already have the player signed in + int userIdx = -1; + for( int j = 0; j < MAX_LOCAL_PLAYERS; j++ ) + { + WXS::User^ user = ProfileManager.GetUser(j); + if( user != nullptr ) + { + if(user->XboxUserId == localAdhocAdditions[i]) + { + userIdx = j; + break; + } + } + } + // If not found - see if we have a controller for that person so we can add them + if( userIdx == -1 ) + { + for( int j = 4; j < 12; j++ ) + { + WXS::User^ user = InputManager.GetUserForGamepad(j); + if( user != nullptr ) + { + if( user->XboxUserId == localAdhocAdditions[i] ) + { + // Found the appropriate controller. Attempt to add it - this will return -1 if unsuccessful + userIdx = ProfileManager.AddGamepadToGame(j); + break; + } + } + } + } + // If we found or were able to add a player to one of the 4 local player slots, accumulate in a mask + if( userIdx != -1 ) + { + m_pDQRNet->m_joinSessionXUIDs[userIdx] = localAdhocAdditions[i]; + adhocMask |= ( 1 << userIdx ); + } + } + // If we found anyone adhoc to add, join them into the game now + if( adhocMask ) + { + m_pDQRNet->AddUsersToSession( adhocMask, m_pDQRNet->ConvertToMicrosoftXboxServicesMultiplayerSessionReference( eventArgs->GameSession )); + } + } + else + { + // Not in a game, or not in The game that we've just been notified of. We need to try to do a best-fit to work out who + // of our signed in players or controllers we know about, should be being considered as being invited to this game + m_pDQRNet->LogComment(L"Not in a game, considering as possible invite scenario\n"); + int playerIdx = -1; + 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 + { + MXSM::MultiplayerSessionMember^ member = session->Members->GetAt(i); + for( int j = 0; j < MAX_LOCAL_PLAYERS; j++ ) + { + WXS::User ^user = ProfileManager.GetUser(j); + if( user != nullptr ) + { + if( user->XboxUserId == member->XboxUserId ) + { + DQRNetworkManager::LogCommentFormat(L"Found already signed in player %s to act as invite recipient",member->XboxUserId->Data()); + playerIdx = j; + break; + } + } + } + } + if( playerIdx == -1 ) // If nothing found, second pass through to attempt to find a controller that matches + { + for( int i = 0; i < session->Members->Size; i++ ) + { + MXSM::MultiplayerSessionMember^ member = session->Members->GetAt(i); + for( int j = 4; j < 12; j++ ) + { + WXS::User ^user = InputManager.GetUserForGamepad(j); + if( user != nullptr ) + { + if( user->XboxUserId == member->XboxUserId ) + { + DQRNetworkManager::LogCommentFormat(L"Found controller %d for %s (session idx %d) to act as invite recipient",j,member->XboxUserId->Data(),i); + playerIdx = ProfileManager.AddGamepadToGame(j); + if( playerIdx != -1 ) + { + DQRNetworkManager::LogCommentFormat(L"Assigned controller to user index %d",playerIdx); + break; + } + } + } + } + } + } + if( playerIdx != -1 ) + { + m_pDQRNet->LogComment(L"Player found, considered as invite scenario\n"); + m_pDQRNet->HandleNewPartyFoundForPlayer(); + // Note - must pass player 0 here as the player that the invite is for, or else the xbox 1 code generally breaks because it sets a non-zero primary player. We're going to + // be trying to join all current local users to the new game session though. no matter who the invite is for. + DQRNetworkManager::SetPartyProcessJoinSession(0, sessionRef->SessionName, sessionRef->ServiceConfigurationId, sessionRef->SessionTemplateName); + } + else + { + app.DebugPrintf("No player found to join party with\n"); + } + } + } + + } + catch ( Platform::COMException^ ex ) + { + m_pDQRNet->LogCommentWithError( L"OnGameSessionReady failed to retrieve current session", ex->HResult ); + } + }).wait(); + +} + +bool PartyController::CanJoinParty() +{ + Concurrency::critical_section::scoped_lock lock(m_lock); + if( m_partyView == nullptr ) + { + return false; + } + + return (m_partyView->GameSession != nullptr && !m_partyView->IsPartyInAnotherTitle); +} + +bool PartyController::CanInvitePartyToMyGame( + Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ multiplayerSession + ) +{ + if( multiplayerSession == nullptr ) + { + // If my session doesn't exist then shouldn't invite party + return false; + } + + PartyView^ partyView = GetPartyView(); + if( partyView == nullptr ) + { + // If the party view doesn't have a session, then could invite party + return true; + } + + if( partyView->IsPartyInAnotherTitle ) + { + // If my session doesn't exist then shouldn't invite party + return true; + } + + if( partyView->GameSession != nullptr ) + { + // If my session and the party session differs, then could invite party + if ( _wcsicmp(partyView->GameSession->ServiceConfigurationId->Data(), multiplayerSession->SessionReference->ServiceConfigurationId->Data() ) != 0 ) + { + return true; + } + if ( _wcsicmp(partyView->GameSession->SessionName->Data(), multiplayerSession->SessionReference->SessionName->Data() ) != 0 ) + { + return true; + } + if ( _wcsicmp(partyView->GameSession->SessionTemplateName->Data(), multiplayerSession->SessionReference->SessionTemplateName->Data() ) != 0 ) + { + return true; + } + } + else + { + // If the party doesn't have a session, then I could invite party + return true; + } + + // If the party is in my session then return false + return false; +} + +int PartyController::GetActiveAndReservedMemberPartySize() +{ + int partySize = 0; + + PartyView^ partyView = GetPartyView(); + if ( partyView != nullptr ) + { + partySize = partyView->Members->Size + partyView->ReservedMembers->Size; + } + + return partySize; +} + +bool PartyController::IsPartyInAnotherTitle() +{ + PartyView^ partyView = GetPartyView(); + if( partyView == nullptr ) + { + return false; + } + + return partyView->IsPartyInAnotherTitle; +} + +bool PartyController::IsGameSessionReadyEventTriggered() +{ + Concurrency::critical_section::scoped_lock lock(m_lock); + return m_isGameSessionReadyEventTriggered; +} + +void PartyController::ClearGameSessionReadyEventTriggered() +{ + Concurrency::critical_section::scoped_lock lock(m_lock); + m_isGameSessionReadyEventTriggered = false; +} + +bool PartyController::DoesPartySessionExist() +{ + Concurrency::critical_section::scoped_lock lock(m_lock); + + if( m_partyView != nullptr && m_partyView->GameSession != nullptr) + { + return true; + } + return false; +} + +Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference ^ PartyController::GetGamePartySessionReference() +{ + Concurrency::critical_section::scoped_lock lock(m_lock); + + if( m_partyView != nullptr ) + { + if( m_partyView->GameSession != nullptr) + { + return m_pDQRNet->ConvertToMicrosoftXboxServicesMultiplayerSessionReference(m_partyView->GameSession); + } + } + return nullptr; +} + +PartyView^ PartyController::GetPartyView() +{ + Concurrency::critical_section::scoped_lock lock(m_lock); + return m_partyView; +} + +bool PartyController::DoesPartyAndSessionPlayersMatch( + Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ session + ) +{ + PartyView^ partyView = GetPartyView(); + + // Verify that session size and party size match + if ( session->Members->Size != partyView->Members->Size ) + { + return false; + } + + bool inParty; + + // Verify that session players match current party players + for ( unsigned int i = 0; i < session->Members->Size; i++ ) + { + inParty = false; + + MultiplayerSessionMember^ member = session->Members->GetAt( i ); + + for ( PartyMember^ partyMember : partyView->Members ) + { + if ( _wcsicmp(member->XboxUserId->Data(), partyMember->XboxUserId->Data() ) == 0 ) + { + inParty = inParty || true; + } + } + + if ( !inParty ) + { + return false; + } + } + + return true; +} + +void PartyController::SetPartyView( PartyView^ partyView ) +{ + Concurrency::critical_section::scoped_lock lock(m_lock); + m_partyView = partyView; +} + +Windows::Foundation::DateTime PartyController::GetCurrentTime() +{ + ULARGE_INTEGER uInt; + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + uInt.LowPart = ft.dwLowDateTime; + uInt.HighPart = ft.dwHighDateTime; + + Windows::Foundation::DateTime time; + time.UniversalTime = uInt.QuadPart; + return time; +} + +double PartyController::GetTimeBetweenInSeconds(Windows::Foundation::DateTime dt1, Windows::Foundation::DateTime dt2) +{ + const uint64 tickPerSecond = 10000000i64; + uint64 deltaTime = dt2.UniversalTime - dt1.UniversalTime; + return (double)deltaTime / (double)tickPerSecond; +} \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/PartyController.h b/Minecraft.Client/Durango/Network/PartyController.h new file mode 100644 index 00000000..621162ce --- /dev/null +++ b/Minecraft.Client/Durango/Network/PartyController.h @@ -0,0 +1,74 @@ +//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +//// PARTICULAR PURPOSE. +//// +//// Copyright (c) Microsoft Corporation. All rights reserved +#pragma once + +#include +class DQRNetworkManager; + +class PartyController +{ +public: + PartyController(DQRNetworkManager *pDQRNet); + + void SetPartyView( Windows::Xbox::Multiplayer::PartyView^ partyView ); + Windows::Xbox::Multiplayer::PartyView^ GetPartyView(); + + void RefreshPartyView(); + bool AddLocalUsersToParty(int userMask, Windows::Xbox::System::User^ primaryUser); + void RemoveLocalUsersFromParty(Windows::Xbox::System::User^ primaryUser); + void RemoveLocalUsersFromParty(Windows::Xbox::System::User^ primaryUser, int playerMask, Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ sessionReference); + void RemoveLocalUserFromParty(Windows::Xbox::System::User^ userToRemove); + void RegisterEventHandlers(); + void UnregisterEventHandlers(); + void UnregisterGamePlayersEventHandler(); + void RegisterGamePlayersChangedEventHandler(); + bool CanJoinParty(); + bool CanInvitePartyToMyGame( Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ multiplayerSession ); + bool IsPartyInAnotherTitle(); + bool IsGameSessionReadyEventTriggered(); + bool DoesPartySessionExist(); + Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference ^ GetGamePartySessionReference(); + void ClearGameSessionReadyEventTriggered(); + int GetActiveAndReservedMemberPartySize(); + bool DoesPartyAndSessionPlayersMatch( + Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ session + ); + void CheckPartySessionFull(Windows::Xbox::System::User^ primaryUser); + void SetJoinability(bool isJoinable); + void DisassociateSessionFromParty( Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ sessionReference); + +private: + Concurrency::critical_section m_lock; + bool m_isGameSessionReadyEventTriggered; + bool m_isGamePlayerEventRegistered; + DQRNetworkManager *m_pDQRNet; + + static void DebugPrintPartyView( Windows::Xbox::Multiplayer::PartyView^ partyView ); + + void OnPartyStateChanged( Windows::Xbox::Multiplayer::PartyStateChangedEventArgs^ eventArgs ); + void OnPartyRosterChanged( Windows::Xbox::Multiplayer::PartyRosterChangedEventArgs^ eventArgs ); + void OnGamePlayersChanged( Windows::Xbox::Multiplayer::GamePlayersChangedEventArgs^ eventArgs ); + void OnGameSessionReady( Windows::Xbox::Multiplayer::GameSessionReadyEventArgs^ eventArgs ); + Windows::Xbox::Multiplayer::PartyView^ m_partyView; + Microsoft::Xbox::Services::Multiplayer::MultiplayerSessionReference^ m_partyGameReadyRef; + void AddAvailableGamePlayers( + Windows::Foundation::Collections::IVectorView^ availablePlayers, + int& remainingSlots, + Microsoft::Xbox::Services::Multiplayer::MultiplayerSession^ currentSession + ); + + Windows::Foundation::DateTime GetCurrentTime(); + double GetTimeBetweenInSeconds(Windows::Foundation::DateTime dt1, Windows::Foundation::DateTime dt2); + + // Party/Session events. + Windows::Foundation::EventRegistrationToken m_partyRosterChangedToken; + Windows::Foundation::EventRegistrationToken m_partyStateChangedToken; + Windows::Foundation::EventRegistrationToken m_partyGamePlayersChangedToken; + Windows::Foundation::EventRegistrationToken m_partyGameSessionReadyToken; +}; + + diff --git a/Minecraft.Client/Durango/Network/PlatformNetworkManagerDurango.cpp b/Minecraft.Client/Durango/Network/PlatformNetworkManagerDurango.cpp new file mode 100644 index 00000000..3efba5ed --- /dev/null +++ b/Minecraft.Client/Durango/Network/PlatformNetworkManagerDurango.cpp @@ -0,0 +1,940 @@ +#include "stdafx.h" +#include "..\..\..\Minecraft.World\Socket.h" +#include "..\..\..\Minecraft.World\StringHelpers.h" +#include "PlatformNetworkManagerDurango.h" +#include "NetworkPlayerDurango.h" + +CPlatformNetworkManagerDurango *g_pPlatformNetworkManager; + +void CPlatformNetworkManagerDurango::HandleStateChange(DQRNetworkManager::eDQRNetworkManagerState oldState, DQRNetworkManager::eDQRNetworkManagerState newState) +{ + static const char * c_apszStateNames[] = + { + "DNM_STATE_INITIALISING", + "DNM_STATE_INITIALISE_FAILED", + "DNM_STATE_IDLE", + "DNM_STATE_HOSTING", + "DNM_STATE_JOINING", + "DNM_STATE_STARTING", + "DNM_STATE_PLAYING", + "DNM_STATE_LEAVING", + "DNM_STATE_ENDING", + }; + + app.DebugPrintf( "Network State: %s ==> %s\n", + c_apszStateNames[ oldState ], + c_apszStateNames[ newState ] ); + + if( newState == DQRNetworkManager::DNM_STATE_HOSTING ) + { + m_bLeavingGame = false; + m_bLeaveGameOnTick = false; + m_bHostChanged = false; + g_NetworkManager.StateChange_AnyToHosting(); + } + else if( newState == DQRNetworkManager::DNM_STATE_JOINING ) + { + m_bLeavingGame = false; + m_bLeaveGameOnTick = false; + m_bHostChanged = false; + g_NetworkManager.StateChange_AnyToJoining(); + } + else if( newState == DQRNetworkManager::DNM_STATE_IDLE && oldState == DQRNetworkManager::DNM_STATE_JOINING ) + { + g_NetworkManager.StateChange_JoiningToIdle(JOIN_FAILED_NONSPECIFIC); + } + else if( newState == DQRNetworkManager::DNM_STATE_IDLE && oldState == DQRNetworkManager::DNM_STATE_HOSTING ) + { + m_bLeavingGame = true; + } + else if( newState == DQRNetworkManager::DNM_STATE_STARTING ) + { + m_lastPlayerEventTimeStart = app.getAppTime(); + + g_NetworkManager.StateChange_AnyToStarting(); + } + // Fix for #93148 - TCR 001: BAS Game Stability: Title will crash for the multiplayer client if host of the game will exit during the clients loading to created world. + // 4J Stu - If the client joins just as the host is exiting, then they can skip to leaving without passing through ending + else if( newState == DQRNetworkManager::DNM_STATE_ENDING ) + { + g_NetworkManager.StateChange_AnyToEnding( oldState == DQRNetworkManager::DNM_STATE_PLAYING ); + + if( m_pDQRNet->IsHost() ) + { + m_bLeavingGame = true; + } + } + + if( newState == DQRNetworkManager::DNM_STATE_IDLE ) + { + g_NetworkManager.StateChange_AnyToIdle(); + } +} + +void CPlatformNetworkManagerDurango::HandlePlayerJoined(DQRNetworkPlayer *pDQRPlayer) +{ + const char * pszDescription; + + // If this is a local player, we need to inform the chat system that it has joined + if( pDQRPlayer->IsLocal() ) + { + m_pDQRNet->ChatPlayerJoined(pDQRPlayer->GetLocalPlayerIndex()); + } + + // 4J Stu - We create a fake socket for every where that we need an INBOUND queue of game data. Outbound + // is all handled by QNet so we don't need that. Therefore each client player has one, and the host has one + // for each client player. + bool createFakeSocket = false; + bool localPlayer = false; + + NetworkPlayerDurango *networkPlayer = (NetworkPlayerDurango *)addNetworkPlayer(pDQRPlayer); + + // Request full display name for this player + m_pDQRNet->RequestDisplayName(pDQRPlayer); + + if( pDQRPlayer->IsLocal() ) + { + localPlayer = true; + if( pDQRPlayer->IsHost() ) + { + pszDescription = "local host"; + // 4J Stu - No socket for the localhost as it uses a special loopback queue + + m_machineDQRPrimaryPlayers.push_back( pDQRPlayer ); + } + else + { + pszDescription = "local"; + + // We need an inbound queue on all local players to receive data from the host + createFakeSocket = true; + } + } + else + { + if( pDQRPlayer->IsHost() ) + { + pszDescription = "remote host"; + } + else + { + pszDescription = "remote"; + + // If we are the host, then create a fake socket for every remote player + if( m_pDQRNet->IsHost() ) + { + createFakeSocket = true; + } + } + + if( m_pDQRNet->IsHost() && !m_bHostChanged ) + { + // Do we already have a primary player for this system? + bool systemHasPrimaryPlayer = false; + for(AUTO_VAR(it, m_machineDQRPrimaryPlayers.begin()); it < m_machineDQRPrimaryPlayers.end(); ++it) + { + DQRNetworkPlayer *pQNetPrimaryPlayer = *it; + if( pDQRPlayer->IsSameSystem(pQNetPrimaryPlayer) ) + { + systemHasPrimaryPlayer = true; + break; + } + } + if( !systemHasPrimaryPlayer ) + m_machineDQRPrimaryPlayers.push_back( pDQRPlayer ); + } + } + g_NetworkManager.PlayerJoining( networkPlayer ); + + if( createFakeSocket == true && !m_bHostChanged ) + { + g_NetworkManager.CreateSocket( networkPlayer, localPlayer ); + } + + app.DebugPrintf( "Player 0x%p \"%ls\" joined; %s; voice %i; camera %i.\n", + pDQRPlayer, + pDQRPlayer->GetGamertag(), + pszDescription, + (int) pDQRPlayer->HasVoice(), + (int) pDQRPlayer->HasCamera() ); + + + if( m_pDQRNet->IsHost() ) + { + g_NetworkManager.UpdateAndSetGameSessionData(); + SystemFlagAddPlayer( networkPlayer ); + } + + for( int idx = 0; idx < XUSER_MAX_COUNT; ++idx) + { + if(playerChangedCallback[idx] != NULL) + playerChangedCallback[idx]( playerChangedCallbackParam[idx], networkPlayer, false ); + } + + if(m_pDQRNet->GetState() == QNET_STATE_GAME_PLAY) + { + int localPlayerCount = 0; + for(unsigned int idx = 0; idx < XUSER_MAX_COUNT; ++idx) + { + if( m_pDQRNet->GetLocalPlayerByUserIndex(idx) != NULL ) ++localPlayerCount; + } + + float appTime = app.getAppTime(); + + // Only record stats for the primary player here + m_lastPlayerEventTimeStart = appTime; + } +} + +void CPlatformNetworkManagerDurango::HandlePlayerLeaving(DQRNetworkPlayer *pDQRPlayer) +{ + //__debugbreak(); + + app.DebugPrintf( "Player 0x%p leaving.\n", + pDQRPlayer ); + + INetworkPlayer *networkPlayer = getNetworkPlayer(pDQRPlayer); + + if( networkPlayer ) + { + // Get our wrapper object associated with this player. + Socket *socket = networkPlayer->GetSocket(); + if( socket != NULL ) + { + // If we are in game then remove this player from the game as well. + // We may get here either from the player requesting to exit the game, + // in which case we they will already have left the game server, or from a disconnection + // where we then have to remove them from the game server + if( m_pDQRNet->IsHost() && !m_bHostChanged ) + { + g_NetworkManager.CloseConnection(networkPlayer); + } + + // Free the wrapper object memory. + // TODO 4J Stu - We may still be using this at the point that the player leaves the session. + // We need this as long as the game server still needs to communicate with the player + //delete socket; + + networkPlayer->SetSocket( NULL ); + } + + if( m_pDQRNet->IsHost() && !m_bHostChanged ) + { + if( isSystemPrimaryPlayer(pDQRPlayer) ) + { + DQRNetworkPlayer *pNewDQRPrimaryPlayer = NULL; + for(unsigned int i = 0; i < m_pDQRNet->GetPlayerCount(); ++i ) + { + DQRNetworkPlayer *pDQRPlayer2 = m_pDQRNet->GetPlayerByIndex( i ); + + if( pDQRPlayer2 != pDQRPlayer && pDQRPlayer2->IsSameSystem( pDQRPlayer ) ) + { + pNewDQRPrimaryPlayer = pDQRPlayer2; + break; + } + } + AUTO_VAR(it, find( m_machineDQRPrimaryPlayers.begin(), m_machineDQRPrimaryPlayers.end(), pDQRPlayer)); + if( it != m_machineDQRPrimaryPlayers.end() ) + { + m_machineDQRPrimaryPlayers.erase( it ); + } + + if( pNewDQRPrimaryPlayer != NULL ) + m_machineDQRPrimaryPlayers.push_back( pNewDQRPrimaryPlayer ); + } + + g_NetworkManager.UpdateAndSetGameSessionData( networkPlayer ); + SystemFlagRemovePlayer( networkPlayer ); + + } + + g_NetworkManager.PlayerLeaving( networkPlayer ); + + for( int idx = 0; idx < XUSER_MAX_COUNT; ++idx) + { + if(playerChangedCallback[idx] != NULL) + playerChangedCallback[idx]( playerChangedCallbackParam[idx], networkPlayer, true ); + } + + if(m_pDQRNet->GetState() == DQRNetworkManager::DNM_STATE_PLAYING) + { + int localPlayerCount = 0; + for(unsigned int idx = 0; idx < XUSER_MAX_COUNT; ++idx) + { + if( m_pDQRNet->GetLocalPlayerByUserIndex(idx) != NULL ) ++localPlayerCount; + } + + float appTime = app.getAppTime(); + m_lastPlayerEventTimeStart = appTime; + } + + removeNetworkPlayer(pDQRPlayer); + } +} + +void CPlatformNetworkManagerDurango::HandleDataReceived(DQRNetworkPlayer *playerFrom, DQRNetworkPlayer *playerTo, unsigned char *data, unsigned int dataSize) +{ +#if 0 + // TODO - put this back in + if(m_pSQRNet->GetState() == SQRNetworkManager::SNM_STATE_ENDING) + { + return; + } +#endif + + if( playerTo->IsHost() ) + { + // If we are the host we care who this came from + //app.DebugPrintf( "Pushing data into host read queue for user \"%ls\"\n", pPlayerFrom->GetGamertag()); + // Push this data into the read queue for the player that sent it + INetworkPlayer *pPlayerFrom = getNetworkPlayer(playerFrom); + Socket *socket = pPlayerFrom->GetSocket(); + + if(socket != NULL) + socket->pushDataToQueue(data, dataSize, false); + } + else + { + // If we are not the host the message must have come from the host, so we care more about who it is addressed to + INetworkPlayer *pPlayerTo = getNetworkPlayer(playerTo); + Socket *socket = pPlayerTo->GetSocket(); + //app.DebugPrintf( "Pushing data into read queue for user \"%ls\"\n", apPlayersTo[dwPlayer]->GetGamertag()); + if(socket != NULL) + socket->pushDataToQueue(data, dataSize); + } +} + +void CPlatformNetworkManagerDurango::HandleInviteReceived(int playerIndex, DQRNetworkManager::SessionInfo *pInviteInfo) +{ + g_NetworkManager.GameInviteReceived(playerIndex, pInviteInfo); +} + +bool CPlatformNetworkManagerDurango::Initialise(CGameNetworkManager *pGameNetworkManager, int flagIndexSize) +{ + m_pDQRNet = new DQRNetworkManager(this); + m_pDQRNet->Initialise(); + + m_hostGameSessionIsJoinable = false; + m_pGameNetworkManager = pGameNetworkManager; + m_flagIndexSize = flagIndexSize; + g_pPlatformNetworkManager = this; + for( int i = 0; i < XUSER_MAX_COUNT; i++ ) + { + playerChangedCallback[ i ] = NULL; + } + + m_bLeavingGame = false; + m_bLeaveGameOnTick = false; + m_bHostChanged = false; + + m_bSearchResultsReady = false; + m_bSearchPending = false; + + m_bIsOfflineGame = false; + m_pSearchParam = NULL; + m_SessionsUpdatedCallback = NULL; + + m_searchResultsCount = 0; + m_lastSearchStartTime = 0; + + // The results that will be filled in with the current search + m_pSearchResults = NULL; + + Windows::Networking::Connectivity::NetworkInformation::NetworkStatusChanged += ref new Windows::Networking::Connectivity::NetworkStatusChangedEventHandler( []( Platform::Object^ pxObject ) + { + app.DebugPrintf("NetworkStatusChanged callback\n" ); + auto internetProfile = Windows::Networking::Connectivity::NetworkInformation::GetInternetConnectionProfile(); + if (internetProfile != nullptr) + { + auto connectionLevel = internetProfile->GetNetworkConnectivityLevel(); + app.DebugPrintf("Connection level has changed to (%d)\n", connectionLevel); + + //int iPrimaryPlayer = g_NetworkManager.GetPrimaryPad(); + //bool bConnected = (connectionLevel == Windows::Networking::Connectivity::NetworkConnectivityLevel::XboxLiveAccess)?true:false; + //if((g_NetworkManager.GetLockedProfile()!=-1) && iPrimaryPlayer!=-1 && bConnected == false && g_NetworkManager.IsInSession() ) + //{ + // app.SetAction(iPrimaryPlayer,eAppAction_EthernetDisconnected); + //} + } + }); + + // Success! + return true; +} + +void CPlatformNetworkManagerDurango::Terminate() +{ +} + +int CPlatformNetworkManagerDurango::GetJoiningReadyPercentage() +{ + return 100; +} + +int CPlatformNetworkManagerDurango::CorrectErrorIDS(int IDS) +{ + return IDS; +} + +bool CPlatformNetworkManagerDurango::isSystemPrimaryPlayer(DQRNetworkPlayer *pDQRPlayer) +{ + bool playerIsSystemPrimary = false; + for(auto it = m_machineDQRPrimaryPlayers.begin(); it < m_machineDQRPrimaryPlayers.end(); ++it) + { + DQRNetworkPlayer *pDQRPrimaryPlayer = *it; + if( pDQRPrimaryPlayer == pDQRPlayer ) + { + playerIsSystemPrimary = true; + break; + } + } + return playerIsSystemPrimary; +} + +// We call this twice a frame, either side of the render call so is a good place to "tick" things +void CPlatformNetworkManagerDurango::DoWork() +{ + m_pDQRNet->Tick(); + + TickSearch(); + + if( m_bLeaveGameOnTick ) + { + m_pDQRNet->LeaveRoom(); + m_bLeaveGameOnTick = false; + } +} + +int CPlatformNetworkManagerDurango::GetPlayerCount() +{ + return m_pDQRNet->GetPlayerCount(); +} + +bool CPlatformNetworkManagerDurango::ShouldMessageForFullSession() +{ + return m_pDQRNet->ShouldMessageForFullSession(); +} + +int CPlatformNetworkManagerDurango::GetOnlinePlayerCount() +{ + return m_pDQRNet->GetOnlinePlayerCount(); +} + +int CPlatformNetworkManagerDurango::GetLocalPlayerMask(int playerIndex) +{ + return 1 << playerIndex; +} + +bool CPlatformNetworkManagerDurango::AddLocalPlayerByUserIndex( int userIndex ) +{ + return m_pDQRNet->AddLocalPlayerByUserIndex( userIndex ); +} + +bool CPlatformNetworkManagerDurango::RemoveLocalPlayerByUserIndex( int userIndex ) +{ + return m_pDQRNet->RemoveLocalPlayerByUserIndex( userIndex ); +} + +bool CPlatformNetworkManagerDurango::IsInStatsEnabledSession() +{ + return true; +} + +bool CPlatformNetworkManagerDurango::SessionHasSpace(unsigned int spaceRequired /*= 1*/) +{ + return true; +} + +void CPlatformNetworkManagerDurango::SendInviteGUI(int quadrant) +{ + m_pDQRNet->SendInviteGUI(quadrant); +} + +bool CPlatformNetworkManagerDurango::IsAddingPlayer() +{ + return m_pDQRNet->IsAddingPlayer(); +} + +bool CPlatformNetworkManagerDurango::LeaveGame(bool bMigrateHost) +{ + if( m_bLeavingGame ) return true; + + m_bLeavingGame = true; + + // If we are the host wait for the game server to end + if(m_pDQRNet->IsHost() && g_NetworkManager.ServerStoppedValid()) + { +// m_pDQRNet->EndGame(); + g_NetworkManager.ServerStoppedWait(); + g_NetworkManager.ServerStoppedDestroy(); + } + return _LeaveGame(bMigrateHost, true);; +} + +bool CPlatformNetworkManagerDurango::_LeaveGame(bool bMigrateHost, bool bLeaveRoom) +{ + m_bLeavingGame = true; + m_bLeaveGameOnTick = true; + m_migrateHostOnLeave = bMigrateHost; + return true; +} + +void CPlatformNetworkManagerDurango::HostGame(int localUsersMask, bool bOnlineGame, bool bIsPrivate, unsigned char publicSlots /*= MINECRAFT_NET_MAX_PLAYERS*/, unsigned char privateSlots /*= 0*/) +{ +// #ifdef _XBOX + // 4J Stu - We probably did this earlier as well, but just to be sure! + SetLocalGame( !bOnlineGame ); + SetPrivateGame( bIsPrivate ); + SystemFlagReset(); + + // Make sure that the Primary Pad is in by default + localUsersMask |= GetLocalPlayerMask( g_NetworkManager.GetPrimaryPad() ); + + m_bLeavingGame = false; + + _HostGame( localUsersMask, publicSlots, privateSlots ); +//#endif +} + +void CPlatformNetworkManagerDurango::_HostGame(int usersMask, unsigned char publicSlots /*= MINECRAFT_NET_MAX_PLAYERS*/, unsigned char privateSlots /*= 0*/) +{ + memset(&m_hostGameSessionData,0,sizeof(m_hostGameSessionData)); + m_hostGameSessionData.netVersion = MINECRAFT_NET_VERSION; + m_hostGameSessionData.isReadyToJoin = false; + m_hostGameSessionData.m_uiGameHostSettings = app.GetGameHostOption(eGameHostOption_All); + m_hostGameSessionIsJoinable = !IsPrivateGame(); + + m_pDQRNet->CreateAndJoinSession(usersMask, (unsigned char *)&m_hostGameSessionData, sizeof(m_hostGameSessionData), IsLocalGame() ); +} + +bool CPlatformNetworkManagerDurango::_StartGame() +{ + return true; +} + +int CPlatformNetworkManagerDurango::JoinGame(FriendSessionInfo *searchResult, int localUsersMask, int primaryUserIndex) +{ + app.DebugPrintf("Joining game party from search result\n"); + + int joinPlayerCount = 0; + for( int i = 0; i < DQRNetworkManager::MAX_LOCAL_PLAYER_COUNT; i++ ) + { + if( localUsersMask & ( 1 << i ) ) + { + // Check if this joining user is already in the session, in which case we don't need to count it + bool isJoiningUser = false; + for( int j = 0; j < searchResult->searchResult.m_usedSlotCount; j++ ) + { + Platform::String^ xuid = ref new Platform::String(searchResult->searchResult.m_sessionXuids[j].c_str()); + if( xuid == ProfileManager.GetUser(i)->XboxUserId ) + { + app.DebugPrintf("Joining user found to be already in session, so won't be counting to our total\n"); + isJoiningUser = true; + break; + } + } + if( !isJoiningUser ) + { + joinPlayerCount++; + } + } + } + app.DebugPrintf("Used slots: %d, fully playing players: %d, trying to join %d\n", searchResult->searchResult.m_usedSlotCount, searchResult->searchResult.m_playerCount, joinPlayerCount); + GameSessionData *gameSession = (GameSessionData *)(&searchResult->data); + if( ( searchResult->searchResult.m_usedSlotCount + joinPlayerCount ) > DQRNetworkManager::MAX_ONLINE_PLAYER_COUNT ) + { + return CGameNetworkManager::JOINGAME_FAIL_SERVER_FULL; + } + + if(m_pDQRNet->JoinPartyFromSearchResult(&searchResult->searchResult, localUsersMask)) + { + app.DebugPrintf("Join success\n"); + return CGameNetworkManager::JOINGAME_SUCCESS; + } + else + { + app.DebugPrintf("Join fail\n"); + return CGameNetworkManager::JOINGAME_FAIL_GENERAL; + } +} + +void CPlatformNetworkManagerDurango::CancelJoinGame() +{ + m_pDQRNet->CancelJoinPartyFromSearchResult(); +} + +bool CPlatformNetworkManagerDurango::SetLocalGame(bool isLocal) +{ + m_bIsOfflineGame = isLocal; + + return true; +} + +void CPlatformNetworkManagerDurango::SetPrivateGame(bool isPrivate) +{ + app.DebugPrintf("Setting as private game: %s\n", isPrivate ? "yes" : "no" ); + m_bIsPrivateGame = isPrivate; +} + +void CPlatformNetworkManagerDurango::RegisterPlayerChangedCallback(int iPad, void (*callback)(void *callbackParam, INetworkPlayer *pPlayer, bool leaving), void *callbackParam) +{ + playerChangedCallback[iPad] = callback; + playerChangedCallbackParam[iPad] = callbackParam; +} + +void CPlatformNetworkManagerDurango::UnRegisterPlayerChangedCallback(int iPad, void (*callback)(void *callbackParam, INetworkPlayer *pPlayer, bool leaving), void *callbackParam) +{ + if(playerChangedCallbackParam[iPad] == callbackParam) + { + playerChangedCallback[iPad] = NULL; + playerChangedCallbackParam[iPad] = NULL; + } +} + +void CPlatformNetworkManagerDurango::HandleSignInChange() +{ + return; +} + +void CPlatformNetworkManagerDurango::HandleAddLocalPlayerFailed(int idx, bool serverFull) +{ + g_NetworkManager.AddLocalPlayerFailed(idx, serverFull); +} + +void CPlatformNetworkManagerDurango::HandleDisconnect(bool bLostRoomOnly) +{ + g_NetworkManager.HandleDisconnect(bLostRoomOnly); +} + +bool CPlatformNetworkManagerDurango::_RunNetworkGame() +{ + m_pDQRNet->StartGame(); + m_hostGameSessionData.isReadyToJoin = true; + m_pDQRNet->UpdateCustomSessionData(); + return true; +} + +void CPlatformNetworkManagerDurango::UpdateAndSetGameSessionData(INetworkPlayer *pNetworkPlayerLeaving /*= NULL*/) +{ + if( this->m_bLeavingGame ) + return; + + if( GetHostPlayer() == NULL ) + return; + + m_hostGameSessionData.m_uiGameHostSettings = app.GetGameHostOption(eGameHostOption_All); + + m_pDQRNet->UpdateCustomSessionData(); +} + +int CPlatformNetworkManagerDurango::RemovePlayerOnSocketClosedThreadProc( void* lpParam ) +{ + INetworkPlayer *pNetworkPlayer = (INetworkPlayer *)lpParam; + + Socket *socket = pNetworkPlayer->GetSocket(); + + if( socket != NULL ) + { + //printf("Waiting for socket closed event\n"); + socket->m_socketClosedEvent->WaitForSignal(INFINITE); + + //printf("Socket closed event has fired\n"); + // 4J Stu - Clear our reference to this socket + pNetworkPlayer->SetSocket( NULL ); + delete socket; + } + + return g_pPlatformNetworkManager->RemoveLocalPlayer( pNetworkPlayer ); +} + +bool CPlatformNetworkManagerDurango::RemoveLocalPlayer( INetworkPlayer *pNetworkPlayer ) +{ + return true; +} + +CPlatformNetworkManagerDurango::PlayerFlags::PlayerFlags(INetworkPlayer *pNetworkPlayer, unsigned int count) +{ + // 4J Stu - Don't assert, just make it a multiple of 8! This count is calculated from a load of separate values, + // and makes tweaking world/render sizes a pain if we hit an assert here + count = (count + 8 - 1) & ~(8 - 1); + //assert( ( count % 8 ) == 0 ); + this->m_pNetworkPlayer = pNetworkPlayer; + this->flags = new unsigned char [ count / 8 ]; + memset( this->flags, 0, count / 8 ); + this->count = count; +} + +CPlatformNetworkManagerDurango::PlayerFlags::~PlayerFlags() +{ + delete [] flags; +} + +// Add a player to the per system flag storage - if we've already got a player from that system, copy its flags over +void CPlatformNetworkManagerDurango::SystemFlagAddPlayer(INetworkPlayer *pNetworkPlayer) +{ + PlayerFlags *newPlayerFlags = new PlayerFlags( pNetworkPlayer, m_flagIndexSize); + // If any of our existing players are on the same system, then copy over flags from that one + for( unsigned int i = 0; i < m_playerFlags.size(); i++ ) + { + if( pNetworkPlayer->IsSameSystem(m_playerFlags[i]->m_pNetworkPlayer) ) + { + memcpy( newPlayerFlags->flags, m_playerFlags[i]->flags, m_playerFlags[i]->count / 8 ); + break; + } + } + m_playerFlags.push_back(newPlayerFlags); +} + +// Remove a player from the per system flag storage - just maintains the m_playerFlags vector without any gaps in it +void CPlatformNetworkManagerDurango::SystemFlagRemovePlayer(INetworkPlayer *pNetworkPlayer) +{ + for( unsigned int i = 0; i < m_playerFlags.size(); i++ ) + { + if( m_playerFlags[i]->m_pNetworkPlayer == pNetworkPlayer ) + { + delete m_playerFlags[i]; + m_playerFlags[i] = m_playerFlags.back(); + m_playerFlags.pop_back(); + return; + } + } +} + +void CPlatformNetworkManagerDurango::SystemFlagReset() +{ + for( unsigned int i = 0; i < m_playerFlags.size(); i++ ) + { + delete m_playerFlags[i]; + } + m_playerFlags.clear(); +} + +// Set a per system flag - this is done by setting the flag on every player that shares that system +void CPlatformNetworkManagerDurango::SystemFlagSet(INetworkPlayer *pNetworkPlayer, int index) +{ + if( ( index < 0 ) || ( index >= m_flagIndexSize ) ) return; + if( pNetworkPlayer == NULL ) return; + + for( unsigned int i = 0; i < m_playerFlags.size(); i++ ) + { + if( pNetworkPlayer->IsSameSystem(m_playerFlags[i]->m_pNetworkPlayer) ) + { + m_playerFlags[i]->flags[ index / 8 ] |= ( 128 >> ( index % 8 ) ); + } + } +} + +// Get value of a per system flag - can be read from the flags of the passed in player as anything else sent to that +// system should also have been duplicated here +bool CPlatformNetworkManagerDurango::SystemFlagGet(INetworkPlayer *pNetworkPlayer, int index) +{ + if( ( index < 0 ) || ( index >= m_flagIndexSize ) ) return false; + if( pNetworkPlayer == NULL ) + { + return false; + } + + for( unsigned int i = 0; i < m_playerFlags.size(); i++ ) + { + if( m_playerFlags[i]->m_pNetworkPlayer == pNetworkPlayer ) + { + return ( ( m_playerFlags[i]->flags[ index / 8 ] & ( 128 >> ( index % 8 ) ) ) != 0 ); + } + } + return false; +} + +wstring CPlatformNetworkManagerDurango::GatherStats() +{ + return L""; +} + +wstring CPlatformNetworkManagerDurango::GatherRTTStats() +{ + return L""; +} + +void CPlatformNetworkManagerDurango::TickSearch() +{ + if( m_bSearchPending ) + { + if( !m_pDQRNet->FriendPartyManagerIsBusy() ) + { + m_searchResultsCount = m_pDQRNet->FriendPartyManagerGetCount(); + delete [] m_pSearchResults; + m_pSearchResults = new DQRNetworkManager::SessionSearchResult[m_searchResultsCount]; + + for( int i = 0; i < m_searchResultsCount; i++ ) + { + m_pDQRNet->FriendPartyManagerGetSessionInfo(i, &m_pSearchResults[i] ); + } + m_bSearchPending = false; + + if( m_SessionsUpdatedCallback != NULL ) m_SessionsUpdatedCallback(m_pSearchParam); + } + } + else + { + if( !m_pDQRNet->FriendPartyManagerIsBusy() ) + { + // Don't start searches unless we have registered a callback + if( m_SessionsUpdatedCallback != NULL && (m_lastSearchStartTime + MINECRAFT_DURANGO_PARTY_SEARCH_DELAY_MILLISECONDS) < GetTickCount() ) + { + if( m_pDQRNet->FriendPartyManagerSearch() ); + { + m_bSearchPending = true; + m_lastSearchStartTime = GetTickCount(); + } + } + } + } +} + +vector *CPlatformNetworkManagerDurango::GetSessionList(int iPad, int localPlayers, bool partyOnly) +{ + vector *filteredList = new vector(); + for( int i = 0; i < m_searchResultsCount; i++ ) + { + GameSessionData *gameSessionData = (GameSessionData *)m_pSearchResults[i].m_extData; + if( ( gameSessionData->netVersion == MINECRAFT_NET_VERSION ) && + ( gameSessionData->isReadyToJoin ) ) + { + FriendSessionInfo *session = new FriendSessionInfo(); + session->searchResult = m_pSearchResults[i]; + session->searchResult.m_extData = NULL; // We have another copy of the GameSessionData, so don't need to make another copy of this here + session->displayLabelLength = session->searchResult.m_playerNames[0].size(); + session->displayLabel = new wchar_t[ session->displayLabelLength + 1 ]; + memcpy(&session->data, gameSessionData, sizeof(GameSessionData)); + wcscpy_s(session->displayLabel, session->displayLabelLength + 1, session->searchResult.m_playerNames[0].c_str() ); + filteredList->push_back(session); + } + } + return filteredList; +} + +bool CPlatformNetworkManagerDurango::GetGameSessionInfo(int iPad, SessionID sessionId, FriendSessionInfo *foundSessionInfo) +{ + return false; +} + +void CPlatformNetworkManagerDurango::SetSessionsUpdatedCallback( void (*SessionsUpdatedCallback)(LPVOID pParam), LPVOID pSearchParam ) +{ + m_SessionsUpdatedCallback = SessionsUpdatedCallback; m_pSearchParam = pSearchParam; +} + +void CPlatformNetworkManagerDurango::GetFullFriendSessionInfo( FriendSessionInfo *foundSession, void (* FriendSessionUpdatedFn)(bool success, void *pParam), void *pParam ) +{ + FriendSessionUpdatedFn(true, pParam); +} + +void CPlatformNetworkManagerDurango::ForceFriendsSessionRefresh() +{ + app.DebugPrintf("Resetting friends session search data\n"); + m_lastSearchStartTime = 0; + m_searchResultsCount = 0; + delete [] m_pSearchResults; + m_pSearchResults = NULL; +} + +INetworkPlayer *CPlatformNetworkManagerDurango::addNetworkPlayer(DQRNetworkPlayer *pDQRPlayer) +{ + NetworkPlayerDurango *pNetworkPlayer = new NetworkPlayerDurango(pDQRPlayer); + pDQRPlayer->SetCustomDataValue((ULONG_PTR)pNetworkPlayer); + currentNetworkPlayers.push_back( pNetworkPlayer ); + return pNetworkPlayer; +} + +void CPlatformNetworkManagerDurango::removeNetworkPlayer(DQRNetworkPlayer *pDQRPlayer) +{ + INetworkPlayer *pNetworkPlayer = getNetworkPlayer(pDQRPlayer); + for( AUTO_VAR(it, currentNetworkPlayers.begin()); it != currentNetworkPlayers.end(); it++ ) + { + if( *it == pNetworkPlayer ) + { + currentNetworkPlayers.erase(it); + return; + } + } +} + +INetworkPlayer *CPlatformNetworkManagerDurango::getNetworkPlayer(DQRNetworkPlayer *pDQRPlayer) +{ + return pDQRPlayer ? (INetworkPlayer *)(pDQRPlayer->GetCustomDataValue()) : NULL; +} + + +INetworkPlayer *CPlatformNetworkManagerDurango::GetLocalPlayerByUserIndex(int userIndex ) +{ + return getNetworkPlayer(m_pDQRNet->GetLocalPlayerByUserIndex(userIndex)); +} + +INetworkPlayer *CPlatformNetworkManagerDurango::GetPlayerByIndex(int playerIndex) +{ + return getNetworkPlayer(m_pDQRNet->GetPlayerByIndex(playerIndex)); +} + +INetworkPlayer * CPlatformNetworkManagerDurango::GetPlayerByXuid(PlayerUID xuid) +{ + return getNetworkPlayer(m_pDQRNet->GetPlayerByXuid(xuid)) ; +} + +INetworkPlayer * CPlatformNetworkManagerDurango::GetPlayerBySmallId(unsigned char smallId) +{ + return getNetworkPlayer(m_pDQRNet->GetPlayerBySmallId(smallId)); +} + +wstring CPlatformNetworkManagerDurango::GetDisplayNameByGamertag(wstring gamertag) +{ + return m_pDQRNet->GetDisplayNameByGamertag(gamertag); +} + +INetworkPlayer *CPlatformNetworkManagerDurango::GetHostPlayer() +{ + return getNetworkPlayer(m_pDQRNet->GetHostPlayer()); +} + +bool CPlatformNetworkManagerDurango::IsHost() +{ + return m_pDQRNet->IsHost() && !m_bHostChanged; +} + +bool CPlatformNetworkManagerDurango::JoinGameFromInviteInfo( int userIndex, int userMask, const INVITE_INFO *pInviteInfo) +{ + m_pDQRNet->m_currentUserMask = userMask; + m_pDQRNet->JoinSessionFromInviteInfo(userMask); + return true; +} + +void CPlatformNetworkManagerDurango::SetSessionTexturePackParentId( int id ) +{ + m_hostGameSessionData.texturePackParentId = id; +} + +void CPlatformNetworkManagerDurango::SetSessionSubTexturePackId( int id ) +{ + m_hostGameSessionData.subTexturePackId = id; +} + +void CPlatformNetworkManagerDurango::Notify(int ID, ULONG_PTR Param) +{ +} + +bool CPlatformNetworkManagerDurango::IsInSession() +{ + return m_pDQRNet->IsInSession(); +} + +bool CPlatformNetworkManagerDurango::IsInGameplay() +{ + return m_pDQRNet->GetState() == DQRNetworkManager::DNM_STATE_PLAYING; +} + +bool CPlatformNetworkManagerDurango::IsReadyToPlayOrIdle() +{ + return m_pDQRNet->IsReadyToPlayOrIdle(); +} + +bool CPlatformNetworkManagerDurango::IsSessionJoinable() +{ + return m_hostGameSessionIsJoinable; +} \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/PlatformNetworkManagerDurango.h b/Minecraft.Client/Durango/Network/PlatformNetworkManagerDurango.h new file mode 100644 index 00000000..55807227 --- /dev/null +++ b/Minecraft.Client/Durango/Network/PlatformNetworkManagerDurango.h @@ -0,0 +1,168 @@ +#pragma once +using namespace std; +#include +#include "..\..\..\Minecraft.World\C4JThread.h" +#include "..\..\Common\Network\NetworkPlayerInterface.h" +#include "..\..\Common\Network\PlatformNetworkManagerInterface.h" +#include "..\..\Common\Network\SessionInfo.h" +#include "DQRNetworkManager.h" + +#define MINECRAFT_DURANGO_PARTY_SEARCH_DELAY_MILLISECONDS 30000 + +class CPlatformNetworkManagerDurango : public CPlatformNetworkManager, IDQRNetworkManagerListener +{ + friend class CGameNetworkManager; +public: + virtual bool Initialise(CGameNetworkManager *pGameNetworkManager, int flagIndexSize); + virtual void Terminate(); + virtual int GetJoiningReadyPercentage(); + virtual int CorrectErrorIDS(int IDS); + + virtual void DoWork(); + virtual int GetPlayerCount(); + virtual int GetOnlinePlayerCount(); + virtual int GetLocalPlayerMask(int playerIndex); + virtual bool AddLocalPlayerByUserIndex( int userIndex ); + virtual bool RemoveLocalPlayerByUserIndex( int userIndex ); + virtual INetworkPlayer *GetLocalPlayerByUserIndex( int userIndex ); + virtual INetworkPlayer *GetPlayerByIndex(int playerIndex); + virtual INetworkPlayer * GetPlayerByXuid(PlayerUID xuid); + virtual INetworkPlayer * GetPlayerBySmallId(unsigned char smallId); + virtual wstring GetDisplayNameByGamertag(wstring gamertag); + virtual bool ShouldMessageForFullSession(); + + virtual INetworkPlayer *GetHostPlayer(); + virtual bool IsHost(); + virtual bool JoinGameFromInviteInfo( int userIndex, int userMask, const INVITE_INFO *pInviteInfo); + virtual bool LeaveGame(bool bMigrateHost); + + virtual bool IsInSession(); + virtual bool IsInGameplay(); + virtual bool IsReadyToPlayOrIdle(); + virtual bool IsInStatsEnabledSession(); + virtual bool SessionHasSpace(unsigned int spaceRequired = 1); + virtual void SendInviteGUI(int quadrant); + virtual bool IsAddingPlayer(); + + virtual void HostGame(int localUsersMask, bool bOnlineGame, bool bIsPrivate, unsigned char publicSlots = MINECRAFT_NET_MAX_PLAYERS, unsigned char privateSlots = 0); + virtual int JoinGame(FriendSessionInfo *searchResult, int localUsersMask, int primaryUserIndex ); + virtual void CancelJoinGame(); + virtual bool SetLocalGame(bool isLocal); + virtual bool IsLocalGame() { return m_bIsOfflineGame; } + virtual void SetPrivateGame(bool isPrivate); + virtual bool IsPrivateGame() { return m_bIsPrivateGame; } + virtual bool IsLeavingGame() { return m_bLeavingGame; } + virtual void ResetLeavingGame() { m_bLeavingGame = false; } + + virtual void RegisterPlayerChangedCallback(int iPad, void (*callback)(void *callbackParam, INetworkPlayer *pPlayer, bool leaving), void *callbackParam); + virtual void UnRegisterPlayerChangedCallback(int iPad, void (*callback)(void *callbackParam, INetworkPlayer *pPlayer, bool leaving), void *callbackParam); + + virtual void HandleSignInChange(); + + virtual bool _RunNetworkGame(); + +private: + bool isSystemPrimaryPlayer(DQRNetworkPlayer *pDQRPlayer); + virtual bool _LeaveGame(bool bMigrateHost, bool bLeaveRoom); + virtual void _HostGame(int dwUsersMask, unsigned char publicSlots = MINECRAFT_NET_MAX_PLAYERS, unsigned char privateSlots = 0); + virtual bool _StartGame(); + + DQRNetworkManager * m_pDQRNet; // pointer to SQRNetworkManager interface + + HANDLE m_notificationListener; + + vector m_machineDQRPrimaryPlayers; // collection of players that we deem to be the main one for that system + + bool m_bLeavingGame; + bool m_bLeaveGameOnTick; + bool m_migrateHostOnLeave; + bool m_bHostChanged; + + bool m_bIsOfflineGame; + bool m_bIsPrivateGame; + int m_flagIndexSize; + + // This is only maintained by the host, and is not valid on client machines + GameSessionData m_hostGameSessionData; + bool m_hostGameSessionIsJoinable; + CGameNetworkManager *m_pGameNetworkManager; +public: + virtual void UpdateAndSetGameSessionData(INetworkPlayer *pNetworkPlayerLeaving = NULL); + +private: + // TODO 4J Stu - Do we need to be able to have more than one of these? + void (*playerChangedCallback[XUSER_MAX_COUNT])(void *callbackParam, INetworkPlayer *pPlayer, bool leaving); + void *playerChangedCallbackParam[XUSER_MAX_COUNT]; + + static int RemovePlayerOnSocketClosedThreadProc( void* lpParam ); + virtual bool RemoveLocalPlayer( INetworkPlayer *pNetworkPlayer ); + + // Things for handling per-system flags + class PlayerFlags + { + public: + INetworkPlayer *m_pNetworkPlayer; + unsigned char *flags; + unsigned int count; + PlayerFlags(INetworkPlayer *pNetworkPlayer, unsigned int count); + ~PlayerFlags(); + }; + vector m_playerFlags; + void SystemFlagAddPlayer(INetworkPlayer *pNetworkPlayer); + void SystemFlagRemovePlayer(INetworkPlayer *pNetworkPlayer); + void SystemFlagReset(); +public: + virtual void SystemFlagSet(INetworkPlayer *pNetworkPlayer, int index); + virtual bool SystemFlagGet(INetworkPlayer *pNetworkPlayer, int index); + + // For telemetry +private: + float m_lastPlayerEventTimeStart; + +public: + wstring GatherStats(); + wstring GatherRTTStats(); + +private: + vector friendsSessions[XUSER_MAX_COUNT]; + int m_searchResultsCount; + int m_lastSearchStartTime; + + // The results that will be filled in with the current search + DQRNetworkManager::SessionSearchResult *m_pSearchResults; + + int m_lastSearchPad; + bool m_bSearchResultsReady; + bool m_bSearchPending; + LPVOID m_pSearchParam; + void (*m_SessionsUpdatedCallback)(LPVOID pParam); + + void TickSearch(); + + vectorcurrentNetworkPlayers; + INetworkPlayer *addNetworkPlayer(DQRNetworkPlayer *pDQRPlayer); + void removeNetworkPlayer(DQRNetworkPlayer *pDQRPlayer); + static INetworkPlayer *getNetworkPlayer(DQRNetworkPlayer *pDQRPlayer); + + virtual void SetSessionTexturePackParentId( int id ); + virtual void SetSessionSubTexturePackId( int id ); + virtual void Notify(int ID, ULONG_PTR Param); + +public: + virtual vector *GetSessionList(int iPad, int localPlayers, bool partyOnly); + virtual bool GetGameSessionInfo(int iPad, SessionID sessionId,FriendSessionInfo *foundSession); + virtual void SetSessionsUpdatedCallback( void (*SessionsUpdatedCallback)(LPVOID pParam), LPVOID pSearchParam ); + virtual void GetFullFriendSessionInfo( FriendSessionInfo *foundSession, void (* FriendSessionUpdatedFn)(bool success, void *pParam), void *pParam ); + virtual void ForceFriendsSessionRefresh(); + + // ... and the new ones that have been converted to IDQRNetworkManagerListener + virtual void HandleDataReceived(DQRNetworkPlayer *playerFrom, DQRNetworkPlayer *playerTo, unsigned char *data, unsigned int dataSize); + virtual void HandlePlayerJoined(DQRNetworkPlayer *player); + virtual void HandlePlayerLeaving(DQRNetworkPlayer *player); + virtual void HandleStateChange(DQRNetworkManager::eDQRNetworkManagerState oldState, DQRNetworkManager::eDQRNetworkManagerState newState); +// virtual void HandleResyncPlayerRequest(DQRNetworkPlayer **aPlayers); + virtual void HandleAddLocalPlayerFailed(int idx, bool serverFull); + virtual void HandleDisconnect(bool bLostRoomOnly); + virtual void HandleInviteReceived(int playerIndex, DQRNetworkManager::SessionInfo *pInviteInfo); + virtual bool IsSessionJoinable(); +}; \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/Windows.Xbox.Networking.RealtimeSession.dll b/Minecraft.Client/Durango/Network/Windows.Xbox.Networking.RealtimeSession.dll new file mode 100644 index 00000000..7ff53c86 Binary files /dev/null and b/Minecraft.Client/Durango/Network/Windows.Xbox.Networking.RealtimeSession.dll differ diff --git a/Minecraft.Client/Durango/Network/base64.cpp b/Minecraft.Client/Durango/Network/base64.cpp new file mode 100644 index 00000000..c5af745c --- /dev/null +++ b/Minecraft.Client/Durango/Network/base64.cpp @@ -0,0 +1,156 @@ +/* + base64.cpp and base64.h + + Copyright (C) 2004-2008 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include "stdafx.h" + +#include "base64.h" +#include + +static const std::wstring base64_chars = + L"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + L"abcdefghijklmnopqrstuvwxyz" + L"0123456789+/"; + + +static inline bool is_base64(wchar_t c) +{ + return (isalnum(c) || (c == L'+') || (c == L'/')); +} + +// 4J changed to use Platform::String +Platform::String^ base64_encode(unsigned char* chars_to_encode, unsigned int in_len) +{ + std::wstring ret; + int i = 0; + int j = 0; + unsigned char char_array_3[3]; + unsigned char char_array_4[4]; + + while (in_len--) + { + char_array_3[i++] = *(chars_to_encode++); + if (i == 3) + { + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for(int ii = 0; (ii <4) ; ii++) + { + ret += base64_chars[char_array_4[ii]]; + } + i = 0; + } + } + + if (i) + { + for(j = i; j < 3; j++) + { + char_array_3[j] = '\0'; + } + + char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; + char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); + char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); + char_array_4[3] = char_array_3[2] & 0x3f; + + for (j = 0; (j < i + 1); j++) + { + ret += base64_chars[char_array_4[j]]; + } + + while((i++ < 3)) + { + ret += L'='; + } + + } + + return ref new Platform::String(ret.c_str()); + +} + +void base64_decode(Platform::String ^encoded_string, unsigned char *output, unsigned int out_len) +{ + int in_len = encoded_string->Length(); + int i = 0; + int j = 0; + int in_ = 0; + unsigned char char_array_4[4]; + unsigned char char_array_3[3]; + + unsigned char *pucOut = output; + + while (in_len-- && ( encoded_string->Data()[in_] != L'=') && is_base64(encoded_string->Data()[in_])) + { + char_array_4[i++] = (unsigned char)(encoded_string->Data()[in_]); + in_++; + if (i ==4) + { + for (i = 0; i <4; i++) + { + char_array_4[i] = (unsigned char )base64_chars.find(char_array_4[i]); + } + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (i = 0; (i < 3); i++) + { + *pucOut++ = char_array_3[i]; + if( ( pucOut - output ) >= out_len ) return; + } + i = 0; + } + } + + if(i) + { + for (j = i; j <4; j++) + { + char_array_4[j] = 0; + } + + for (j = 0; j <4; j++) + { + char_array_4[j] = (unsigned char )base64_chars.find(char_array_4[j]); + } + + char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); + char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); + char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; + + for (j = 0; (j < i - 1); j++) + { + *pucOut++ = char_array_3[j]; + if( ( pucOut - output ) >= out_len ) return; + } + } +} \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/base64.h b/Minecraft.Client/Durango/Network/base64.h new file mode 100644 index 00000000..637a0832 --- /dev/null +++ b/Minecraft.Client/Durango/Network/base64.h @@ -0,0 +1,6 @@ +#pragma once + +#include + +Platform::String^ base64_encode(unsigned char* chars_to_encode, unsigned int in_len); +void base64_decode(Platform::String ^encoded_string, unsigned char *output, unsigned int out_len); \ No newline at end of file diff --git a/Minecraft.Client/Durango/Network/windows.xbox.networking.realtimesession.pdb b/Minecraft.Client/Durango/Network/windows.xbox.networking.realtimesession.pdb new file mode 100644 index 00000000..18025f4f Binary files /dev/null and b/Minecraft.Client/Durango/Network/windows.xbox.networking.realtimesession.pdb differ diff --git a/Minecraft.Client/Durango/Network/windows.xbox.networking.realtimesession.winmd b/Minecraft.Client/Durango/Network/windows.xbox.networking.realtimesession.winmd new file mode 100644 index 00000000..3cf7a546 Binary files /dev/null and b/Minecraft.Client/Durango/Network/windows.xbox.networking.realtimesession.winmd differ -- cgit v1.2.3